unisec 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/unisec/bidi.rb +1 -0
- data/lib/unisec/cli/cli.rb +3 -0
- data/lib/unisec/cli/hexdump.rb +4 -1
- data/lib/unisec/cli/normalization.rb +87 -0
- data/lib/unisec/hexdump.rb +1 -0
- data/lib/unisec/normalization.rb +137 -0
- data/lib/unisec/surrogates.rb +1 -0
- data/lib/unisec/version.rb +1 -1
- data/lib/unisec/versions.rb +13 -13
- data/lib/unisec.rb +1 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 60d2859aca7324908f4894b8ca2cc99b145657e7ea68fbc51e11a1e787886a57
|
4
|
+
data.tar.gz: 4c426795faa520f02daec94f1cf14379a4a994a0902cc52640e2a358b210c28b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8d5c67bf6bc1e76253971cc4b3bdccc513fb13fd3d3fa2dcdffa97329e09b45654b2f2f0a2c734e54c87bcffdb71adfa392a352ec757b6de0f761feaa8b73d28
|
7
|
+
data.tar.gz: 7be763bb5b2adb2139771c3e3f789c101f884209c7ca7e5488df18e97c6cac25578b11c34b451efaddeb11a9926fefeb935857da32a33e3de192b0e39cb46532
|
data/lib/unisec/bidi.rb
CHANGED
@@ -129,6 +129,7 @@ module Unisec
|
|
129
129
|
#
|
130
130
|
# The light version displays only the spoof payload for easy piping with other commands.
|
131
131
|
# @param light [Boolean] `true` = light display (displays only the spoof payload for easy piping with other commands), `false` (default) = full display.
|
132
|
+
# @return [String] CLI-ready output
|
132
133
|
# @example
|
133
134
|
# puts Unisec::Bidi::Spoof.new('noraj').display
|
134
135
|
# # Target string: noraj
|
data/lib/unisec/cli/cli.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'unisec/cli/bidi'
|
4
4
|
require 'unisec/cli/confusables'
|
5
5
|
require 'unisec/cli/hexdump'
|
6
|
+
require 'unisec/cli/normalization'
|
6
7
|
require 'unisec/cli/properties'
|
7
8
|
require 'unisec/cli/rugrep'
|
8
9
|
require 'unisec/cli/size'
|
@@ -23,6 +24,8 @@ module Unisec
|
|
23
24
|
register 'confusables randomize', Confusables::Randomize
|
24
25
|
register 'grep', Grep
|
25
26
|
register 'hexdump', Hexdump
|
27
|
+
register 'normalize all', Normalize::All
|
28
|
+
register 'normalize replace', Normalize::Replace
|
26
29
|
register 'properties char', Properties::Char
|
27
30
|
register 'properties codepoints', Properties::Codepoints
|
28
31
|
register 'properties list', Properties::List
|
data/lib/unisec/cli/hexdump.rb
CHANGED
@@ -17,6 +17,9 @@ module Unisec
|
|
17
17
|
# UTF-16LE: 4100 4300 4300 4500 4900 5300
|
18
18
|
# UTF-32BE: 00000041 00000043 00000043 00000045 00000049 00000053
|
19
19
|
# UTF-32LE: 41000000 43000000 43000000 45000000 49000000 53000000
|
20
|
+
#
|
21
|
+
# $unisec hexdump "ACCEIS" --enc utf16le
|
22
|
+
# 4100 4300 4300 4500 4900 5300
|
20
23
|
# ```
|
21
24
|
class Hexdump < Dry::CLI::Command
|
22
25
|
desc 'Hexdump in all Unicode encodings'
|
@@ -35,7 +38,7 @@ module Unisec
|
|
35
38
|
puts Unisec::Hexdump.new(input).display
|
36
39
|
else
|
37
40
|
# using send() is safe here thanks to the value whitelist
|
38
|
-
puts
|
41
|
+
puts Unisec::Hexdump.send(options[:enc], input)
|
39
42
|
end
|
40
43
|
end
|
41
44
|
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/cli'
|
4
|
+
require 'unisec'
|
5
|
+
require 'unisec/utils'
|
6
|
+
|
7
|
+
module Unisec
|
8
|
+
module CLI
|
9
|
+
module Commands
|
10
|
+
# CLI sub-commands `unisec normalize xxx` for the class {Unisec::Normalization} from the lib.
|
11
|
+
module Normalize
|
12
|
+
# Command `unisec normalize all "example"`
|
13
|
+
#
|
14
|
+
# Example:
|
15
|
+
#
|
16
|
+
# ```plaintext
|
17
|
+
# ➜ unisec normalize all ẛ̣
|
18
|
+
# Original: ẛ̣
|
19
|
+
# U+1E9B U+0323
|
20
|
+
# NFC: ẛ̣
|
21
|
+
# U+1E9B U+0323
|
22
|
+
# NFKC: ṩ
|
23
|
+
# U+1E69
|
24
|
+
# NFD: ẛ̣
|
25
|
+
# U+017F U+0323 U+0307
|
26
|
+
# NFKD: ṩ
|
27
|
+
# U+0073 U+0323 U+0307
|
28
|
+
#
|
29
|
+
# ➜ unisec normalize all ẛ̣ --form nfkd
|
30
|
+
# ṩ
|
31
|
+
# ```
|
32
|
+
class All < Dry::CLI::Command
|
33
|
+
desc 'Normalize in all forms'
|
34
|
+
|
35
|
+
argument :input, required: true,
|
36
|
+
desc: 'String input. Read from STDIN if equal to -.'
|
37
|
+
|
38
|
+
option :form, default: nil, values: %w[nfc nfkc nfd nfkd],
|
39
|
+
desc: 'Output only in the specified normalization form.'
|
40
|
+
|
41
|
+
# Normalize in all forms
|
42
|
+
# @param input [String] Input string to normalize
|
43
|
+
def call(input: nil, **options)
|
44
|
+
input = $stdin.read.chomp if input == '-'
|
45
|
+
if options[:form].nil?
|
46
|
+
puts Unisec::Normalization.new(input).display
|
47
|
+
else
|
48
|
+
# using send() is safe here thanks to the value whitelist
|
49
|
+
puts Unisec::Normalization.send(options[:form], input)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Command `unisec normalize replace "example"`
|
55
|
+
#
|
56
|
+
# Example:
|
57
|
+
#
|
58
|
+
# ```plaintext
|
59
|
+
# ➜ unisec normalize replace "<svg onload=\"alert('XSS')\">"
|
60
|
+
# Original: <svg onload="alert('XSS')">
|
61
|
+
# U+003C U+0073 U+0076 U+0067 U+0020 U+006F U+006E U+006C U+006F U+0061 U+0064 U+003D U+0022 U+0061 U+006C U+0065 U+0072 U+0074 U+0028 U+0027 U+0058 U+0053 U+0053 U+0027 U+0029 U+0022 U+003E
|
62
|
+
# Bypass payload: ﹤svg onload="alert('XSS')"﹥
|
63
|
+
# U+FE64 U+0073 U+0076 U+0067 U+0020 U+006F U+006E U+006C U+006F U+0061 U+0064 U+003D U+FF02 U+0061 U+006C U+0065 U+0072 U+0074 U+0028 U+FF07 U+0058 U+0053 U+0053 U+FF07 U+0029 U+FF02 U+FE65
|
64
|
+
# NFKC: <svg onload="alert('XSS')">
|
65
|
+
# U+003C U+0073 U+0076 U+0067 U+0020 U+006F U+006E U+006C U+006F U+0061 U+0064 U+003D U+0022 U+0061 U+006C U+0065 U+0072 U+0074 U+0028 U+0027 U+0058 U+0053 U+0053 U+0027 U+0029 U+0022 U+003E
|
66
|
+
# NFKD: <svg onload="alert('XSS')">
|
67
|
+
# U+003C U+0073 U+0076 U+0067 U+0020 U+006F U+006E U+006C U+006F U+0061 U+0064 U+003D U+0022 U+0061 U+006C U+0065 U+0072 U+0074 U+0028 U+0027 U+0058 U+0053 U+0053 U+0027 U+0029 U+0022 U+003E
|
68
|
+
#
|
69
|
+
# ➜ echo -n "<svg onload=\"alert('XSS')\">" | unisec normalize replace -
|
70
|
+
# ```
|
71
|
+
class Replace < Dry::CLI::Command
|
72
|
+
desc 'Prepare a XSS payload for HTML escape bypass (HTML escape followed by NFKC / NFKD normalization)'
|
73
|
+
|
74
|
+
argument :input, required: true,
|
75
|
+
desc: 'String input. Read from STDIN if equal to -.'
|
76
|
+
|
77
|
+
# Prepare a XSS payload for HTML escape bypass (HTML escape followed by NFKC / NFKD normalization)
|
78
|
+
# @param input [String] Input string to normalize
|
79
|
+
def call(input: nil, **_options)
|
80
|
+
input = $stdin.read.chomp if input == '-'
|
81
|
+
puts Unisec::Normalization.new(input).display_replace
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/lib/unisec/hexdump.rb
CHANGED
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ctf_party'
|
4
|
+
|
5
|
+
module Unisec
|
6
|
+
# Normalization Forms
|
7
|
+
class Normalization
|
8
|
+
# HTML escapable characters mapped with their Unicode counterparts that will
|
9
|
+
# cast to themself after applying normalization forms using compatibility mode.
|
10
|
+
HTML_ESCAPE_BYPASS = {
|
11
|
+
'<' => ['﹤', '<'],
|
12
|
+
'>' => ['﹥', '>'],
|
13
|
+
'"' => ['"'],
|
14
|
+
"'" => ['''],
|
15
|
+
'&' => ['﹠', '&']
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
# Original input
|
19
|
+
# @return [String] untouched input
|
20
|
+
attr_reader :original
|
21
|
+
|
22
|
+
# Normalization Form C (NFC) - Canonical Decomposition, followed by Canonical Composition
|
23
|
+
# @return [String] input normalized with NFC
|
24
|
+
attr_reader :nfc
|
25
|
+
|
26
|
+
# Normalization Form KC (NFKC) - Compatibility Decomposition, followed by Canonical Composition
|
27
|
+
# @return [String] input normalized with NFKC
|
28
|
+
attr_reader :nfkc
|
29
|
+
|
30
|
+
# Normalization Form D (NFD) - Canonical Decomposition
|
31
|
+
# @return [String] input normalized with NFD
|
32
|
+
attr_reader :nfd
|
33
|
+
|
34
|
+
# Normalization Form KD (NFKD) - Compatibility Decomposition
|
35
|
+
# @return [String] input normalized with NFKD
|
36
|
+
attr_reader :nfkd
|
37
|
+
|
38
|
+
# Generate all normilzation forms for a given input
|
39
|
+
# @param str [String] the target string
|
40
|
+
# @return [nil]
|
41
|
+
def initialize(str)
|
42
|
+
@original = str
|
43
|
+
@nfc = Normalization.nfc(str)
|
44
|
+
@nfkc = Normalization.nfkc(str)
|
45
|
+
@nfd = Normalization.nfd(str)
|
46
|
+
@nfkd = Normalization.nfkd(str)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Normalization Form C (NFC) - Canonical Decomposition, followed by Canonical Composition
|
50
|
+
# @param str [String] the target string
|
51
|
+
# @return [String] input normalized with NFC
|
52
|
+
def self.nfc(str)
|
53
|
+
str.unicode_normalize(:nfc)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Normalization Form KC (NFKC) - Compatibility Decomposition, followed by Canonical Composition
|
57
|
+
# @param str [String] the target string
|
58
|
+
# @return [String] input normalized with NFKC
|
59
|
+
def self.nfkc(str)
|
60
|
+
str.unicode_normalize(:nfkc)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Normalization Form D (NFD) - Canonical Decomposition
|
64
|
+
# @param str [String] the target string
|
65
|
+
# @return [String] input normalized with NFD
|
66
|
+
def self.nfd(str)
|
67
|
+
str.unicode_normalize(:nfd)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Normalization Form KD (NFKD) - Compatibility Decomposition
|
71
|
+
# @param str [String] the target string
|
72
|
+
# @return [String] input normalized with NFKD
|
73
|
+
def self.nfkd(str)
|
74
|
+
str.unicode_normalize(:nfkd)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Replace HTML escapable characters with their Unicode counterparts that will
|
78
|
+
# cast to themself after applying normalization forms using compatibility mode.
|
79
|
+
# Usefull for XSS, to bypass HTML escape.
|
80
|
+
# If several values are possible, one is picked randomly.
|
81
|
+
# @param str [String] the target string
|
82
|
+
# @return [String] escaped input
|
83
|
+
def self.replace_bypass(str)
|
84
|
+
str = str.dup
|
85
|
+
HTML_ESCAPE_BYPASS.each do |k, v|
|
86
|
+
str.gsub!(k, v.sample)
|
87
|
+
end
|
88
|
+
str
|
89
|
+
end
|
90
|
+
|
91
|
+
# Instance version of {Normalization.replace_bypass}.
|
92
|
+
def replace_bypass
|
93
|
+
Normalization.replace_bypass(@original)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Display a CLI-friendly output summurizing all normalization forms
|
97
|
+
# @return [String] CLI-ready output
|
98
|
+
# @example
|
99
|
+
# puts Unisec::Normalization.new("\u{1E9B 0323}").display
|
100
|
+
# # =>
|
101
|
+
# # Original: ẛ̣
|
102
|
+
# # U+1E9B U+0323
|
103
|
+
# # NFC: ẛ̣
|
104
|
+
# # U+1E9B U+0323
|
105
|
+
# # NFKC: ṩ
|
106
|
+
# # U+1E69
|
107
|
+
# # NFD: ẛ̣
|
108
|
+
# # U+017F U+0323 U+0307
|
109
|
+
# # NFKD: ṩ
|
110
|
+
# # U+0073 U+0323 U+0307
|
111
|
+
def display
|
112
|
+
colorize = lambda { |form_title, form_attr|
|
113
|
+
"#{Paint[form_title.to_s, :underline,
|
114
|
+
:bold]}: #{form_attr}\n #{Paint[Unisec::Properties.chars2codepoints(form_attr), :red]}\n"
|
115
|
+
}
|
116
|
+
colorize.call('Original', @original) +
|
117
|
+
colorize.call('NFC', @nfc) +
|
118
|
+
colorize.call('NFKC', @nfkc) +
|
119
|
+
colorize.call('NFD', @nfd) +
|
120
|
+
colorize.call('NFKD', @nfkd)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Display a CLI-friendly output of the XSS payload to bypass HTML escape and
|
124
|
+
# what it does once normalized in NFKC & NFKD.
|
125
|
+
def display_replace
|
126
|
+
colorize = lambda { |form_title, form_attr|
|
127
|
+
"#{Paint[form_title.to_s, :underline,
|
128
|
+
:bold]}: #{form_attr}\n #{Paint[Unisec::Properties.chars2codepoints(form_attr), :red]}\n"
|
129
|
+
}
|
130
|
+
payload = replace_bypass
|
131
|
+
colorize.call('Original', @original) +
|
132
|
+
colorize.call('Bypass payload', payload) +
|
133
|
+
colorize.call('NFKC', Normalization.nfkc(payload)) +
|
134
|
+
colorize.call('NFKD', Normalization.nfkd(payload))
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
data/lib/unisec/surrogates.rb
CHANGED
@@ -102,6 +102,7 @@ module Unisec
|
|
102
102
|
# Display a CLI-friendly output summurizing everithing about the surrogates:
|
103
103
|
# the corresponding character, code point, high and low surrogates
|
104
104
|
# (each displayed as hexadecimal, decimal and binary).
|
105
|
+
# @return [String] CLI-ready output
|
105
106
|
# @example
|
106
107
|
# surr = Unisec::Surrogates.new(128169)
|
107
108
|
# puts surr.display # =>
|
data/lib/unisec/version.rb
CHANGED
data/lib/unisec/versions.rb
CHANGED
@@ -72,19 +72,19 @@ module Unisec
|
|
72
72
|
# # …
|
73
73
|
def self.display # rubocop:disable Metrics/AbcSize
|
74
74
|
data = versions
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
75
|
+
colorize = ->(node) { Paint[data[node][:label], :red, :bold].ljust(44) + " #{data[node][:version]}\n" }
|
76
|
+
Paint["Unicode:\n", :underline] +
|
77
|
+
colorize.call(:ruby_unicode) +
|
78
|
+
colorize.call(:twittercldr_unicode) +
|
79
|
+
colorize.call(:unicodeconfusable_unicode) +
|
80
|
+
colorize.call(:twittercldr_icu) +
|
81
|
+
colorize.call(:twittercldr_cldr) +
|
82
|
+
colorize.call(:ruby_unicode_emoji) +
|
83
|
+
colorize.call(:ucd_derivedname) +
|
84
|
+
Paint["\nGems:\n", :underline] +
|
85
|
+
colorize.call(:unisec) +
|
86
|
+
colorize.call(:twittercldr) +
|
87
|
+
colorize.call(:unicodeconfusable)
|
88
88
|
end
|
89
89
|
end
|
90
90
|
end
|
data/lib/unisec.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unisec
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexandre ZANNI
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-05-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ctf-party
|
@@ -88,7 +88,7 @@ dependencies:
|
|
88
88
|
version: '1.9'
|
89
89
|
description: 'Toolkit for security research manipulating Unicode: confusables, homoglyphs,
|
90
90
|
hexdump, code point, UTF-8, UTF-16, UTF-32, properties, regexp search, size, grapheme,
|
91
|
-
surrogates, version, ICU, CLDR, UCD'
|
91
|
+
surrogates, version, ICU, CLDR, UCD, BiDi, normalization'
|
92
92
|
email: alexandre.zanni@europe.com
|
93
93
|
executables:
|
94
94
|
- unisec
|
@@ -104,6 +104,7 @@ files:
|
|
104
104
|
- lib/unisec/cli/cli.rb
|
105
105
|
- lib/unisec/cli/confusables.rb
|
106
106
|
- lib/unisec/cli/hexdump.rb
|
107
|
+
- lib/unisec/cli/normalization.rb
|
107
108
|
- lib/unisec/cli/properties.rb
|
108
109
|
- lib/unisec/cli/rugrep.rb
|
109
110
|
- lib/unisec/cli/size.rb
|
@@ -111,6 +112,7 @@ files:
|
|
111
112
|
- lib/unisec/cli/versions.rb
|
112
113
|
- lib/unisec/confusables.rb
|
113
114
|
- lib/unisec/hexdump.rb
|
115
|
+
- lib/unisec/normalization.rb
|
114
116
|
- lib/unisec/properties.rb
|
115
117
|
- lib/unisec/rugrep.rb
|
116
118
|
- lib/unisec/size.rb
|