phisher_phinder 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.env.example +3 -0
  3. data/.gitignore +18 -0
  4. data/.rspec +3 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +6 -0
  8. data/CHANGELOG.md +1 -0
  9. data/Gemfile +14 -0
  10. data/Gemfile.lock +93 -0
  11. data/LICENSE +21 -0
  12. data/LICENSE.txt +21 -0
  13. data/README.md +38 -0
  14. data/Rakefile +38 -0
  15. data/bin/console +20 -0
  16. data/bin/setup +8 -0
  17. data/db/migrations/0001_create_geo_ip_cache.rb +35 -0
  18. data/lib/phisher_phinder.rb +28 -0
  19. data/lib/phisher_phinder/body_hyperlink.rb +47 -0
  20. data/lib/phisher_phinder/cached_geoip_client.rb +95 -0
  21. data/lib/phisher_phinder/expanded_data_processor.rb +61 -0
  22. data/lib/phisher_phinder/extended_ip.rb +16 -0
  23. data/lib/phisher_phinder/extended_ip_factory.rb +51 -0
  24. data/lib/phisher_phinder/geoip_ip_data.rb +6 -0
  25. data/lib/phisher_phinder/mail.rb +50 -0
  26. data/lib/phisher_phinder/mail_parser.rb +111 -0
  27. data/lib/phisher_phinder/mail_parser/body_parser.rb +94 -0
  28. data/lib/phisher_phinder/mail_parser/header_value_parser.rb +24 -0
  29. data/lib/phisher_phinder/mail_parser/received_headers/by_parser.rb +45 -0
  30. data/lib/phisher_phinder/mail_parser/received_headers/classifier.rb +27 -0
  31. data/lib/phisher_phinder/mail_parser/received_headers/for_parser.rb +23 -0
  32. data/lib/phisher_phinder/mail_parser/received_headers/from_parser.rb +40 -0
  33. data/lib/phisher_phinder/mail_parser/received_headers/parser.rb +74 -0
  34. data/lib/phisher_phinder/mail_parser/received_headers/starttls_parser.rb +24 -0
  35. data/lib/phisher_phinder/mail_parser/received_headers/timestamp_parser.rb +32 -0
  36. data/lib/phisher_phinder/simple_ip.rb +15 -0
  37. data/lib/phisher_phinder/version.rb +3 -0
  38. data/phisher_phinder.gemspec +32 -0
  39. metadata +112 -0
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PhisherPhinder
4
+ module MailParser
5
+ module ReceivedHeaders
6
+ class Classifier
7
+ def classify(header_data)
8
+ {partial: !complete?(header_data)}
9
+ end
10
+
11
+ private
12
+
13
+ def complete?(header_data)
14
+ (
15
+ header_data[:advertised_sender] &&
16
+ header_data[:recipient] &&
17
+ header_data[:recipient_mailbox] &&
18
+ (
19
+ (header_data[:protocol] == 'ESMTPS' && header_data[:starttls]) ||
20
+ (header_data[:protocol] != 'ESMTPS' && !header_data[:starttls])
21
+ )
22
+ )
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PhisherPhinder
4
+ module MailParser
5
+ module ReceivedHeaders
6
+ class ForParser
7
+ def parse(component)
8
+ component =~ /\Afor\s(\S+)\z/
9
+
10
+ {
11
+ recipient_mailbox: strip_angle_brackets($1)
12
+ }
13
+ end
14
+
15
+ private
16
+
17
+ def strip_angle_brackets(email_address_string)
18
+ email_address_string =~ /\<([^>]+)\>/ ? $1 : email_address_string
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PhisherPhinder
4
+ module MailParser
5
+ module ReceivedHeaders
6
+ class FromParser
7
+ def initialize(extended_ip_factory)
8
+ @extended_ip_factory = extended_ip_factory
9
+ end
10
+
11
+ def parse(component)
12
+ return {advertised_sender: nil, helo: nil, sender: nil} unless component
13
+
14
+ patterns = [
15
+ /from\s(?<advertised_sender>[\S]+)\s\(HELO\s(?<helo>[^)]+)\)\s\(\[(?<sender_ip>[^\]]+)\]\)/,
16
+ /from\s(?<advertised_sender>[\S]+)\s\((?<sender_host>\S+?)\.?\s\[(?<sender_ip>[^\]]+)\]\)/,
17
+ /from\s(?<advertised_sender>\S+)\s\((?<sender_host>\S+?)\.?\s(?<sender_ip>\S+?)\)/,
18
+ /from\s(?<advertised_sender>\S+)\s\(\[(?<sender_ip>[^\]]+)\]\)/,
19
+ /from\s(?<advertised_sender>\S+)\s\((?<sender_ip>[^)]+)\)/,
20
+ /\(from\s(?<advertised_sender>[^)]+)\)/,
21
+ /from\s(?<advertised_sender>\S+)/,
22
+ ]
23
+
24
+ matches = patterns.inject(nil) do |memo, pattern|
25
+ memo || component.match(pattern)
26
+ end
27
+
28
+ {
29
+ advertised_sender: matches[:advertised_sender],
30
+ helo: matches.names.include?('helo') ? matches[:helo] : nil,
31
+ sender: {
32
+ host: matches.names.include?('sender_host') ? matches[:sender_host] : nil,
33
+ ip: matches.names.include?('sender_ip') ? @extended_ip_factory.build(matches[:sender_ip]) : nil
34
+ }
35
+ }
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PhisherPhinder
4
+ module MailParser
5
+ module ReceivedHeaders
6
+ class Parser
7
+ def initialize(by_parser:, for_parser:, from_parser:, starttls_parser:, timestamp_parser:, classifier:)
8
+ @parsers = {
9
+ by: by_parser,
10
+ for: for_parser,
11
+ from: from_parser,
12
+ starttls: starttls_parser,
13
+ time: timestamp_parser,
14
+ }
15
+ @classifier = classifier
16
+ end
17
+
18
+ def parse(header)
19
+ require 'strscan'
20
+
21
+ header_value, timestamp = header.split(';')
22
+
23
+ parsers = [@by_parser, @for_parser, @from_parser, @starttls_parser, @timestamp_parser]
24
+ components = extract_value_components(header_value).merge(time: timestamp)
25
+
26
+ output = @parsers.inject({}) do |memo, (component_name, parser)|
27
+ memo.merge(parser.parse(components[component_name]))
28
+ end
29
+
30
+ output.merge(@classifier.classify(output))
31
+ end
32
+
33
+ private
34
+
35
+ def extract_value_components(header_value)
36
+ require 'strscan'
37
+
38
+ scanner = StringScanner.new(header_value)
39
+
40
+ output = {}
41
+
42
+ if scanner.check(/\(from\s+[^)]+\)/)
43
+ from_part = scanner.scan(/\(from\s+[^)]+\)/)
44
+ elsif scanner.check(/from\s.+?\(HELO\s[^)]+\)\s\(\[[^\]]+\]\)/)
45
+ from_part = scanner.scan(/from.+?(?=by)/)
46
+ elsif scanner.check(/from\s[^)(]+\sby/)
47
+ from_part = scanner.scan(/from.+?(?=by)/)
48
+ else
49
+ from_part = scanner.scan(/from\s.+?\([^)]+?\)/)
50
+ end
51
+
52
+ if scanner.check(/.+\S+\s+by/)
53
+ starttls_part = scanner.scan(/.+\s(?=by)/)
54
+ by_part = scanner.scan(/\s?by.+?\sid\s[\S]+\s?/)
55
+ for_part = scanner.scan(/for\s+\S+/)
56
+ elsif scanner.check(/\s?by.+?\s(id|ID)\s[\S]+\s?/)
57
+ by_part = scanner.scan(/\s?by.+?\s(id|ID)\s[\S]+\s?/) unless scanner.eos?
58
+ for_part = scanner.scan(/for\s+\S+/) unless scanner.eos?
59
+ starttls_part = scanner.rest unless scanner.eos?
60
+ elsif scanner.check(/by.+(?!\sid)/)
61
+ by_part = scanner.scan(/.+/)
62
+ end
63
+
64
+ {
65
+ by: by_part,
66
+ for: for_part,
67
+ from: from_part,
68
+ starttls: starttls_part
69
+ }
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PhisherPhinder
4
+ module MailParser
5
+ module ReceivedHeaders
6
+ class StarttlsParser
7
+ def parse(component)
8
+ return {starttls: nil} unless component
9
+
10
+ patterns = [
11
+ /\(version=(?<version>\S+)\scipher=(?<cipher>\S+)\sbits=(?<bits>\S+)\)/,
12
+ /using\s(?<version>\S+)\swith cipher\s(?<cipher>\S+)\s\((?<bits>.+?) bits\)/
13
+ ]
14
+
15
+ matches = patterns.inject(nil) do |memo, pattern|
16
+ memo || component.match(pattern)
17
+ end
18
+
19
+ {starttls: {version: matches[:version], cipher: matches[:cipher], bits: matches[:bits]}}
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PhisherPhinder
4
+ module MailParser
5
+ module ReceivedHeaders
6
+ class TimestampParser
7
+ def parse(timestamp)
8
+ return {time: nil} unless timestamp
9
+
10
+ require 'time'
11
+
12
+ date_formats = [
13
+ "%a, %d %b %Y %H:%M:%S %z (%Z)",
14
+ "%a, %d %b %Y %H:%M:%S %z",
15
+ "%d %b %Y %H:%M:%S %z"
16
+ ]
17
+
18
+ parsed_time = date_formats.inject(nil) do |parsed_time, pattern|
19
+ begin
20
+ parsed_time || Time.strptime(timestamp.strip, pattern)
21
+ rescue ArgumentError
22
+ end
23
+ end
24
+
25
+ raise "Could not match `#{timestamp}` with the available patterns" unless parsed_time
26
+
27
+ {time: parsed_time}
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal
2
+
3
+ module PhisherPhinder
4
+ class SimpleIp
5
+ attr_reader :ip_address
6
+
7
+ def initialize(ip_address:)
8
+ @ip_address = ip_address
9
+ end
10
+
11
+ def ==(other)
12
+ other.is_a?(SimpleIp) && ip_address == other.ip_address
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ module PhisherPhinder
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,32 @@
1
+ require_relative 'lib/phisher_phinder/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "phisher_phinder"
5
+ spec.version = PhisherPhinder::VERSION
6
+ spec.authors = ["Rory McKinley"]
7
+ spec.email = ["rorymckinley@gmail.com"]
8
+
9
+ spec.summary = %q{A gem for dissecting phishing emails}
10
+ spec.description = %q{A collection of tools to dissect and report on phishing emails}
11
+ spec.homepage = "https://capefox.co"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
14
+
15
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/rorymckinley/phisher_phinder"
19
+ spec.metadata["changelog_uri"] = "https://github.com/rorymckinley/phisher_phinder/blob/master/CHANGELOG.md"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
+ end
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_development_dependency "pry"
31
+ spec.add_development_dependency "rspec", "3.9.0"
32
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: phisher_phinder
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Rory McKinley
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-09-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pry
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 3.9.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 3.9.0
41
+ description: A collection of tools to dissect and report on phishing emails
42
+ email:
43
+ - rorymckinley@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".env.example"
49
+ - ".gitignore"
50
+ - ".rspec"
51
+ - ".ruby-gemset"
52
+ - ".ruby-version"
53
+ - ".travis.yml"
54
+ - CHANGELOG.md
55
+ - Gemfile
56
+ - Gemfile.lock
57
+ - LICENSE
58
+ - LICENSE.txt
59
+ - README.md
60
+ - Rakefile
61
+ - bin/console
62
+ - bin/setup
63
+ - db/migrations/0001_create_geo_ip_cache.rb
64
+ - lib/phisher_phinder.rb
65
+ - lib/phisher_phinder/body_hyperlink.rb
66
+ - lib/phisher_phinder/cached_geoip_client.rb
67
+ - lib/phisher_phinder/expanded_data_processor.rb
68
+ - lib/phisher_phinder/extended_ip.rb
69
+ - lib/phisher_phinder/extended_ip_factory.rb
70
+ - lib/phisher_phinder/geoip_ip_data.rb
71
+ - lib/phisher_phinder/mail.rb
72
+ - lib/phisher_phinder/mail_parser.rb
73
+ - lib/phisher_phinder/mail_parser/body_parser.rb
74
+ - lib/phisher_phinder/mail_parser/header_value_parser.rb
75
+ - lib/phisher_phinder/mail_parser/received_headers/by_parser.rb
76
+ - lib/phisher_phinder/mail_parser/received_headers/classifier.rb
77
+ - lib/phisher_phinder/mail_parser/received_headers/for_parser.rb
78
+ - lib/phisher_phinder/mail_parser/received_headers/from_parser.rb
79
+ - lib/phisher_phinder/mail_parser/received_headers/parser.rb
80
+ - lib/phisher_phinder/mail_parser/received_headers/starttls_parser.rb
81
+ - lib/phisher_phinder/mail_parser/received_headers/timestamp_parser.rb
82
+ - lib/phisher_phinder/simple_ip.rb
83
+ - lib/phisher_phinder/version.rb
84
+ - phisher_phinder.gemspec
85
+ homepage: https://capefox.co
86
+ licenses:
87
+ - MIT
88
+ metadata:
89
+ allowed_push_host: https://rubygems.org
90
+ homepage_uri: https://capefox.co
91
+ source_code_uri: https://github.com/rorymckinley/phisher_phinder
92
+ changelog_uri: https://github.com/rorymckinley/phisher_phinder/blob/master/CHANGELOG.md
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: 2.3.0
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubygems_version: 3.1.2
109
+ signing_key:
110
+ specification_version: 4
111
+ summary: A gem for dissecting phishing emails
112
+ test_files: []