Class: Unisec::Bidi::Spoof

Inherits:
Object
  • Object
show all
Defined in:
lib/unisec/bidi.rb

Overview

Attack using BiDi code points like RtLO, for example, for spoofing a domain name or a file name

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input, **opts) ⇒ Spoof

Returns a new instance of Spoof.

Examples:

bd = Unisec::Bidi::Spoof.new('https://moc.example.org//:sptth')
bd.target_display # => "https://moc.example.org//:sptth"
bd.spoof_string # => "https://gro.elpmaxe.com//:sptth"
bd.spoof_payload => "‮https://gro.elpmaxe.com//:sptth‬"

Parameters:

  • input (String)

    the target string

  • opts (Hash)

    optional parameters, see bidi_affix



43
44
45
46
47
48
49
# File 'lib/unisec/bidi.rb', line 43

def initialize(input, **opts)
  opts[:index] ||= opts[:infix_pos]

  @target_display = input
  @spoof_string = reverse(**opts)
  @spoof_payload = bidi_affix(**opts)
end

Instance Attribute Details

#spoof_payloadString (readonly)

The string for the spoofing attack with the BiDi characters. (Spoof payload = spoof string + BiDi)

Returns:

  • (String)

    the spoof string (with BiDi)



34
35
36
# File 'lib/unisec/bidi.rb', line 34

def spoof_payload
  @spoof_payload
end

#spoof_stringString (readonly)

The string for the spoofing attack without the BiDi characters

Returns:

  • (String)

    the spoof string (without BiDi)



30
31
32
# File 'lib/unisec/bidi.rb', line 30

def spoof_string
  @spoof_string
end

#target_displayString (readonly)

The target string to spoof (eg. URL, domain or file name)

Returns:

  • (String)

    the target string



13
14
15
# File 'lib/unisec/bidi.rb', line 13

def target_display
  @target_display
end

Class Method Details

.bidi_affix(input, **opts) ⇒ String

Inject BiDi characters into the input string

Examples:

# By default inject a RLO prefix, a PDF suffix and no infix.
Unisec::Bidi::Spoof.bidi_affix('acceis')
# => "‮acceis‬"

# RLI ... PDI
Unisec::Bidi::Spoof.bidi_affix('acceis', prefix: "\u{2067}", suffix: "\u{2069}")
# => "⁧acceis⁩"

# RLE ... PDF
Unisec::Bidi::Spoof.bidi_affix('acceis', prefix: "\u{202B}", suffix: "\u{202C}")
# => "‫acceis‬"

# RLO ... PDF
Unisec::Bidi::Spoof.bidi_affix('https://moc.example.org//:sptth', prefix: "\u{202E}", suffix: "\u{202C}")
# => "‮https://moc.example.org//:sptth‬"

# FSI RLO ... PDF PDI
Unisec::Bidi::Spoof.bidi_affix('https://moc.example.org//:sptth', prefix: "\u{2068 202E}", suffix: "\u{202C 2069}")
# => "⁨‮https://moc.example.org//:sptth‬⁩"

# RLM ...
Unisec::Bidi::Spoof.bidi_affix('unicode', prefix: "\u{200F}", suffix: '')
# => "‏unicode"

# For file name spoofing, it is useful to be able to inject just a RLO before the fake extension
# so we can void the prefix and suffix and just set the position of an infix
ex = Unisec::Bidi::Spoof.bidi_affix('document_anntxt.exe', prefix: '', suffix: '', infix_bidi: "\u{202E}", infix_pos: 12)
# => "document_ann‮txt.exe"
puts ex
# document_ann‮txt.exe

Parameters:

  • input (String)

    input string

  • opts (Hash)

    optional parameters

Options Hash (**opts):

  • :prefix (String)

    Prefix Bidi. Default: RLO (U+202E).

  • :suffix (String)

    Suffix Bidi. Default: PDF (U+202C).

  • :infix_bidi (String)

    Bidi injected at a chosen position. Default: none (empty string).

  • :infix_pos (String)

    Position (index) where to inject an extra BiDi. Default: 0.

Returns:

  • (String)

    spoof payload (input string with injected BiDi)



112
113
114
115
116
117
118
119
120
121
# File 'lib/unisec/bidi.rb', line 112

def self.bidi_affix(input, **opts)
  opts[:prefix] ||= "\u{202E}" # RLO
  opts[:suffix] ||= "\u{202C}" # PDF
  opts[:infix_bidi] ||= ''
  opts[:infix_pos] ||= 0

  out = "#{opts[:prefix]}#{input}#{opts[:suffix]}"
  out.insert(opts[:infix_pos], opts[:infix_bidi])
  out
end

.reverse(target, **opts) ⇒ String

Reverse the (sub)-string (grapheme cluster aware)

Examples:

Unisec::Bidi::Spoof.reverse('document_anntxt.exe', index: 12)
# => "document_annexe.txt"

Unisec::Bidi::Spoof.reverse("🇫🇷🐓")
# => "🐓🇫🇷"

Parameters:

  • target (String)

    string to reverse

  • opts (Hash)

    optional parameters

Options Hash (**opts):

  • :index (String)

    Index at which the revese starts (before this position will be left untouched)

Returns:

  • (String)

    the reversed string



62
63
64
65
66
# File 'lib/unisec/bidi.rb', line 62

def self.reverse(target, **opts)
  opts[:index] ||= 0

  target[0...opts[:index]] + Unisec::Utils::String.grapheme_reverse(target[opts[:index]..])
end

Instance Method Details

#bidi_affix(**opts) ⇒ Object

Call bidi_affix with @spoof_string as input.



124
125
126
# File 'lib/unisec/bidi.rb', line 124

def bidi_affix(**opts)
  Spoof.bidi_affix(@spoof_string, **opts)
end

#display(light: false) ⇒ String

Display a CLI-friendly output summurizing the spoof payload

The light version displays only the spoof payload for easy piping with other commands.

Examples:

puts Unisec::Bidi::Spoof.new('noraj').display
# Target string: noraj
# Spoof payload (display) ⚠: ‮jaron‬
# Spoof string 🛈: jaron
# Spoof payload (hex): e280ae6a61726f6ee280ac
# Spoof payload (hex, escaped): \xe2\x80\xae\x6a\x61\x72\x6f\x6e\xe2\x80\xac
# Spoof payload (base64): 4oCuamFyb27igKw=
# Spoof payload (urlencode): %E2%80%AEjaron%E2%80%AC
# Spoof payload (code points): U+202E U+006A U+0061 U+0072 U+006F U+006E U+202C
#
#
#
# ⚠: 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.
# 🛈: Does not contain the BiDi character (e.g. RtLO).

puts Unisec::Bidi::Spoof.new('noraj').display(light: true)
# ‮jaron‬

Parameters:

  • light (Boolean) (defaults to: false)

    true = light display (displays only the spoof payload for easy piping with other commands), false (default) = full display.

Returns:

  • (String)

    CLI-ready output



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/unisec/bidi.rb', line 151

def display(light: false)
  if light == false # full display
    "Target string: #{@target_display}\n" \
      "Spoof payload (display) ⚠: #{@spoof_payload}\n" \
      "Spoof string 🛈: #{@spoof_string}\n" \
      "Spoof payload (hex): #{@spoof_payload.to_hex}\n" \
      "Spoof payload (hex, escaped): #{@spoof_payload.to_hex(prefixall: '\\x')}\n" \
      "Spoof payload (base64): #{@spoof_payload.to_b64}\n" \
      "Spoof payload (urlencode): #{@spoof_payload.urlencode}\n" \
      "Spoof payload (code points): #{Unisec::Properties.chars2codepoints(@spoof_payload)}\n" \
      "\n\n\n" \
      '⚠: 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.\n" \
      '🛈: Does not contain the BiDi character (e.g. RtLO).'
  else # light display
    @spoof_payload
  end
end

#reverse(**opts) ⇒ Object

Call reverse with @target_display as default input (target).



69
70
71
# File 'lib/unisec/bidi.rb', line 69

def reverse(**opts)
  Spoof.reverse(@target_display, **opts)
end

#set_target_display(input, **opts) ⇒ String

Set a new target string to spoof

It will automatically set @spoof_string and @spoof_payload as well.

Parameters:

  • input (String)

    the target string

  • opts (Hash)

    optional parameters, see bidi_affix

Returns:

  • (String)

    the target string



21
22
23
24
25
26
# File 'lib/unisec/bidi.rb', line 21

def set_target_display(input, **opts)
  @target_display = input
  @spoof_string = reverse(**opts)
  @spoof_payload = bidi_affix(**opts)
  @target_display
end