unisec 0.0.3 → 0.0.4
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 +170 -0
- data/lib/unisec/cli/bidi.rb +63 -0
- data/lib/unisec/cli/cli.rb +2 -0
- data/lib/unisec/cli/hexdump.rb +12 -3
- data/lib/unisec/utils.rb +10 -0
- data/lib/unisec/version.rb +1 -1
- data/lib/unisec.rb +1 -0
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 207fef6efc641ecf3ba11879d368cd2c6ac54f4d7001d948428c32a67e992e05
|
4
|
+
data.tar.gz: bec8d1577d59b146747a1346794df62ea7928feb47ca3c64e669636f91cdb9fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cb406f1c0b27575d84b497d5cec9a994f05a33f816630ebacd5013594997394c5b5473575d1a241193bb1e586bd3a7510c80a7597d9995f35cbb0f5b09882987
|
7
|
+
data.tar.gz: 994e93feae65577b462e85ac3450dbb4d8a7f6160d00f4a19fe21bae518de85846a2e4cb71668b7fee0c5afb2ea39932831d2e333e802ab048b6dbd5bd653cc1
|
data/lib/unisec/bidi.rb
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'unisec/utils'
|
4
|
+
require 'ctf_party'
|
5
|
+
|
6
|
+
module Unisec
|
7
|
+
# Manipulation of bidirectional related content
|
8
|
+
class Bidi
|
9
|
+
# Attack using BiDi code points like RtLO, for example, for spoofing a domain name or a file name
|
10
|
+
class Spoof
|
11
|
+
# The target string to spoof (eg. URL, domain or file name)
|
12
|
+
# @return [String] the target string
|
13
|
+
attr_reader :target_display
|
14
|
+
|
15
|
+
# Set a new target string to spoof
|
16
|
+
#
|
17
|
+
# It will automatically set `@spoof_string` and `@spoof_payload` as well.
|
18
|
+
# @param input [String] the target string
|
19
|
+
# @param opts [Hash] optional parameters, see {Spoof.bidi_affix}
|
20
|
+
# @return [String] the target string
|
21
|
+
def set_target_display(input, **opts)
|
22
|
+
@target_display = input
|
23
|
+
@spoof_string = reverse(**opts)
|
24
|
+
@spoof_payload = bidi_affix(**opts)
|
25
|
+
@target_display
|
26
|
+
end
|
27
|
+
|
28
|
+
# The string for the spoofing attack without the BiDi characters
|
29
|
+
# @return [String] the spoof string (without BiDi)
|
30
|
+
attr_reader :spoof_string
|
31
|
+
|
32
|
+
# The string for the spoofing attack with the BiDi characters. (Spoof payload = spoof string + BiDi)
|
33
|
+
# @return [String] the spoof string (with BiDi)
|
34
|
+
attr_reader :spoof_payload
|
35
|
+
|
36
|
+
# @param input [String] the target string
|
37
|
+
# @param opts [Hash] optional parameters, see {Spoof.bidi_affix}
|
38
|
+
# @example
|
39
|
+
# bd = Unisec::Bidi::Spoof.new('https://moc.example.org//:sptth')
|
40
|
+
# bd.target_display # => "https://moc.example.org//:sptth"
|
41
|
+
# bd.spoof_string # => "https://gro.elpmaxe.com//:sptth"
|
42
|
+
# bd.spoof_payload => "https://gro.elpmaxe.com//:sptth"
|
43
|
+
def initialize(input, **opts)
|
44
|
+
opts[:index] ||= opts[:infix_pos]
|
45
|
+
|
46
|
+
@target_display = input
|
47
|
+
@spoof_string = reverse(**opts)
|
48
|
+
@spoof_payload = bidi_affix(**opts)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Reverse the (sub)-string (grapheme cluster aware)
|
52
|
+
# @param target [String] string to reverse
|
53
|
+
# @param opts [Hash] optional parameters
|
54
|
+
# @option opts [String] :index Index at which the revese starts (before this position will be left untouched)
|
55
|
+
# @return [String] the reversed string
|
56
|
+
# @example
|
57
|
+
# Unisec::Bidi::Spoof.reverse('document_anntxt.exe', index: 12)
|
58
|
+
# # => "document_annexe.txt"
|
59
|
+
#
|
60
|
+
# Unisec::Bidi::Spoof.reverse("🇫🇷🐓")
|
61
|
+
# # => "🐓🇫🇷"
|
62
|
+
def self.reverse(target, **opts)
|
63
|
+
opts[:index] ||= 0
|
64
|
+
|
65
|
+
target[0...opts[:index]] + Unisec::Utils::String.grapheme_reverse(target[opts[:index]..])
|
66
|
+
end
|
67
|
+
|
68
|
+
# Call {Spoof.reverse} with `@target_display` as default input (target).
|
69
|
+
def reverse(**opts)
|
70
|
+
Spoof.reverse(@target_display, **opts)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Inject BiDi characters into the input string
|
74
|
+
# @param input [String] input string
|
75
|
+
# @param opts [Hash] optional parameters
|
76
|
+
# @option opts [String] :prefix Prefix Bidi. Default: RLO (U+202E).
|
77
|
+
# @option opts [String] :suffix Suffix Bidi. Default: PDF (U+202C).
|
78
|
+
# @option opts [String] :infix_bidi Bidi injected at a chosen position. Default: none (empty string).
|
79
|
+
# @option opts [String] :infix_pos Position (index) where to inject an extra BiDi. Default: 0.
|
80
|
+
# @return [String] spoof payload (input string with injected BiDi)
|
81
|
+
# @example
|
82
|
+
# # By default inject a RLO prefix, a PDF suffix and no infix.
|
83
|
+
# Unisec::Bidi::Spoof.bidi_affix('acceis')
|
84
|
+
# # => "acceis"
|
85
|
+
#
|
86
|
+
# # RLI ... PDI
|
87
|
+
# Unisec::Bidi::Spoof.bidi_affix('acceis', prefix: "\u{2067}", suffix: "\u{2069}")
|
88
|
+
# # => "acceis"
|
89
|
+
#
|
90
|
+
# # RLE ... PDF
|
91
|
+
# Unisec::Bidi::Spoof.bidi_affix('acceis', prefix: "\u{202B}", suffix: "\u{202C}")
|
92
|
+
# # => "acceis"
|
93
|
+
#
|
94
|
+
# # RLO ... PDF
|
95
|
+
# Unisec::Bidi::Spoof.bidi_affix('https://moc.example.org//:sptth', prefix: "\u{202E}", suffix: "\u{202C}")
|
96
|
+
# # => "https://moc.example.org//:sptth"
|
97
|
+
#
|
98
|
+
# # FSI RLO ... PDF PDI
|
99
|
+
# Unisec::Bidi::Spoof.bidi_affix('https://moc.example.org//:sptth', prefix: "\u{2068 202E}", suffix: "\u{202C 2069}")
|
100
|
+
# # => "https://moc.example.org//:sptth"
|
101
|
+
#
|
102
|
+
# # RLM ...
|
103
|
+
# Unisec::Bidi::Spoof.bidi_affix('unicode', prefix: "\u{200F}", suffix: '')
|
104
|
+
# # => "unicode"
|
105
|
+
#
|
106
|
+
# # For file name spoofing, it is useful to be able to inject just a RLO before the fake extension
|
107
|
+
# # so we can void the prefix and suffix and just set the position of an infix
|
108
|
+
# ex = Unisec::Bidi::Spoof.bidi_affix('document_anntxt.exe', prefix: '', suffix: '', infix_bidi: "\u{202E}", infix_pos: 12)
|
109
|
+
# # => "document_anntxt.exe"
|
110
|
+
# puts ex
|
111
|
+
# # document_anntxt.exe
|
112
|
+
def self.bidi_affix(input, **opts)
|
113
|
+
opts[:prefix] ||= "\u{202E}" # RLO
|
114
|
+
opts[:suffix] ||= "\u{202C}" # PDF
|
115
|
+
opts[:infix_bidi] ||= ''
|
116
|
+
opts[:infix_pos] ||= 0
|
117
|
+
|
118
|
+
out = "#{opts[:prefix]}#{input}#{opts[:suffix]}"
|
119
|
+
out.insert(opts[:infix_pos], opts[:infix_bidi])
|
120
|
+
out
|
121
|
+
end
|
122
|
+
|
123
|
+
# Call {Spoof.bidi_affix} with `@spoof_string` as input.
|
124
|
+
def bidi_affix(**opts)
|
125
|
+
Spoof.bidi_affix(@spoof_string, **opts)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Display a CLI-friendly output summurizing the spoof payload
|
129
|
+
#
|
130
|
+
# The light version displays only the spoof payload for easy piping with other commands.
|
131
|
+
# @param light [Boolean] `true` = light display (displays only the spoof payload for easy piping with other commands), `false` (default) = full display.
|
132
|
+
# @example
|
133
|
+
# puts Unisec::Bidi::Spoof.new('noraj').display
|
134
|
+
# # Target string: noraj
|
135
|
+
# # Spoof payload (display) ⚠: jaron
|
136
|
+
# # Spoof string 🛈: jaron
|
137
|
+
# # Spoof payload (hex): e280ae6a61726f6ee280ac
|
138
|
+
# # Spoof payload (hex, escaped): \xe2\x80\xae\x6a\x61\x72\x6f\x6e\xe2\x80\xac
|
139
|
+
# # Spoof payload (base64): 4oCuamFyb27igKw=
|
140
|
+
# # Spoof payload (urlencode): %E2%80%AEjaron%E2%80%AC
|
141
|
+
# # Spoof payload (code points): U+202E U+006A U+0061 U+0072 U+006F U+006E U+202C
|
142
|
+
# #
|
143
|
+
# #
|
144
|
+
# #
|
145
|
+
# # ⚠: for the spoof payload to display correctly, be sure your VTE has RTL support, e.g. see https://wiki.archlinux.org/title/Bidirectional_text#Terminal.
|
146
|
+
# # 🛈: Does not contain the BiDi character (e.g. RtLO).
|
147
|
+
#
|
148
|
+
# puts Unisec::Bidi::Spoof.new('noraj').display(light: true)
|
149
|
+
# # jaron
|
150
|
+
def display(light: false)
|
151
|
+
if light == false # full display
|
152
|
+
"Target string: #{@target_display}\n" \
|
153
|
+
"Spoof payload (display) ⚠: #{@spoof_payload}\n" \
|
154
|
+
"Spoof string 🛈: #{@spoof_string}\n" \
|
155
|
+
"Spoof payload (hex): #{@spoof_payload.to_hex}\n" \
|
156
|
+
"Spoof payload (hex, escaped): #{@spoof_payload.to_hex(prefixall: '\\x')}\n" \
|
157
|
+
"Spoof payload (base64): #{@spoof_payload.to_b64}\n" \
|
158
|
+
"Spoof payload (urlencode): #{@spoof_payload.urlencode}\n" \
|
159
|
+
"Spoof payload (code points): #{Unisec::Properties.chars2codepoints(@spoof_payload)}\n" \
|
160
|
+
"\n\n\n" \
|
161
|
+
'⚠: for the spoof payload to display correctly, be sure your VTE has RTL support, ' \
|
162
|
+
"e.g. see https://wiki.archlinux.org/title/Bidirectional_text#Terminal.\n" \
|
163
|
+
'🛈: Does not contain the BiDi character (e.g. RtLO).'
|
164
|
+
else # light display
|
165
|
+
@spoof_payload
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,63 @@
|
|
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 bidi xxx` for the class {Unisec::Bidi} from the lib.
|
11
|
+
module Bidi
|
12
|
+
# Command `unisec bidi spoof`
|
13
|
+
#
|
14
|
+
# Example:
|
15
|
+
#
|
16
|
+
# ```plaintext
|
17
|
+
# $ unisec bidi spoof noraj
|
18
|
+
# Target string: noraj
|
19
|
+
# Spoof payload (display) ⚠: jaron
|
20
|
+
# Spoof string 🛈: jaron
|
21
|
+
# Spoof payload (hex): e280ae6a61726f6ee280ac
|
22
|
+
# Spoof payload (hex, escaped): \xe2\x80\xae\x6a\x61\x72\x6f\x6e\xe2\x80\xac
|
23
|
+
# Spoof payload (base64): 4oCuamFyb27igKw=
|
24
|
+
# Spoof payload (urlencode): %E2%80%AEjaron%E2%80%AC
|
25
|
+
# Spoof payload (code points): U+202E U+006A U+0061 U+0072 U+006F U+006E U+202C
|
26
|
+
#
|
27
|
+
#
|
28
|
+
#
|
29
|
+
# ⚠: for the spoof payload to display correctly, be sure your VTE has RTL support, e.g. see https://wiki.archlinux.org/title/Bidirectional_text#Terminal.
|
30
|
+
# 🛈: Does not contain the BiDi character (e.g. RtLO).
|
31
|
+
#
|
32
|
+
# $ unisec bidi spoof 'document_annexe.txt' --prefix '' --suffix '' --infix-bidi $'\U202E' --infix-pos 12 --light=true
|
33
|
+
# document_anntxt.exe
|
34
|
+
# ```
|
35
|
+
class Spoof < Dry::CLI::Command
|
36
|
+
desc 'Craft a payload for BiDi attacks (for example, for spoofing a domain name or a file name)'
|
37
|
+
|
38
|
+
argument :input, required: true,
|
39
|
+
desc: 'String input'
|
40
|
+
option :light, default: false, values: %w[true false],
|
41
|
+
desc: 'true = light display (displays only the spoof payload for easy piping with other ' \
|
42
|
+
'commands), false = full display'
|
43
|
+
option :prefix, default: nil, desc: 'Prefix Bidi. Default: RLO (U+202E).'
|
44
|
+
option :suffix, default: nil, desc: 'Suffix Bidi. Default: PDF (U+202C).'
|
45
|
+
option :infix_bidi, default: nil, desc: 'Bidi injected at a chosen position. Default: none (empty string).'
|
46
|
+
option :infix_pos, default: nil, desc: 'Spoof payload (input string with injected BiDi)'
|
47
|
+
|
48
|
+
# Craft a payload for BiDi attacks
|
49
|
+
# @param input [String] Input string to spoof
|
50
|
+
# @param options [Hash] optional parameters, see {Unisec::Bidi::Spoof.bidi_affix}
|
51
|
+
def call(input: nil, **options)
|
52
|
+
to_bool = ->(str) { ['true', true].include?(str) }
|
53
|
+
light = to_bool.call(options.fetch(:light))
|
54
|
+
infix_pos = options[:infix_pos].to_i unless options[:infix_pos].nil?
|
55
|
+
puts Unisec::Bidi::Spoof.new(input, prefix: options[:prefix], suffix: options[:suffix],
|
56
|
+
infix_bidi: options[:infix_bidi],
|
57
|
+
infix_pos: infix_pos).display(light: light)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/unisec/cli/cli.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'unisec/cli/bidi'
|
3
4
|
require 'unisec/cli/confusables'
|
4
5
|
require 'unisec/cli/hexdump'
|
5
6
|
require 'unisec/cli/properties'
|
@@ -17,6 +18,7 @@ module Unisec
|
|
17
18
|
|
18
19
|
# Mapping between the (sub-)commands as seen by the user
|
19
20
|
# on the command-line interface and the CLI modules in the lib
|
21
|
+
register 'bidi spoof', Bidi::Spoof
|
20
22
|
register 'confusables list', Confusables::List
|
21
23
|
register 'confusables randomize', Confusables::Randomize
|
22
24
|
register 'grep', Grep
|
data/lib/unisec/cli/hexdump.rb
CHANGED
@@ -22,12 +22,21 @@ module Unisec
|
|
22
22
|
desc 'Hexdump in all Unicode encodings'
|
23
23
|
|
24
24
|
argument :input, required: true,
|
25
|
-
desc: 'String input'
|
25
|
+
desc: 'String input. Read from STDIN if equal to -.'
|
26
|
+
|
27
|
+
option :enc, default: nil, values: %w[utf8 utf16be utf16le utf32be utf32le],
|
28
|
+
desc: 'Output only in the specified encoding.'
|
26
29
|
|
27
30
|
# Hexdump of all Unicode encodings.
|
28
31
|
# @param input [String] Input string to encode
|
29
|
-
def call(input: nil, **)
|
30
|
-
|
32
|
+
def call(input: nil, **options)
|
33
|
+
input = $stdin.read.chomp if input == '-'
|
34
|
+
if options[:enc].nil?
|
35
|
+
puts Unisec::Hexdump.new(input).display
|
36
|
+
else
|
37
|
+
# using send() is safe here thanks to the value whitelist
|
38
|
+
puts puts Unisec::Hexdump.send(options[:enc], input)
|
39
|
+
end
|
31
40
|
end
|
32
41
|
end
|
33
42
|
end
|
data/lib/unisec/utils.rb
CHANGED
@@ -98,6 +98,16 @@ module Unisec
|
|
98
98
|
:string
|
99
99
|
end
|
100
100
|
end
|
101
|
+
|
102
|
+
# Reverse a string by graphemes (not by code points)
|
103
|
+
# @return [String] the reversed string
|
104
|
+
# @example
|
105
|
+
# b = "\u{1f1eb}\u{1f1f7}\u{1F413}" # => "🇫🇷🐓"
|
106
|
+
# b.reverse # => "🐓🇷🇫"
|
107
|
+
# Unisec::Utils::String.grapheme_reverse(b) # => "🐓🇫🇷"
|
108
|
+
def self.grapheme_reverse(str)
|
109
|
+
str.grapheme_clusters.reverse.join
|
110
|
+
end
|
101
111
|
end
|
102
112
|
end
|
103
113
|
end
|
data/lib/unisec/version.rb
CHANGED
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.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexandre ZANNI
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ctf-party
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '3.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '3.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: dry-cli
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -99,6 +99,8 @@ files:
|
|
99
99
|
- bin/unisec
|
100
100
|
- data/DerivedName.txt
|
101
101
|
- lib/unisec.rb
|
102
|
+
- lib/unisec/bidi.rb
|
103
|
+
- lib/unisec/cli/bidi.rb
|
102
104
|
- lib/unisec/cli/cli.rb
|
103
105
|
- lib/unisec/cli/confusables.rb
|
104
106
|
- lib/unisec/cli/hexdump.rb
|
@@ -145,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
145
147
|
- !ruby/object:Gem::Version
|
146
148
|
version: '0'
|
147
149
|
requirements: []
|
148
|
-
rubygems_version: 3.
|
150
|
+
rubygems_version: 3.5.3
|
149
151
|
signing_key:
|
150
152
|
specification_version: 4
|
151
153
|
summary: Unicode Security Toolkit
|