homographic_spoofing 0.1.0

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.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +117 -0
  4. data/lib/homographic_spoofing/detector/base.rb +41 -0
  5. data/lib/homographic_spoofing/detector/detection.rb +2 -0
  6. data/lib/homographic_spoofing/detector/email_address.rb +40 -0
  7. data/lib/homographic_spoofing/detector/idn.rb +78 -0
  8. data/lib/homographic_spoofing/detector/local.rb +14 -0
  9. data/lib/homographic_spoofing/detector/quoted_string.rb +13 -0
  10. data/lib/homographic_spoofing/detector/rule/base.rb +15 -0
  11. data/lib/homographic_spoofing/detector/rule/context.rb +19 -0
  12. data/lib/homographic_spoofing/detector/rule/data/allowed_idn_characters.txt +1 -0
  13. data/lib/homographic_spoofing/detector/rule/data/digits.csv +680 -0
  14. data/lib/homographic_spoofing/detector/rule/disallowed_characters.rb +140 -0
  15. data/lib/homographic_spoofing/detector/rule/idn/base.rb +3 -0
  16. data/lib/homographic_spoofing/detector/rule/idn/context.rb +8 -0
  17. data/lib/homographic_spoofing/detector/rule/idn/dangerous_pattern.rb +73 -0
  18. data/lib/homographic_spoofing/detector/rule/idn/deviation_characters.rb +10 -0
  19. data/lib/homographic_spoofing/detector/rule/idn/digits.rb +25 -0
  20. data/lib/homographic_spoofing/detector/rule/idn/invisible_characters.rb +14 -0
  21. data/lib/homographic_spoofing/detector/rule/idn/script_confusable.rb +59 -0
  22. data/lib/homographic_spoofing/detector/rule/idn/script_specific.rb +31 -0
  23. data/lib/homographic_spoofing/detector/rule/idn/unsafe_middle_dot.rb +12 -0
  24. data/lib/homographic_spoofing/detector/rule/local/dot_atom_text.rb +49 -0
  25. data/lib/homographic_spoofing/detector/rule/local/nfkc.rb +6 -0
  26. data/lib/homographic_spoofing/detector/rule/mixed_digits.rb +30 -0
  27. data/lib/homographic_spoofing/detector/rule/mixed_scripts.rb +30 -0
  28. data/lib/homographic_spoofing/detector/rule/quoted_string/bidi_control.rb +10 -0
  29. data/lib/homographic_spoofing/detector/rule/quoted_string/data/nonspacing_marks.txt +1 -0
  30. data/lib/homographic_spoofing/detector/rule/quoted_string/nfc.rb +6 -0
  31. data/lib/homographic_spoofing/detector/rule/quoted_string/nonspacing_marks.rb +21 -0
  32. data/lib/homographic_spoofing/railtie.rb +5 -0
  33. data/lib/homographic_spoofing/sanitizer/base.rb +39 -0
  34. data/lib/homographic_spoofing/sanitizer/email_address.rb +10 -0
  35. data/lib/homographic_spoofing/sanitizer/idn.rb +10 -0
  36. data/lib/homographic_spoofing/sanitizer/quoted_string.rb +10 -0
  37. data/lib/homographic_spoofing/version.rb +3 -0
  38. data/lib/homographic_spoofing.rb +47 -0
  39. metadata +166 -0
@@ -0,0 +1,140 @@
1
+ # 3. and 4. of Google Chrome IDN policy See https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%3AIdentifierStatus%3DAllowed%3A&abb=on&g=&i=
2
+ class HomographicSpoofing::Detector::Rule::DisallowedCharacters < HomographicSpoofing::Detector::Rule::Base
3
+ class << self
4
+ # See http://kb.mozillazine.org/Network.IDN.blacklist_chars
5
+ MOZZILLA_DISALLOWED_CHARACTERS = Set[
6
+ "\u0020", # Space
7
+ "\u00a0", # No-break space
8
+ "\u00bc", # Vulgar fraction one quarter
9
+ "\u00bd", # Vulgar fraction one half
10
+ "\u00be", # Vulgar fraction three quarters
11
+ "\u01c3", # Latin letter retroflex click
12
+ "\u02d0", # Modifier letter triangular colon
13
+ "\u0337", # Combining short solidus overlay
14
+ "\u0338", # Combining long solidus overlay
15
+ "\u0589", # Armenian full stop
16
+ "\u058a", # Armenian hyphen
17
+ "\u05c3", # Hebrew punctuation sof pasuq
18
+ "\u05f4", # Hebrew punctuation gershayim
19
+ "\u0609", # Arabic-indic per mille sign
20
+ "\u060a", # Arabic-indic per ten thousand sign
21
+ "\u066a", # Arabic percent sign
22
+ "\u06d4", # Arabic full stop
23
+ "\u0701", # Syriac supralinear full stop
24
+ "\u0702", # Syriac sublinear full stop
25
+ "\u0703", # Syriac supralinear colon
26
+ "\u0704", # Syriac sublinear colon
27
+ "\u115f", # Hangul choseong filler
28
+ "\u1160", # Hangul jungseong filler
29
+ "\u1735", # Philippine single punctuation
30
+ "\u2000", # En quad
31
+ "\u2001", # Em quad
32
+ "\u2002", # En space
33
+ "\u2003", # Em space
34
+ "\u2004", # Three-per-em space
35
+ "\u2005", # Four-per-em space
36
+ "\u2006", # Six-per-em-space
37
+ "\u2007", # Figure space
38
+ "\u2008", # Punctuation space
39
+ "\u2009", # Thin space
40
+ "\u200a", # Hair space
41
+ "\u200b", # Zero width space
42
+ "\u200e", # Left-to-right mark
43
+ "\u200f", # Right-to-left mark
44
+ "\u2010", # Hyphen
45
+ "\u2019", # Right single quotation mark
46
+ "\u2024", # One dot leader
47
+ "\u2027", # Hyphenation point
48
+ "\u2028", # Line separator
49
+ "\u2029", # Paragraph separator
50
+ "\u202a", # Left-to-right embedding
51
+ "\u202b", # Right-to-left embedding
52
+ "\u202c", # Pop directional formatting
53
+ "\u202d", # Left-to-right override
54
+ "\u202e", # Right-to-left override
55
+ "\u202f", # Narrow no-break space
56
+ "\u2039", # Single left-pointing angle quotation mark
57
+ "\u203a", # Single right-pointing angle quotation mark
58
+ "\u2041", # Caret insertion point
59
+ "\u2044", # Fraction slash
60
+ "\u2052", # Commercial minus sign
61
+ "\u205f", # Medium mathematical space
62
+ "\u2153", # Vulgar fraction one third
63
+ "\u2154", # Vulgar fraction two thirds
64
+ "\u2155", # Vulgar fraction one fifth
65
+ "\u2156", # Vulgar fraction two fifths
66
+ "\u2157", # Vulgar fraction three fifths
67
+ "\u2158", # Vulgar fraction four fifths
68
+ "\u2159", # Vulgar fraction one sixth
69
+ "\u215a", # Vulgar fraction five sixths
70
+ "\u215b", # Vulgar fraction one eight
71
+ "\u215c", # Vulgar fraction three eighths
72
+ "\u215d", # Vulgar fraction five eighths
73
+ "\u215e", # Vulgar fraction seven eighths
74
+ "\u215f", # Fraction numerator one
75
+ "\u2215", # Division slash
76
+ "\u2236", # Ratio
77
+ "\u23ae", # Integral extension
78
+ "\u2571", # Box drawings light diagonal upper right to lower left
79
+ "\u29f6", # Solidus with overbar
80
+ "\u29f8", # Big solidus
81
+ "\u2afb", # Triple solidus binary relation
82
+ "\u2afd", # Double solidus operator
83
+ "\u2ff0", # Ideographic description character left to right
84
+ "\u2ff1", # Ideographic description character above to below
85
+ "\u2ff2", # Ideographic description character left to middle and right
86
+ "\u2ff3", # Ideographic description character above to middle and below
87
+ "\u2ff4", # Ideographic description character full surround
88
+ "\u2ff5", # Ideographic description character surround from above
89
+ "\u2ff6", # Ideographic description character surround from below
90
+ "\u2ff7", # Ideographic description character surround from left
91
+ "\u2ff8", # Ideographic description character surround from upper left
92
+ "\u2ff9", # Ideographic description character surround from upper right
93
+ "\u2ffa", # Ideographic description character surround from lower left
94
+ "\u2ffb", # Ideographic description character overlaid
95
+ "\u3000", # Ideographic space
96
+ "\u3002", # Ideographic full stop
97
+ "\u3014", # Left tortoise shell bracket
98
+ "\u3015", # Right tortoise shell bracket
99
+ "\u3033", # Vertical kana repeat mark upper half
100
+ "\u30a0", # Katakana-hiragana double hyphen
101
+ "\u3164", # Hangul filler
102
+ "\u321d", # Parenthesized korean character ojeon
103
+ "\u321e", # Parenthesized korean character o hu
104
+ "\u33ae", # Square rad over s
105
+ "\u33af", # Square rad over s squared
106
+ "\u33c6", # Square c over kg
107
+ "\u33df", # Square a over m
108
+ "\ua789", # Modifier letter colon
109
+ "\ufe14", # Presentation form for vertical semicolon
110
+ "\ufe15", # Presentation form for vertical exclamation mark
111
+ "\ufe3f", # Presentation form for vertical left angle bracket
112
+ "\ufe5d", # Small left tortoise shell bracket
113
+ "\ufe5e", # Small right tortoise shell bracket
114
+ "\ufeff", # Zero-width no-break space
115
+ "\uff0e", # Fullwidth full stop
116
+ "\uff0f", # Fullwidth solidus
117
+ "\uff61", # Halfwidth ideographic full stop
118
+ "\uffa0", # Halfwidth hangul filler
119
+ "\ufff9", # Interlinear annotation anchor
120
+ "\ufffa", # Interlinear annotation separator
121
+ "\ufffb", # Interlinear annotation terminator
122
+ "\ufffc", # Object replacement character
123
+ "\ufffd" # Replacement character
124
+ ]
125
+
126
+ def allowed_chars_set
127
+ @@allowed_chars_set ||= (read_allowed_idn_chars.chars.to_set - MOZZILLA_DISALLOWED_CHARACTERS)
128
+ end
129
+
130
+ private
131
+ # Built with script/development/generate_allowed_idn_characters.rb
132
+ def read_allowed_idn_chars
133
+ File.read("#{__dir__}/data/allowed_idn_characters.txt")
134
+ end
135
+ end
136
+
137
+ def attack_detected?
138
+ !label_set.subset?(self.class.allowed_chars_set)
139
+ end
140
+ end
@@ -0,0 +1,3 @@
1
+ class HomographicSpoofing::Detector::Rule::Idn::Base < HomographicSpoofing::Detector::Rule::Base
2
+ delegate :tld, to: :@context
3
+ end
@@ -0,0 +1,8 @@
1
+ class HomographicSpoofing::Detector::Rule::Idn::Context < HomographicSpoofing::Detector::Rule::Context
2
+ attr_reader :tld
3
+
4
+ def initialize(label:, tld:)
5
+ @tld = tld
6
+ super(label:)
7
+ end
8
+ end
@@ -0,0 +1,73 @@
1
+ # 12. of Google Chrome IDN policy
2
+ class HomographicSpoofing::Detector::Rule::Idn::DangerousPattern < HomographicSpoofing::Detector::Rule::Idn::Base
3
+ DANGEROUS_PATTERNS = Regexp.union(
4
+ /# Disallow the following as they may be mistaken for slashes when
5
+ # they're surrounded by non-Japanese scripts (i.e. has non-Katakana
6
+ # Hiragana or Han scripts on both sides):
7
+ # "ノ" (Katakana no, U+30ce), "ソ" (Katakana so, U+30bd),
8
+ # "ゾ" (Katakana zo, U+30be), "ン" (Katakana n, U+30f3),
9
+ # "丶" (CJK unified ideograph, U+4E36),
10
+ # "乀" (CJK unified ideograph, U+4E40),
11
+ # "乁" (CJK unified ideograph, U+4E41),
12
+ # "丿" (CJK unified ideograph, U+4E3F).
13
+ # If {no, so, zo, n} next to a
14
+ # non-Japanese script on either side is disallowed.
15
+ [^\p{kana}\p{hira}\p{hani}]
16
+ [\u30ce\u30f3\u30bd\u30be\u4e36\u4e40\u4e41\u4e3f]
17
+ [^\p{kana}\p{hira}\p{hani}]/x,
18
+
19
+ /# Disallow three Hiragana letters (U+307[8-A]) or Katakana letters
20
+ # (U+30D[8-A]) that look exactly like each other when they're used
21
+ # in a label otherwise entirely in Katakana or Hiragana.
22
+ ^[\p{kana}]+[\u3078-\u307a][\p{kana}]+\z/x,
23
+ /^[\p{hira}]+[\u30d8-\u30da][\p{hira}]+\z/,
24
+
25
+ /# Disallow U+30FD (Katakana iteration mark) and U+30FE (Katakana
26
+ # voiced iteration mark) unless they're preceded by a Katakana.
27
+ ([^\p{kana}][\u30fd\u30fe]|^[\u30fd\u30fe])/x,
28
+
29
+ /# Disallow U+30FB (Katakana Middle Dot) and U+30FC (Hiragana-
30
+ # Katakana Prolonged Sound) used out-of-context.
31
+ ([^\p{kana}\p{hira}]\u30fc|^\u30fc|[a-z]\u30fb|\u30fb[a-z])/x,
32
+
33
+ /# Disallow these CJK ideographs if they are next to non-CJK
34
+ # characters. These characters can be used to spoof Latin
35
+ # characters or punctuation marks:
36
+ # U+4E00 (一), U+3127 (ㄧ), U+4E28 (丨), U+4E5B (乛), U+4E03 (七),
37
+ # U+4E05 (丅), U+5341 (十), U+3007 (〇), U+3112 (ㄒ), U+311A (ㄚ),
38
+ # U+311F (ㄟ), U+3128 (ㄨ), U+3129 (ㄩ), U+3108 (ㄈ), U+31BA (ㆺ),
39
+ # U+31B3 (ㆳ), U+5DE5 (工), U+31B2 (ㆲ), U+8BA0 (讠), U+4E01 (丁)
40
+ # These characters are already blocked:
41
+ # U+2F00 (⼀) (normalized to U+4E00), U+3192 (㆒), U+2F02 (⼂),
42
+ # U+2F17 (⼗) and U+3038 (〸) (both normalized to U+5341 (十)).
43
+ # Check if there is non-{Hiragana, Katagana, Han, Bopomofo} on the
44
+ # left.
45
+ [^\p{kana}\p{hira}\p{hani}\p{bopo}]
46
+ [\u4e00\u3127\u4e28\u4e5b\u4e03\u4e05\u5341\u3007\u3112\u311a\u311f\u3128\u3129\u3108\u31ba\u31b3\u5dE5\u31b2\u8ba0\u4e01]/x,
47
+ /# Check if there is non-{Hiragana, Katagana, Han, Bopomofo} on the
48
+ # right.
49
+ [\u4e00\u3127\u4e28\u4e5b\u4e03\u4e05\u5341\u3007\u3112\u311a\u311f\u3128\u3129\u3108\u31ba\u31b3\u5de5\u31b2\u8ba0\u4e01]
50
+ [^\p{kana}\p{hira}\p{hani}\p{bopo}]/x,
51
+
52
+ /# Disallow combining diacritical mark (U+0300-U+0339) after a
53
+ # non-LGC character. Other combining diacritical marks are not in
54
+ # the allowed character set.
55
+ [^\p{latn}\p{grek}\p{cyrl}][\u0300-\u0339]/x,
56
+
57
+ /# Disallow dotless i (U+0131) followed by a combining mark.
58
+ \u0131[\u0300-\u0339]/x,
59
+
60
+ /# Disallow combining Kana voiced sound marks.
61
+ (\u3099|\u309a)/x,
62
+
63
+ /# Disallow U+0307 (dot above) after 'i', 'j', 'l' or dotless i
64
+ # (U+0131). Dotless j (U+0237) is not in the allowed set to begin
65
+ # with.
66
+ [ijl]\u0307/x,
67
+ /^\u0237/
68
+ )
69
+
70
+ def attack_detected?
71
+ DANGEROUS_PATTERNS.match?(label)
72
+ end
73
+ end
@@ -0,0 +1,10 @@
1
+ # Four characters handled differently by IDNA 2003 and IDNA 2008. UTS46
2
+ # transitional processing treats them as IDNA 2003 does; maps U+00DF and
3
+ # U+03C2 and drops U+200[CD].
4
+ class HomographicSpoofing::Detector::Rule::Idn::DeviationCharacters < HomographicSpoofing::Detector::Rule::Idn::Base
5
+ DEVIATION_CHARACTERS = %W[ ß ς \u200c \u200d ].to_set
6
+
7
+ def attack_detected?
8
+ (label_set & DEVIATION_CHARACTERS).present?
9
+ end
10
+ end
@@ -0,0 +1,25 @@
1
+ # 11. of Google Chrome IDN policy
2
+ class HomographicSpoofing::Detector::Rule::Idn::Digits < HomographicSpoofing::Detector::Rule::Idn::Base
3
+ def attack_detected?
4
+ contains_digit_lookalike? && contains_only_digits_or_digit_lookalike?
5
+ end
6
+
7
+ private
8
+ def contains_digit_lookalike?
9
+ label.chars.any? { |char| digit_lookalike?(char) }
10
+ end
11
+
12
+ def contains_only_digits_or_digit_lookalike?
13
+ label.chars.all? { |char| digit?(char) || digit_lookalike?(char) }
14
+ end
15
+
16
+ def digit?(char)
17
+ /[0-9]/.match?(char)
18
+ end
19
+
20
+ DIGIT_LOOKALIKES = %w[ θ २ ২ ੨ ੨ ૨ ೩ ೭ շ з ҙ ӡ उ ও ਤ ੩ ૩ ౩ ဒ ვ პ ੜ კ ੫ 丩 ㄐ ճ ৪ ੪ ୫ ૭ ୨ ౨ ].to_set
21
+
22
+ def digit_lookalike?(char)
23
+ DIGIT_LOOKALIKES.include?(char)
24
+ end
25
+ end
@@ -0,0 +1,14 @@
1
+ # 7. of Google Chrome IDN policy
2
+ class HomographicSpoofing::Detector::Rule::Idn::InvisibleCharacters < HomographicSpoofing::Detector::Rule::Idn::Base
3
+ def attack_detected?
4
+ INVISIBLE_CHARACTERS_REGEXP.match?(label)
5
+ end
6
+
7
+ private
8
+ INVISIBLE_CHARACTERS_REGEXP = Regexp.union(
9
+ /\u0e48{2,}/, # Thai tone repeated
10
+ /\u0301{2,}/, # accute accent repeated
11
+ /\u00e1\u0301/, # 'a' with acuted accent + another acute accent
12
+ /^\u0300/, # Combining mark at the beginning
13
+ )
14
+ end
@@ -0,0 +1,59 @@
1
+ # 9. and 10. of Google Chrome IDN policy See http://unicode.org/reports/tr39/#Confusable_Detection
2
+ class HomographicSpoofing::Detector::Rule::Idn::ScriptConfusable < HomographicSpoofing::Detector::Rule::Idn::Base
3
+ def attack_detected?
4
+ SCRIPT_CONFUSABLES.any? do |confusable|
5
+ confusable_chars = label.scan(confusable.script)
6
+ confusable_chars.present? &&
7
+ confusable_chars.all? { confusable.latin_lookalike.match?(_1) } &&
8
+ !is_script_confusable_allowed_for_tld?(confusable)
9
+ end
10
+ end
11
+
12
+ private
13
+ Confusable = Struct.new(:script, :latin_lookalike, :allowed_tlds)
14
+ SCRIPT_CONFUSABLES = [
15
+ # Armenian
16
+ [ /\p{armn}/, /[ագզէլհյոսւօՙ]/, /am/ ],
17
+ # Cyrillic
18
+ [ /\p{cyrl}/, /[аысԁеԍһіюјӏорԗԛѕԝхуъьҽпгѵѡ]/, /bg|by|kz|pyc|ru|su|ua|uz/ ],
19
+ # # Ethiopic (Ge'ez).
20
+ [ /\p{ethi}/, /[ሀሠሰስበነተከዐዕዘጠፐꬅ]/, /er|et/ ],
21
+ # # Georgian
22
+ [ /\p{geor}/, /[იოყძხჽჿ]/, /ge/ ],
23
+ # # Greek
24
+ [ /\p{grek}/, /[αικνρυωηοτ]/, /gr/ ],
25
+ # # Hebrew
26
+ [ /\p{hebr}/, /[דוחיןסװײ׳ﬦ]/, /il/ ],
27
+ # # Bengali
28
+ [ /\p{beng}/, /[০৭]/, nil ],
29
+ # # Devanagari
30
+ [ /\p{Deva}/, /[ऽ०ॱ]/, nil ],
31
+ # # Gujarati
32
+ [ /\p{Gujr}/, /[ડટ૦૧]/, nil ],
33
+ # # Gurmukhi
34
+ [ /\p{Guru}/, /[੦੧]/, nil ],
35
+ # # Kannada
36
+ [ /\p{Knda}/, /[ಽ೦೧]/, nil ],
37
+ # # Malayalam
38
+ [ /\p{Mlym}/, /[ടഠധനറ൦]/, nil ],
39
+ # # Oriya
40
+ [ /\p{Orya}/, /[ଠ୦୮]/, nil ],
41
+ # # Tamil
42
+ [ /\p{Taml}/, /[டப௦]/, nil ],
43
+ # # Telugu
44
+ [ /\p{Telu}/, /[౦౧]/, nil ],
45
+ # # Myanmar
46
+ [ /\p{Mymr}/, /[ခဂငထပဝ၀၂ၔၜ\u1090\u1091\u1095\u1096\u1097]/, /[a-z]+\.mm/ ],
47
+ # # Thai
48
+ [ /\p{Thai}/, /[ทนบพรหเแ๐ดลปฟม]/, /th/ ]
49
+ ].map { Confusable.new(*_1) }
50
+
51
+ def is_script_confusable_allowed_for_tld?(confusable)
52
+ tld_contains_any_letter_from_script?(confusable.script) ||
53
+ confusable.allowed_tlds&.match?(tld)
54
+ end
55
+
56
+ def tld_contains_any_letter_from_script?(script)
57
+ script.match?(tld)
58
+ end
59
+ end
@@ -0,0 +1,31 @@
1
+ class HomographicSpoofing::Detector::Rule::Idn::ScriptSpecific < HomographicSpoofing::Detector::Rule::Idn::Base
2
+ def attack_detected?
3
+ latin_spoof? || icelandic_spoof? || azerbaijan_spoof?
4
+ end
5
+
6
+ private
7
+ LATN = "Latin"
8
+ # Disallow non-ASCII Latin letters to mix with a non-Latin script.
9
+ # Note that the non-ASCII Latin check should not be applied when the entire label is made of Latin.
10
+ def latin_spoof?
11
+ scripts != Set[LATN] && non_ascii_latin_letters.present?
12
+ end
13
+
14
+ def non_ascii_latin_letters
15
+ label.scan(/\p{latin}/).reject { _1 =~ /[a-z0-9]/ }
16
+ end
17
+
18
+ ICELANDIC_CHARACTERS = %w[ þ ð ].to_set
19
+ # Latin small letter thorn ("þ", U+00FE) can be used to spoof both b and p.
20
+ # It's used in modern Icelandic orthography, so allow it for the Icelandic
21
+ # ccTLD (.is) but block in any other TLD. Also block Latin small letter eth
22
+ # ("ð", U+00F0) which can be used to spoof the letter o.
23
+ def icelandic_spoof?
24
+ tld != "is" && (label_set & ICELANDIC_CHARACTERS).any?
25
+ end
26
+
27
+ # ə is only allowed under the .az TLD.
28
+ def azerbaijan_spoof?
29
+ tld != "az" && label_set.include?("ə")
30
+ end
31
+ end
@@ -0,0 +1,12 @@
1
+ # 8. of Google Chrome IDN policy
2
+ #
3
+ # Allow middle dot (U+00B7) only on Catalan domains when between two 'l's, to
4
+ # permit the Catalan character ela geminada to be expressed.
5
+ # See https://tools.ietf.org/html/rfc5892#appendix-A.3 for details.
6
+ class HomographicSpoofing::Detector::Rule::Idn::UnsafeMiddleDot < HomographicSpoofing::Detector::Rule::Idn::Base
7
+ def attack_detected?
8
+ label.scan(/l?·l?/).find do |match|
9
+ tld != "cat" || match != "l·l"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,49 @@
1
+ # See http://www.unicode.org/reports/tr39/#Email_Security_Profiles dot-atom-text.
2
+ class HomographicSpoofing::Detector::Rule::Local::DotAtomText < HomographicSpoofing::Detector::Rule::Base
3
+ def attack_detected?
4
+ if invalid_dot_sequence?
5
+ true
6
+ elsif label_no_dots.present?
7
+ !valid_start_sequence? || contains_invalid_char?
8
+ end
9
+ end
10
+
11
+ private
12
+ def invalid_dot_sequence?
13
+ label.starts_with?(".") || label.ends_with?(".") || multiple_dots?
14
+ end
15
+
16
+ def multiple_dots?
17
+ /\.{2,}/.match?(label)
18
+ end
19
+
20
+ def label_no_dots
21
+ @label_no_dots ||= label.tr(".", "")
22
+ end
23
+
24
+ XID_Start_REGEXP = /\p{XIDS}/
25
+
26
+ def valid_start_sequence?
27
+ start = label.first
28
+ simple_char?(start) || XID_Start_REGEXP.match?(start)
29
+ end
30
+
31
+ def contains_invalid_char?
32
+ label_no_dots.chars.any? { invalid_char?(_1) }
33
+ end
34
+
35
+ # https://tools.ietf.org/html/rfc5322#section-3.2.3 atext
36
+ ATEXT_REGEXP = %r{[!#-'*+\-/-9=?A-Z\^-~]}
37
+
38
+ def invalid_char?(c)
39
+ if simple_char?(c)
40
+ !ATEXT_REGEXP.match(c)
41
+ else
42
+ HomographicSpoofing::Detector::Rule::DisallowedCharacters.allowed_chars_set.exclude?(c)
43
+ end
44
+ end
45
+
46
+ def simple_char?(c)
47
+ c < "\u007f"
48
+ end
49
+ end
@@ -0,0 +1,6 @@
1
+ # See http://www.unicode.org/reports/tr39/#Email_Security_Profiles nkfc.
2
+ class HomographicSpoofing::Detector::Rule::Local::Nfkc < HomographicSpoofing::Detector::Rule::Base
3
+ def attack_detected?
4
+ !label.unicode_normalized?(:nfkc)
5
+ end
6
+ end
@@ -0,0 +1,30 @@
1
+ # 6. of Google Chrome IDN policy
2
+ class HomographicSpoofing::Detector::Rule::MixedDigits < HomographicSpoofing::Detector::Rule::Base
3
+ def attack_detected?
4
+ digits_scripts.many?
5
+ end
6
+
7
+ private
8
+ def digits_scripts
9
+ digits.map { digits_map[_1] }.uniq
10
+ end
11
+
12
+ def digits
13
+ label.scan(/[[:digit:]]/)
14
+ end
15
+
16
+ def digits_map
17
+ @@digits_map ||= build_digits_map
18
+ end
19
+
20
+ def build_digits_map
21
+ CSV.parse(read_digits).each_with_object({}) do |(char, script), map|
22
+ map[char] = script
23
+ end
24
+ end
25
+
26
+ # Built with script/development/generate_digits_characters.rb
27
+ def read_digits
28
+ File.read("#{__dir__}/data/digits.csv")
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # 5. of Google Chrome IDN policy See http://www.unicode.org/reports/tr39/#highly_restrictive
2
+ class HomographicSpoofing::Detector::Rule::MixedScripts < HomographicSpoofing::Detector::Rule::Base
3
+ def attack_detected?
4
+ !highly_restrictive_scripts_combination?
5
+ end
6
+
7
+ private
8
+ BOPO = "Bopomofo"
9
+ HANG = "Hangul"
10
+ HANI = "Han"
11
+ HIRA = "Hiragana"
12
+ KANA = "Katakana"
13
+ LATN = "Latin"
14
+
15
+ JAPANESE = Set[HANI, HIRA, KANA]
16
+ CHINESE = Set[BOPO, HANI]
17
+ KOREAN = Set[HANI, HANG]
18
+
19
+ HIGHLY_RESTRICTIVE_SCRIPT_COMBINATIONS = [
20
+ Set[*JAPANESE, LATN],
21
+ Set[*CHINESE, LATN],
22
+ Set[*KOREAN, LATN]
23
+ ]
24
+
25
+ def highly_restrictive_scripts_combination?
26
+ scripts.length == 1 || HIGHLY_RESTRICTIVE_SCRIPT_COMBINATIONS.any? do |highly_restrictive_script_combination|
27
+ scripts.subset?(highly_restrictive_script_combination)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,10 @@
1
+ # See http://www.unicode.org/reports/tr39/#Email_Security_Profiles bidicontrol.
2
+ class HomographicSpoofing::Detector::Rule::QuotedString::BidiControl < HomographicSpoofing::Detector::Rule::Base
3
+ # See https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%3Abidicontrol%3A%5D&c=on&g=&i=
4
+ # for the full list of bidirectional format characters.
5
+ DISALLOWED_REGEXP = /[\u202a\u202b\u202c\u202d\u202e\u2066\u2067\u2068\u2069]/
6
+
7
+ def attack_detected?
8
+ DISALLOWED_REGEXP.match?(label)
9
+ end
10
+ end
@@ -0,0 +1 @@
1
+ ̴̵̶̷̸̡̢̧̨̛̖̗̘̙̜̝̞̟̠̣̤̥̦̩̪̫̬̭̮̯̰̱̲̳̹̺̻̼͙͚͇͈͉͍͎̀́̂̃̄̅̆̇̈̉̊̋̌̍̎̏̐̑̒̓̔̽̾̿͛̀́͂̓̈́͆͊͋͌̕̚͘ͅ͏͓͔͕͖͐͑͒͗ͣͤͥͦͧͨͩͪͫͬͭͮͯ҃҄҅҆҇͜͟͢͝͞͠͡҈҉ְֱֲֳִֵֶַָׇֹֺֻּֽֿׁׂًٌٍؘَؙُؚِّْٰܑ֑֖֛֢֣֤֥֦֧֪ׅۣ۪ۭٕٖٜٟܱܴܷܸܹܻܼܾ݂݄݆݈֚֭֮֒֓֔֕֗֘֙֜֝֞֟֠֡֨֩֫֬֯ׄؐؑؒؓؔؕؗۖۗۘۙۚۛۜ۟۠ۡۢۤۧۨ۫۬ؖٓٔٗ٘ٙٚٛٝٞܰܲܳܵܶܺܽܿ݀݁݃݅݇݉݊ަާިީުޫެޭޮޯްࣰࣱࣲ߲߽࡙࡚࡛࢙࢚࢛࣏࣐࣑࣒࣓ࣣࣦࣩ࣭࣮࣯ࣶࣹࣺ߫߬߭߮߯߰߱߳ࠖࠗ࠘࠙ࠛࠜࠝࠞࠟࠠࠡࠢࠣࠥࠦࠧࠩࠪࠫࠬ࠭࢘࢜࢝࢞࢟࣊࣋࣌࣍࣎ࣔࣕࣖࣗࣘࣙࣚࣛࣜࣝࣞࣟ࣠࣡ࣳࣤࣥࣧࣨ࣪࣫࣬ࣴࣵࣷࣸࣻࣼࣽࣾࣿऀँं़ऺुूृॄॅॆेैॕ्॒॑॓॔ॖॗॢॣঁ়ুূৃৄ্ৢৣ৾ਁਂ਼ੁੂੇੈੋੌ੍ੑੰੱੵઁં઼ુૂૃૄૅેૈ્ૢૣૺૻૼ૽૾૿ଁ଼୕ୖିୁୂୃୄ୍ୢୣஂீ்ఀఄ఼ౕౖాిీెేైొోౌ్ౢౣಁ಼ೌ್ೢೣഀഁ഻഼ുൂൃൄ്ൢൣඁ්ිීුූัิีึืฺุู็่้๊๋์ํ๎ັິີຶືຸູົ຺ຼ໌ໍ໎྄່້໊໋ཱཱཱིིུུ༹༘༙༵༷࿆ྂྃ྆྇ྲྀཷླྀཹཱེཻོཽྀྀཾྍྎྏྐྑྒྒྷྔྕྖྗྙྚྛྜྜྷྞྟྠྡྡྷྣྤྥྦྦྷྨྩྪྫྫྷྭྮྯྰྱྲླྴྵྶྷྸྐྵྺྻྼိီုူဲဳဴဵံ့္်ွှၘၙၞၟၠၱၲၳၴႂႅႆႍႝ፝፞፟ᜒᜓ᜔ᜲᜳᝒᝓᝲᝳ឴឵ិីឹឺុូួំ់៌៍៎៏័៑្៝៉៊៓᠋᠌᠍᠏ᢅᢆᢩᤠᤡᤢᤧᤨᤲ᤻ᨘ᤹᤺ᨗᨛᩖᩘᩙᩚᩛᩜᩝᩞ᩠᩺᩻᩼ᩢᩥᩦᩧᩨᩩᩪᩫᩬᩳᩴ᩿᪵᪶᪷᪸᪹᪺᪽᩵᩶᩷᩸᩹᪰᪱᪲᪳᪴᪻᪼᪾ᪿᫀ᫃᫄᫊᫁᫂᫅᫆᫇᫈᫉᫋ᫌᫍᫎᬀᬁᬂᬃ᬴ᬶᬷᬸᬹᬺᬼᭂ᭬᭫᭭᭮᭯᭰᭱᭲᭳ᮀᮁᮢᮣᮬᮭᮤᮥᮨᮩ᯦᮫ᯨᯩᯭᯯᯰᯱᰬᰭᰮᰯᰰᰱᰲᰳᰶ᳔᳢᳣᳤᳥᳦᳧᳨⃒⃓⃘⃙⃚᰷᷐᷎᷺᳕᳖᳗᳘᳙᳜᳝᳞᳟᳭᷂᷊᷹᷽᷏᷿᷸᷷᳐᳑᳒᳚᳛᳴᳠᳸᳹᷀᷁᷃᷻᷄᷅᷆᷇᷈᷉᷋᷌᷑᷒ᷓᷔᷕᷖᷗᷘᷙᷚᷛᷜᷝᷞᷟᷠᷡᷢᷣᷤᷥᷦᷧᷨᷩᷪᷫᷬᷭᷮᷯᷰᷱᷲᷳᷴ᷵᷾⃐⃑⃔⃕⃖⃗⃛⃜᷶᷼᷍⃝⃞⃟⃠⃢⃣⃤⃥⃦⃪⃫゙゚⵿〪⃨⃬⃭⃮⃯〭〫⃡⃧⃩⃰⳯⳰⳱ⷠⷡⷢⷣⷤⷥⷦⷧⷨⷩⷪⷫⷬⷭⷮⷯⷰⷱⷲⷳⷴⷵⷶⷷⷸⷹⷺⷻⷼⷽⷾⷿ꙯〬꙰꙱꙲ꙴꙵꙶꙷꙸꙹꙺꙻ꙼꙽ꚞꚟ꛰꛱ꠂ꠆꠬ꠋꠥꠦ꣄ꣅ꣠꣡꣢꣣꣤꣥꣦꣧꣨꣩꣪꣫꣬꣭꣮꣯꣰꣱ꣿꤦꤧꤨꤩꤪ꤫꤬꤭ꥇꥈꥉꥊꥋꥌꥍꥎꥏꥐꥑꦀꦁꦂ꦳ꦶꦷꦸꦹꦼꦽꧥꨩꨪꨫꨬꨭꨮꨱꨲꨵꨶꩃꩌꩼꪴꪰꪲꪳꪷꪸꪾ꪿꫁ꫬꫭ꫶ꯥꯨ꯭ﬞ︀︁︂︃︄︅︆︇︈︉︊︋︌︍︎️︧︨︩︪︫︬︭𐇽𐋠︠︡︢︣︮︯︤︥︦𐍶𐍷𐍸𐍹𐍺𐨁𐨂𐨃𐨅𐨆𐨌𐨍𐨎𐨹𐨿𐨺𐫦𐻽𐻾𐻿𐽆𐽇𐽋𐽍𐽎𐽏𐽐𐾃𐾅𐨏𐨸𐫥𐴤𐴥𐴦𐴧𐺫𐺬𐽈𐽉𐽊𐽌𐾂𐾄𑀁𑀸𑀹𑀺𑀻𑀼𑀽𑀾𑀿𑁀𑁁𑁂𑁃𑁄𑁅𑁳𑁴𑁆𑁰𑁿𑂀𑂁𑂺𑂳𑂴𑂵𑂶𑂹𑃂𑄳𑄴𑄀𑄁𑄂𑄧𑄨𑄩𑄪𑄫𑄭𑄮𑄯𑄰𑄱𑄲𑅳𑆀𑆁𑆶𑆷𑆸𑆹𑆺𑆻𑆼𑆽𑆾𑇉𑇏𑇊𑇋𑇌𑈯𑈰𑈱𑉁𑈴𑈶𑈷𑈾𑋟𑋣𑋤𑋥𑋦𑋧𑋨𑋩𑋪𑌀𑌁𑌻𑌼𑍀𑍦𑍧𑍨𑍩𑍪𑍫𑍬𑍰𑍱𑍲𑍳𑍴𑐸𑐹𑐺𑐻𑐼𑐽𑐾𑐿𑑂𑑃𑑄𑑆𑑞𑒳𑒴𑒵𑒶𑒷𑒸𑒺𑒿𑓀𑓃𑓂𑖲𑖳𑖴𑖵𑖼𑖽𑗀𑖿𑗜𑗝𑘳𑘴𑘵𑘶𑘷𑘸𑘹𑘺𑘽𑘿𑙀𑚫𑚭𑚰𑚱𑚲𑚳𑚴𑚵𑚷𑜝𑜞𑜟𑜢𑜣𑜤𑜥𑜧𑜨𑜩𑜪𑜫𑠯𑠰𑠱𑠲𑠳𑠴𑠵𑠶𑠷𑠺𑠹𑤻𑤼𑥃𑤾𑧔𑧕𑧖𑧗𑧚𑧛𑧠𑨁𑨂𑨃𑨄𑨅𑨆𑨉𑨊𑨳𑨴𑨵𑨶𑨷𑨸𑨻𑨼𑨽𑨾𑩇𑩑𑩒𑩓𑩔𑩕𑩖𑩙𑩚𑩛𑪊𑪋𑪌𑪍𑪎𑪏𑪐𑪑𑪒𑪓𑪔𑪕𑪖𑪘𑪙𑰰𑰱𑰲𑰳𑰴𑰵𑰶𑰸𑰹𑰺𑰻𑰼𑰽𑲒𑲓𑲔𑲕𑲖𑲗𑲘𑲙𑲚𑲛𑲜𑲝𑲞𑲟𑲠𑲡𑲢𑲣𑲤𑲥𑲦𑲧𑲪𑲫𑲬𑲭𑲮𑲯𑲰𑲲𑲳𑲵𑲶𑴱𑴲𑴳𑴴𑴵𑴶𑴺𑴼𑴽𑴿𑵀𑵁𑵂𑵃𑵄𑵅𑵇𑶐𑶑𑶕𑶗𑻳𑻴𑼀𑼁𑼶𑼷𑼸𑼹𑼺𑽀𑽂𓑀𓑇𓑈𓑉𓑊𓑋𓑌𓑍𓑎𓑏𓑐𓑑𓑒𓑓𓑔𓑕𖫰𖫱𖫲𖫳𖫴𖬰𖬱𖬲𖬳𖬴𖬵𖬶𖽏𖾏𖾐𖾑𖾒𖿤𛲝𛲞𜼀𜼁𜼂𜼃𜼄𜼅𜼆𜼇𜼈𜼉𜼊𜼋𜼌𜼍𜼎𜼏𜼐𜼑𜼒𜼓𜼔𜼕𜼖𜼗𜼘𜼙𜼚𜼛𜼜𜼝𜼞𜼟𜼠𜼡𜼢𜼣𜼤𜼥𜼦𜼧𜼨𜼩𜼪𜼫𜼬𜼭𜼰𜼱𜼲𜼳𜼴𜼵𜼶𜼷𜼸𜼹𜼺𜼻𜼼𜼽𜼾𜼿𜽀𜽁𜽂𜽃𜽄𜽅𜽆𝅧𝅨𝅩𝅻𝅼𝅽𝅾𝅿𝆀𝆁𝆂𝆊𝆋𝆅𝆆𝆇𝆈𝆉𝆪𝆫𝆬𝆭𝉂𝉃𝉄𝨀𝨁𝨂𝨃𝨄𝨅𝨆𝨇𝨈𝨉𝨊𝨋𝨌𝨍𝨎𝨏𝨐𝨑𝨒𝨓𝨔𝨕𝨖𝨗𝨘𝨙𝨚𝨛𝨜𝨝𝨞𝨟𝨠𝨡𝨢𝨣𝨤𝨥𝨦𝨧𝨨𝨩𝨪𝨫𝨬𝨭𝨮𝨯𝨰𝨱𝨲𝨳𝨴𝨵𝨶𝨻𝨼𝨽𝨾𝨿𝩀𝩁𝩂𝩃𝩄𝩅𝩆𝩇𝩈𝩉𝩊𝩋𝩌𝩍𝩎𝩏𝩐𝩑𝩒𝩓𝩔𝩕𝩖𝩗𝩘𝩙𝩚𝩛𝩜𝩝𝩞𝩟𝩠𝩡𝩢𝩣𝩤𝩥𝩦𝩧𝩨𝩩𝩪𝩫𝩬𝩵𝪄𝪛𝪜𝪝𝪞𝪟𝪡𝪢𝪣𝪤𝪥𝪦𝪧𝪨𝪩𝪪𝪫𝪬𝪭𝪮𝪯𞥊𞓮𞣐𞣑𞣒𞣓𞣔𞣕𞣖𞀀𞀁𞀂𞀃𞀄𞀅𞀆𞀈𞀉𞀊𞀋𞀌𞀍𞀎𞀏𞀐𞀑𞀒𞀓𞀔𞀕𞀖𞀗𞀘𞀛𞀜𞀝𞀞𞀟𞀠𞀡𞀣𞀤𞀦𞀧𞀨𞀩𞀪𞂏𞄰𞄱𞄲𞄳𞄴𞄵𞄶𞊮𞋬𞋭𞋮𞋯𞓯𞥄𞥅𞥆𞥇𞥈𞥉𞓬𞓭󠄀󠄁󠄂󠄃󠄄󠄅󠄆󠄇󠄈󠄉󠄊󠄋󠄌󠄍󠄎󠄏󠄐󠄑󠄒󠄓󠄔󠄕󠄖󠄗󠄘󠄙󠄚󠄛󠄜󠄝󠄞󠄟󠄠󠄡󠄢󠄣󠄤󠄥󠄦󠄧󠄨󠄩󠄪󠄫󠄬󠄭󠄮󠄯󠄰󠄱󠄲󠄳󠄴󠄵󠄶󠄷󠄸󠄹󠄺󠄻󠄼󠄽󠄾󠄿󠅀󠅁󠅂󠅃󠅄󠅅󠅆󠅇󠅈󠅉󠅊󠅋󠅌󠅍󠅎󠅏󠅐󠅑󠅒󠅓󠅔󠅕󠅖󠅗󠅘󠅙󠅚󠅛󠅜󠅝󠅞󠅟󠅠󠅡󠅢󠅣󠅤󠅥󠅦󠅧󠅨󠅩󠅪󠅫󠅬󠅭󠅮󠅯󠅰󠅱󠅲󠅳󠅴󠅵󠅶󠅷󠅸󠅹󠅺󠅻󠅼󠅽󠅾󠅿󠆀󠆁󠆂󠆃󠆄󠆅󠆆󠆇󠆈󠆉󠆊󠆋󠆌󠆍󠆎󠆏󠆐󠆑󠆒󠆓󠆔󠆕󠆖󠆗󠆘󠆙󠆚󠆛󠆜󠆝󠆞󠆟󠆠󠆡󠆢󠆣󠆤󠆥󠆦󠆧󠆨󠆩󠆪󠆫󠆬󠆭󠆮󠆯󠆰󠆱󠆲󠆳󠆴󠆵󠆶󠆷󠆸󠆹󠆺󠆻󠆼󠆽󠆾󠆿󠇀󠇁󠇂󠇃󠇄󠇅󠇆󠇇󠇈󠇉󠇊󠇋󠇌󠇍󠇎󠇏󠇐󠇑󠇒󠇓󠇔󠇕󠇖󠇗󠇘󠇙󠇚󠇛󠇜󠇝󠇞󠇟󠇠󠇡󠇢󠇣󠇤󠇥󠇦󠇧󠇨󠇩󠇪󠇫󠇬󠇭󠇮󠇯
@@ -0,0 +1,6 @@
1
+ # See http://www.unicode.org/reports/tr39/#Email_Security_Profiles nfc.
2
+ class HomographicSpoofing::Detector::Rule::QuotedString::Nfc < HomographicSpoofing::Detector::Rule::Base
3
+ def attack_detected?
4
+ !label.unicode_normalized?(:nfc)
5
+ end
6
+ end
@@ -0,0 +1,21 @@
1
+ # See http://www.unicode.org/reports/tr39/#Email_Security_Profiles nonspacing marks.
2
+ class HomographicSpoofing::Detector::Rule::QuotedString::NonspacingMarks < HomographicSpoofing::Detector::Rule::Base
3
+ def attack_detected?
4
+ nonspacing_marks_regexp.match?(label)
5
+ end
6
+
7
+ private
8
+ def nonspacing_marks_regexp
9
+ # 5 or more nonspacing marks in a row or 2 or more repetitions of the same nonspacing mark.
10
+ @@nonspacing_marks_regexp ||= /[#{nonspacing_marks}]{5,}|([#{nonspacing_marks}])\1/
11
+ end
12
+
13
+ def nonspacing_marks
14
+ @nonspacing_marks ||= read_nonspacing_marks
15
+ end
16
+
17
+ # Built with script/development/generate_nonspacing_marks.rb
18
+ def read_nonspacing_marks
19
+ File.read("#{__dir__}/data/nonspacing_marks.txt")
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ class HomographicSpoofing::Railtie < ::Rails::Railtie
2
+ initializer "homographic_spoofing.logger" do
3
+ HomographicSpoofing.logger ||= Rails.logger
4
+ end
5
+ end
@@ -0,0 +1,39 @@
1
+ class HomographicSpoofing::Sanitizer::Base
2
+ class_attribute :logger, default: HomographicSpoofing.logger
3
+
4
+ def self.sanitize(field)
5
+ new(field).sanitize
6
+ end
7
+
8
+ def initialize(field)
9
+ @field = field
10
+ end
11
+
12
+ def sanitize
13
+ result = field.dup
14
+ detector_class.new(field).detections.each do |detection|
15
+ log(detection.reason, detection.label)
16
+ result = punycode(result, detection.label)
17
+ end
18
+ result
19
+ end
20
+
21
+ private
22
+ attr_reader :field
23
+
24
+ def punycode(source, label)
25
+ source.gsub(label, Dnsruby::Name.punycode(label))
26
+ end
27
+
28
+ def detector_class
29
+ raise NotImplementedError, "subclasses must override this"
30
+ end
31
+
32
+ def log(reason, label)
33
+ self.class.logger.info("#{spoofing_type} Spoofing detected for: \"#{reason}\" on: \"#{label}\".") if self.class.logger
34
+ end
35
+
36
+ def spoofing_type
37
+ raise NotImplementedError, "subclasses must override this"
38
+ end
39
+ end
@@ -0,0 +1,10 @@
1
+ class HomographicSpoofing::Sanitizer::EmailAddress < HomographicSpoofing::Sanitizer::Base
2
+ private
3
+ def detector_class
4
+ HomographicSpoofing::Detector::EmailAddress
5
+ end
6
+
7
+ def spoofing_type
8
+ "EmailAddress"
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ class HomographicSpoofing::Sanitizer::Idn < HomographicSpoofing::Sanitizer::Base
2
+ private
3
+ def detector_class
4
+ HomographicSpoofing::Detector::Idn
5
+ end
6
+
7
+ def spoofing_type
8
+ "EmailIDN"
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ class HomographicSpoofing::Sanitizer::QuotedString < HomographicSpoofing::Sanitizer::Base
2
+ private
3
+ def detector_class
4
+ HomographicSpoofing::Detector::QuotedString
5
+ end
6
+
7
+ def spoofing_type
8
+ "EmailQuotedString"
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ module HomographicSpoofing
2
+ VERSION = "0.1.0"
3
+ end