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,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ee817dd4cbc016b47f7710714c4be4c5b07a81baa89b47d46c00ae9112fc04e0
4
+ data.tar.gz: 7fb874d90fbb94f310ccec195ee6901f77c193f92615cdb5d9c8f5764dfaac94
5
+ SHA512:
6
+ metadata.gz: c4d8660b672488b32ce4cb4ba1e3b7652e414b54afff9d2817a8461d570f7db5aac391424287953a4653ef3b00513dfcffa6666dc740aea823231bc4d6b5f685
7
+ data.tar.gz: 9e35efd983eb5961fac93c05036bf09e59919ecd1ce6ecd1c338d026e5c3489276f69edd135d2d81e47870daf3422df5ef07f8de570c3d5dfa0f14111c6cdce6
@@ -0,0 +1,3 @@
1
+ DATABASE_URL=sqlite3://example.sqlite3
2
+ MAXMIND_USER_ID=12345
3
+ MAXMIND_LICENCE_KEY=abcdef
@@ -0,0 +1,18 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ *.sqlite3
14
+
15
+ *.env
16
+ *.env.test
17
+
18
+ test_files
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1 @@
1
+ overphishing
@@ -0,0 +1 @@
1
+ 2.7.1
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.7.1
6
+ before_install: gem install bundler -v 2.1.4
@@ -0,0 +1 @@
1
+ * CHANGELOG
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem 'dotenv', '~> 2.7.5'
6
+ gem 'maxmind-geoip2', '~> 0.4.0'
7
+ gem 'nokogiri', '~> 1.10.10'
8
+ gem 'sequel', '~> 5.33'
9
+ gem 'sqlite3', '~> 1.4.2'
10
+
11
+ gem "rake", "~> 12.0"
12
+ gem "rspec", "~> 3.0"
13
+ gem 'database_cleaner-sequel', '1.8.0'
14
+ gem 'webmock', '~> 3.8.3'
@@ -0,0 +1,93 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ phisher_phinder (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ addressable (2.7.0)
10
+ public_suffix (>= 2.0.2, < 5.0)
11
+ coderay (1.1.2)
12
+ connection_pool (2.2.3)
13
+ crack (0.4.3)
14
+ safe_yaml (~> 1.0.0)
15
+ database_cleaner (1.8.5)
16
+ database_cleaner-sequel (1.8.0)
17
+ database_cleaner (~> 1.8.0)
18
+ sequel
19
+ diff-lcs (1.3)
20
+ domain_name (0.5.20190701)
21
+ unf (>= 0.0.5, < 1.0.0)
22
+ dotenv (2.7.5)
23
+ ffi (1.13.1)
24
+ ffi-compiler (1.0.1)
25
+ ffi (>= 1.0.0)
26
+ rake
27
+ hashdiff (1.0.1)
28
+ http (4.4.1)
29
+ addressable (~> 2.3)
30
+ http-cookie (~> 1.0)
31
+ http-form_data (~> 2.2)
32
+ http-parser (~> 1.2.0)
33
+ http-cookie (1.0.3)
34
+ domain_name (~> 0.5)
35
+ http-form_data (2.3.0)
36
+ http-parser (1.2.1)
37
+ ffi-compiler (>= 1.0, < 2.0)
38
+ maxmind-db (1.1.1)
39
+ maxmind-geoip2 (0.4.0)
40
+ connection_pool (~> 2.2)
41
+ http (~> 4.3)
42
+ maxmind-db (~> 1.1)
43
+ method_source (1.0.0)
44
+ mini_portile2 (2.4.0)
45
+ nokogiri (1.10.10)
46
+ mini_portile2 (~> 2.4.0)
47
+ pry (0.13.1)
48
+ coderay (~> 1.1)
49
+ method_source (~> 1.0)
50
+ public_suffix (4.0.5)
51
+ rake (12.3.3)
52
+ rspec (3.9.0)
53
+ rspec-core (~> 3.9.0)
54
+ rspec-expectations (~> 3.9.0)
55
+ rspec-mocks (~> 3.9.0)
56
+ rspec-core (3.9.2)
57
+ rspec-support (~> 3.9.3)
58
+ rspec-expectations (3.9.2)
59
+ diff-lcs (>= 1.2.0, < 2.0)
60
+ rspec-support (~> 3.9.0)
61
+ rspec-mocks (3.9.1)
62
+ diff-lcs (>= 1.2.0, < 2.0)
63
+ rspec-support (~> 3.9.0)
64
+ rspec-support (3.9.3)
65
+ safe_yaml (1.0.5)
66
+ sequel (5.33.0)
67
+ sqlite3 (1.4.2)
68
+ unf (0.1.4)
69
+ unf_ext
70
+ unf_ext (0.0.7.7)
71
+ webmock (3.8.3)
72
+ addressable (>= 2.3.6)
73
+ crack (>= 0.3.2)
74
+ hashdiff (>= 0.4.0, < 2.0.0)
75
+
76
+ PLATFORMS
77
+ ruby
78
+
79
+ DEPENDENCIES
80
+ database_cleaner-sequel (= 1.8.0)
81
+ dotenv (~> 2.7.5)
82
+ maxmind-geoip2 (~> 0.4.0)
83
+ nokogiri (~> 1.10.10)
84
+ phisher_phinder!
85
+ pry
86
+ rake (~> 12.0)
87
+ rspec (~> 3.0)
88
+ sequel (~> 5.33)
89
+ sqlite3 (~> 1.4.2)
90
+ webmock (~> 3.8.3)
91
+
92
+ BUNDLED WITH
93
+ 2.1.4
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Rory McKinley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Rory McKinley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,38 @@
1
+ # PhisherPhinder
2
+
3
+ A simple gem to explore phishing emails
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'phisher_phinder'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle install
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install phisher_phinder
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Development
26
+
27
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
28
+
29
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
30
+
31
+ ## Contributing
32
+
33
+ Bug reports and pull requests are welcome on GitHub at https://github.com/rorymckinley/phisher_phinder.
34
+
35
+
36
+ ## License
37
+
38
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,38 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ require 'dotenv/load'
4
+ require 'sqlite3'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default => :spec
9
+
10
+ namespace :db do
11
+ desc "Run migrations"
12
+ task :migrate, [:version] do |t, args|
13
+ require "sequel/core"
14
+ Sequel.extension :migration
15
+ version = args[:version].to_i if args[:version]
16
+ Sequel.connect(ENV.fetch("DATABASE_URL")) do |db|
17
+ Sequel::Migrator.run(db, "db/migrations", target: version)
18
+ end
19
+ end
20
+
21
+ desc "Create a migration"
22
+ task :create_migration, [:migration_name] do |_, args|
23
+ raise "Please provide a name for the migration" unless args[:migration_name]
24
+ in_use_sequence_numbers = Dir.glob('db/migrations/*.rb').map do |path|
25
+ matches = path.match('\Adb/migrations/(?<sequence>\d{4})_.+\.rb\z')
26
+ matches[:sequence].to_i
27
+ end
28
+ last_sequence = in_use_sequence_numbers.max || 0
29
+
30
+ next_migration_file_path = "db/migrations/%04d_#{args[:migration_name]}.rb" % [last_sequence + 1]
31
+
32
+ File.open(next_migration_file_path, 'w') do |f|
33
+ f.write("Sequel.migration do\n change do\n end\nend")
34
+ end
35
+
36
+ puts "Created #{next_migration_file_path}"
37
+ end
38
+ end
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require 'dotenv'
5
+ Dotenv.load('.env')
6
+
7
+ require'sequel'
8
+ DB = Sequel.connect(ENV.fetch('DATABASE_URL'))
9
+
10
+ require "phisher_phinder"
11
+
12
+ # You can add fixtures and/or initialization code here to make experimenting
13
+ # with your gem easier. You can also use a different console, if you like.
14
+
15
+ # (If you use this, don't forget to add pry to your Gemfile!)
16
+ require "pry"
17
+ Pry.start
18
+
19
+ require "irb"
20
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,35 @@
1
+ Sequel.migration do
2
+ change do
3
+ create_table(:geoip_ip_data) do
4
+ primary_key :id
5
+ String :ip_address
6
+ Integer :location_accuracy_radius
7
+ Float :latitude
8
+ Float :longitude
9
+ String :time_zone
10
+ String :city_name
11
+ String :city_geoname_id
12
+ Integer :city_confidence
13
+ String :country_name
14
+ String :country_geoname_id
15
+ String :country_iso_code
16
+ Integer :country_confidence
17
+ String :continent_name
18
+ String :continent_geoname_id
19
+ String :postal_code
20
+ Integer :postal_code_confidence
21
+ String :registered_country_name
22
+ String :registered_country_geoname_id
23
+ String :registered_country_iso_code
24
+ String :autonomous_system_organisation
25
+ Integer :autonomous_system_number
26
+ String :isp
27
+ String :network
28
+ String :organisation
29
+ String :static_ip_score
30
+ String :user_type
31
+ Time :created_at, null: false
32
+ Time :updated_at, null: false
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,28 @@
1
+ require "phisher_phinder/version"
2
+
3
+ require 'maxmind/geoip2'
4
+
5
+ require 'sequel'
6
+ Sequel::Model.plugin :timestamps, update_on_create: true
7
+
8
+ require_relative './phisher_phinder/body_hyperlink'
9
+ require_relative './phisher_phinder/cached_geoip_client'
10
+ require_relative './phisher_phinder/geoip_ip_data'
11
+ require_relative './phisher_phinder/expanded_data_processor'
12
+ require_relative './phisher_phinder/extended_ip'
13
+ require_relative './phisher_phinder/extended_ip_factory'
14
+ require_relative './phisher_phinder/mail_parser'
15
+ require_relative './phisher_phinder/mail'
16
+ require_relative './phisher_phinder/simple_ip'
17
+ require_relative './phisher_phinder/mail_parser/received_headers/parser'
18
+ require_relative './phisher_phinder/mail_parser/received_headers/by_parser'
19
+ require_relative './phisher_phinder/mail_parser/received_headers/classifier'
20
+ require_relative './phisher_phinder/mail_parser/received_headers/for_parser'
21
+ require_relative './phisher_phinder/mail_parser/received_headers/from_parser'
22
+ require_relative './phisher_phinder/mail_parser/received_headers/starttls_parser'
23
+ require_relative './phisher_phinder/mail_parser/received_headers/timestamp_parser'
24
+
25
+ module PhisherPhinder
26
+ class Error < StandardError; end
27
+ # Your code goes here...
28
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PhisherPhinder
4
+ class BodyHyperlink
5
+ attr_reader :href, :text, :type, :raw_href
6
+
7
+ def initialize(href, text)
8
+ @type = classify_href(href.strip)
9
+ @raw_href = href
10
+ @href = parse_href(href)
11
+ @text = text
12
+ end
13
+
14
+ def supports_retrieval?
15
+ @type == :url && href.is_a?(URI)
16
+ end
17
+
18
+ def ==(other)
19
+ href == other.href && text == other.text
20
+ end
21
+
22
+ private
23
+
24
+ def classify_href(href_value)
25
+ case href_value
26
+ when /\A#/
27
+ :url_fragment
28
+ when /\Amailto:/
29
+ :email_address
30
+ when /\Atel:/
31
+ :telephone_number
32
+ else
33
+ :url
34
+ end
35
+ end
36
+
37
+ def parse_href(href)
38
+ stripped_href = href.strip
39
+
40
+ (@type == :url && !stripped_href.empty?) ? URI.parse(strip_off_fragments(stripped_href)) : stripped_href
41
+ end
42
+
43
+ def strip_off_fragments(uri)
44
+ uri.split('#').first
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PhisherPhinder
4
+ class CachedGeoipClient
5
+ def initialize(client, expiry_time)
6
+ @client = client
7
+ @expiry_time = expiry_time
8
+ @lang = 'en'
9
+ end
10
+
11
+ def lookup(ip_address)
12
+ cached_record = retrieve_cached_record(ip_address)
13
+ if cached_record && cached_record_valid?(cached_record)
14
+ cached_record
15
+ else
16
+ refresh_cache(ip_address, cached_record)
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def retrieve_cached_record(ip_address)
23
+ PhisherPhinder::GeoipIpData.first(ip_address: ip_address)
24
+ end
25
+
26
+ def cached_record_valid?(cached_record)
27
+ cached_record.updated_at > @expiry_time
28
+ end
29
+
30
+ def refresh_cache(ip_address, cached_record)
31
+ lookup_result = @client.insights(ip_address)
32
+
33
+ if cached_record
34
+ cached_record.update(
35
+ location_accuracy_radius: lookup_result.location.accuracy_radius,
36
+ latitude: lookup_result.location.latitude,
37
+ longitude: lookup_result.location.longitude,
38
+ time_zone: lookup_result.location.time_zone,
39
+ city_name: (lookup_result.city.names ? lookup_result.city.names[@lang] : nil),
40
+ city_geoname_id: lookup_result.city.geoname_id,
41
+ city_confidence: lookup_result.city.confidence,
42
+ country_name: lookup_result.country.names[@lang],
43
+ country_geoname_id: lookup_result.country.geoname_id,
44
+ country_iso_code: lookup_result.country.iso_code,
45
+ country_confidence: lookup_result.country.confidence,
46
+ continent_name: lookup_result.continent.names[@lang],
47
+ continent_geoname_id: lookup_result.continent.geoname_id,
48
+ postal_code: lookup_result.postal.code,
49
+ postal_code_confidence: lookup_result.postal.confidence,
50
+ registered_country_name: lookup_result.registered_country.names[@lang],
51
+ registered_country_geoname_id: lookup_result.registered_country.geoname_id,
52
+ registered_country_iso_code: lookup_result.registered_country.iso_code,
53
+ autonomous_system_organisation: lookup_result.traits.autonomous_system_organization,
54
+ autonomous_system_number: lookup_result.traits.autonomous_system_number,
55
+ isp: lookup_result.traits.isp,
56
+ network: lookup_result.traits.network,
57
+ organisation: lookup_result.traits.organization,
58
+ static_ip_score: lookup_result.traits.static_ip_score,
59
+ user_type: lookup_result.traits.user_type
60
+ )
61
+
62
+ cached_record
63
+ else
64
+ PhisherPhinder::GeoipIpData.create(
65
+ ip_address: ip_address,
66
+ location_accuracy_radius: lookup_result.location.accuracy_radius,
67
+ latitude: lookup_result.location.latitude,
68
+ longitude: lookup_result.location.longitude,
69
+ time_zone: lookup_result.location.time_zone,
70
+ city_name: (lookup_result.city.names ? lookup_result.city.names[@lang] : nil),
71
+ city_geoname_id: lookup_result.city.geoname_id,
72
+ city_confidence: lookup_result.city.confidence,
73
+ country_name: lookup_result.country.names[@lang],
74
+ country_geoname_id: lookup_result.country.geoname_id,
75
+ country_iso_code: lookup_result.country.iso_code,
76
+ country_confidence: lookup_result.country.confidence,
77
+ continent_name: lookup_result.continent.names[@lang],
78
+ continent_geoname_id: lookup_result.continent.geoname_id,
79
+ postal_code: lookup_result.postal.code,
80
+ postal_code_confidence: lookup_result.postal.confidence,
81
+ registered_country_name: lookup_result.registered_country.names[@lang],
82
+ registered_country_geoname_id: lookup_result.registered_country.geoname_id,
83
+ registered_country_iso_code: lookup_result.registered_country.iso_code,
84
+ autonomous_system_organisation: lookup_result.traits.autonomous_system_organization,
85
+ autonomous_system_number: lookup_result.traits.autonomous_system_number,
86
+ isp: lookup_result.traits.isp,
87
+ network: lookup_result.traits.network,
88
+ organisation: lookup_result.traits.organization,
89
+ static_ip_score: lookup_result.traits.static_ip_score,
90
+ user_type: lookup_result.traits.user_type
91
+ )
92
+ end
93
+ end
94
+ end
95
+ end