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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d93010e590d0ae588dfb31364036652eb19d5ab17b504fbb851495a2ca71d18d
4
- data.tar.gz: b76c392938f2c804626b81efa227aaf44a65abec7ac63558144644e851b8d475
3
+ metadata.gz: 207fef6efc641ecf3ba11879d368cd2c6ac54f4d7001d948428c32a67e992e05
4
+ data.tar.gz: bec8d1577d59b146747a1346794df62ea7928feb47ca3c64e669636f91cdb9fa
5
5
  SHA512:
6
- metadata.gz: bde0eb13806b6e6ac2a12d8889c7ef236bdc923e334d59409257a95101d48702aefe10cf83b6a3a2c5bd5d12ebe0170f52cfcaff2cc9c2177db8c455f9a5c6b1
7
- data.tar.gz: 3f3d376b4549b846fee8af810ed134aca724eb377853b64bbe3f75562a33f4b799f7808554ad73211a4158a93feb989e4fc7ecc511babf233f7a6ebf358a17fb
6
+ metadata.gz: cb406f1c0b27575d84b497d5cec9a994f05a33f816630ebacd5013594997394c5b5473575d1a241193bb1e586bd3a7510c80a7597d9995f35cbb0f5b09882987
7
+ data.tar.gz: 994e93feae65577b462e85ac3450dbb4d8a7f6160d00f4a19fe21bae518de85846a2e4cb71668b7fee0c5afb2ea39932831d2e333e802ab048b6dbd5bd653cc1
@@ -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_ann‮txt.exe"
110
+ # puts ex
111
+ # # document_ann‮txt.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_ann‮txt.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
@@ -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
@@ -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
- puts Unisec::Hexdump.new(input).display
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
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Unisec
4
4
  # Version of unisec library and app
5
- VERSION = '0.0.3'
5
+ VERSION = '0.0.4'
6
6
  end
data/lib/unisec.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'unisec/version'
4
4
 
5
+ require 'unisec/bidi'
5
6
  require 'unisec/confusables'
6
7
  require 'unisec/hexdump'
7
8
  require 'unisec/properties'
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.3
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: 2023-10-18 00:00:00.000000000 Z
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: '2.3'
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: '2.3'
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.4.10
150
+ rubygems_version: 3.5.3
149
151
  signing_key:
150
152
  specification_version: 4
151
153
  summary: Unicode Security Toolkit