miteru 1.2.2 → 2.0.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.
- checksums.yaml +4 -4
- data/.github/workflows/gem.yml +36 -0
- data/.github/workflows/{test.yml → ruby.yml} +4 -13
- data/.gitignore +4 -1
- data/.rspec +1 -1
- data/README.md +7 -17
- data/docker-compose.yml +12 -0
- data/exe/miteru +3 -3
- data/lefthook.yml +9 -0
- data/lib/miteru/cli/application.rb +27 -0
- data/lib/miteru/cli/base.rb +16 -0
- data/lib/miteru/cli/database.rb +11 -0
- data/lib/miteru/commands/database.rb +23 -0
- data/lib/miteru/commands/main.rb +37 -0
- data/lib/miteru/commands/sidekiq.rb +35 -0
- data/lib/miteru/commands/web.rb +37 -0
- data/lib/miteru/concerns/database_connectable.rb +16 -0
- data/lib/miteru/concerns/error_unwrappable.rb +30 -0
- data/lib/miteru/config.rb +98 -0
- data/lib/miteru/crawler.rb +28 -44
- data/lib/miteru/database.rb +50 -38
- data/lib/miteru/downloader.rb +52 -41
- data/lib/miteru/errors.rb +37 -0
- data/lib/miteru/feeds/ayashige.rb +9 -20
- data/lib/miteru/feeds/base.rb +141 -0
- data/lib/miteru/feeds/phishing_database.rb +11 -10
- data/lib/miteru/feeds/urlscan.rb +47 -19
- data/lib/miteru/feeds/urlscan_pro.rb +20 -18
- data/lib/miteru/http.rb +51 -0
- data/lib/miteru/kit.rb +28 -20
- data/lib/miteru/mixin.rb +2 -29
- data/lib/miteru/notifiers/base.rb +10 -3
- data/lib/miteru/notifiers/slack.rb +85 -10
- data/lib/miteru/notifiers/urlscan.rb +29 -14
- data/lib/miteru/orchestrator.rb +51 -0
- data/lib/miteru/record.rb +8 -15
- data/lib/miteru/service.rb +28 -0
- data/lib/miteru/sidekiq/application.rb +13 -0
- data/lib/miteru/sidekiq/jobs.rb +21 -0
- data/lib/miteru/version.rb +1 -1
- data/lib/miteru/web/application.rb +42 -0
- data/lib/miteru/website.rb +48 -48
- data/lib/miteru.rb +130 -22
- data/miteru-sidekiq.service +13 -0
- data/miteru.db-shm +0 -0
- data/miteru.db-wal +0 -0
- data/miteru.gemspec +49 -38
- metadata +265 -97
- data/.overcommit.yml +0 -12
- data/.standard.yml +0 -4
- data/lib/miteru/attachement.rb +0 -74
- data/lib/miteru/cli.rb +0 -41
- data/lib/miteru/configuration.rb +0 -122
- data/lib/miteru/error.rb +0 -7
- data/lib/miteru/feeds/feed.rb +0 -53
- data/lib/miteru/feeds/phishstats.rb +0 -28
- data/lib/miteru/feeds.rb +0 -45
- data/lib/miteru/http_client.rb +0 -85
data/.standard.yml
DELETED
data/lib/miteru/attachement.rb
DELETED
@@ -1,74 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "uri"
|
4
|
-
|
5
|
-
module Miteru
|
6
|
-
class Attachement
|
7
|
-
attr_reader :url
|
8
|
-
|
9
|
-
def initialize(url)
|
10
|
-
@url = url
|
11
|
-
end
|
12
|
-
|
13
|
-
def to_a
|
14
|
-
[
|
15
|
-
{
|
16
|
-
text: defanged_url,
|
17
|
-
fallback: "VT & urlscan.io links",
|
18
|
-
actions: actions
|
19
|
-
}
|
20
|
-
]
|
21
|
-
end
|
22
|
-
|
23
|
-
private
|
24
|
-
|
25
|
-
def actions
|
26
|
-
[vt_link, urlscan_link].compact
|
27
|
-
end
|
28
|
-
|
29
|
-
def vt_link
|
30
|
-
return nil unless _vt_link
|
31
|
-
|
32
|
-
{
|
33
|
-
type: "button",
|
34
|
-
text: "Lookup on VirusTotal",
|
35
|
-
url: _vt_link
|
36
|
-
}
|
37
|
-
end
|
38
|
-
|
39
|
-
def urlscan_link
|
40
|
-
return nil unless _urlscan_link
|
41
|
-
|
42
|
-
{
|
43
|
-
type: "button",
|
44
|
-
text: "Lookup on urlscan.io",
|
45
|
-
url: _urlscan_link
|
46
|
-
}
|
47
|
-
end
|
48
|
-
|
49
|
-
def defanged_url
|
50
|
-
@defanged_url ||= url.to_s.gsub(/\./, "[.]")
|
51
|
-
end
|
52
|
-
|
53
|
-
def domain
|
54
|
-
@domain ||=
|
55
|
-
[].tap do |out|
|
56
|
-
out << URI(url).hostname
|
57
|
-
rescue URI::Error => _e
|
58
|
-
out << nil
|
59
|
-
end.first
|
60
|
-
end
|
61
|
-
|
62
|
-
def _urlscan_link
|
63
|
-
return nil unless domain
|
64
|
-
|
65
|
-
"https://urlscan.io/domain/#{domain}"
|
66
|
-
end
|
67
|
-
|
68
|
-
def _vt_link
|
69
|
-
return nil unless domain
|
70
|
-
|
71
|
-
"https://www.virustotal.com/#/domain/#{domain}"
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
data/lib/miteru/cli.rb
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "thor"
|
4
|
-
|
5
|
-
module Miteru
|
6
|
-
class CLI < Thor
|
7
|
-
method_option :auto_download, type: :boolean, default: false, desc: "Enable or disable auto-download of phishing kits"
|
8
|
-
method_option :ayashige, type: :boolean, default: false, desc: "Enable or disable Ayashige(ninoseki/ayashige) feed"
|
9
|
-
method_option :directory_traveling, type: :boolean, default: false, desc: "Enable or disable directory traveling"
|
10
|
-
method_option :download_to, type: :string, default: "/tmp", desc: "Directory to download phishing kits"
|
11
|
-
method_option :post_to_slack, type: :boolean, default: false, desc: "Enable or disable Slack notification"
|
12
|
-
method_option :size, type: :numeric, default: 100, desc: "Number of urlscan.io's results to fetch. (Max: 10,000)"
|
13
|
-
method_option :threads, type: :numeric, desc: "Number of threads to use"
|
14
|
-
method_option :verbose, type: :boolean, default: true
|
15
|
-
desc "execute", "Execute the crawler"
|
16
|
-
def execute
|
17
|
-
Miteru.configure do |config|
|
18
|
-
config.auto_download = options["auto_download"]
|
19
|
-
config.ayashige = options["ayashige"]
|
20
|
-
config.directory_traveling = options["directory_traveling"]
|
21
|
-
config.download_to = options["download_to"]
|
22
|
-
config.post_to_slack = options["post_to_slack"]
|
23
|
-
config.size = options["size"]
|
24
|
-
config.verbose = options["verbose"]
|
25
|
-
|
26
|
-
threads = options["threads"]
|
27
|
-
config.threads = threads if threads
|
28
|
-
end
|
29
|
-
|
30
|
-
Crawler.execute
|
31
|
-
end
|
32
|
-
|
33
|
-
default_command :execute
|
34
|
-
|
35
|
-
class << self
|
36
|
-
def exit_on_failure?
|
37
|
-
true
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
data/lib/miteru/configuration.rb
DELETED
@@ -1,122 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "parallel"
|
4
|
-
|
5
|
-
module Miteru
|
6
|
-
class Configuration
|
7
|
-
# @return [Boolean]
|
8
|
-
attr_accessor :auto_download
|
9
|
-
|
10
|
-
# @return [Boolean]
|
11
|
-
attr_accessor :ayashige
|
12
|
-
|
13
|
-
# @return [Boolean]
|
14
|
-
attr_accessor :directory_traveling
|
15
|
-
|
16
|
-
# @return [String]
|
17
|
-
attr_accessor :download_to
|
18
|
-
|
19
|
-
# @return [Boolean]
|
20
|
-
attr_accessor :post_to_slack
|
21
|
-
|
22
|
-
# @return [Integer]
|
23
|
-
attr_accessor :size
|
24
|
-
|
25
|
-
# @return [Integer]
|
26
|
-
attr_accessor :threads
|
27
|
-
|
28
|
-
# @return [Boolean]
|
29
|
-
attr_accessor :verbose
|
30
|
-
|
31
|
-
# @return [String]
|
32
|
-
attr_accessor :database
|
33
|
-
|
34
|
-
# @return [String, nil]
|
35
|
-
attr_accessor :slack_webhook_url
|
36
|
-
|
37
|
-
# @return [String]
|
38
|
-
attr_accessor :slack_channel
|
39
|
-
|
40
|
-
# @return [String]
|
41
|
-
attr_accessor :urlscan_api_key
|
42
|
-
|
43
|
-
# @return [String]
|
44
|
-
attr_accessor :urlscan_submit_visibility
|
45
|
-
|
46
|
-
# @return [Array<String>]
|
47
|
-
attr_reader :valid_extensions
|
48
|
-
|
49
|
-
# @return [Array<String>]
|
50
|
-
attr_reader :valid_mime_types
|
51
|
-
|
52
|
-
# @return [Integer]
|
53
|
-
attr_reader :file_maxsize
|
54
|
-
|
55
|
-
def initialize
|
56
|
-
@auto_download = false
|
57
|
-
@ayashige = false
|
58
|
-
@directory_traveling = false
|
59
|
-
@download_to = "/tmp"
|
60
|
-
@post_to_slack = false
|
61
|
-
@size = 100
|
62
|
-
@threads = Parallel.processor_count
|
63
|
-
@verbose = false
|
64
|
-
@database = ENV.fetch("MITERU_DATABASE", "miteru.db")
|
65
|
-
@file_maxsize = ENV.fetch("FILE_MAXSIZE", 1024 * 1024 * 100).to_i
|
66
|
-
|
67
|
-
@slack_webhook_url = ENV.fetch("SLACK_WEBHOOK_URL", nil)
|
68
|
-
@slack_channel = ENV.fetch("SLACK_CHANNEL", "#general")
|
69
|
-
|
70
|
-
@urlscan_api_key = ENV.fetch("URLSCAN_API_KEY", nil)
|
71
|
-
@urlscan_submit_visibility = ENV.fetch("URLSCAN_SUBMIT_VISIBILITY", "public")
|
72
|
-
|
73
|
-
@valid_extensions = [".zip", ".rar", ".7z", ".tar", ".gz"].freeze
|
74
|
-
@valid_mime_types = ["application/zip", "application/vnd.rar", "application/x-7z-compressed", "application/x-tar", "application/gzip"]
|
75
|
-
end
|
76
|
-
|
77
|
-
def auto_download?
|
78
|
-
@auto_download
|
79
|
-
end
|
80
|
-
|
81
|
-
def ayashige?
|
82
|
-
@ayashige
|
83
|
-
end
|
84
|
-
|
85
|
-
def directory_traveling?
|
86
|
-
@directory_traveling
|
87
|
-
end
|
88
|
-
|
89
|
-
def post_to_slack?
|
90
|
-
@post_to_slack
|
91
|
-
end
|
92
|
-
|
93
|
-
def verbose?
|
94
|
-
@verbose
|
95
|
-
end
|
96
|
-
|
97
|
-
def slack_webhook_url?
|
98
|
-
!@slack_webhook_url.nil?
|
99
|
-
end
|
100
|
-
|
101
|
-
def urlscan_api_key?
|
102
|
-
!@urlscan_api_key.nil?
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
class << self
|
107
|
-
# @return [Miteru::Configuration] Miteru's current configuration
|
108
|
-
def configuration
|
109
|
-
@configuration ||= Configuration.new
|
110
|
-
end
|
111
|
-
|
112
|
-
# Set Miteru's configuration
|
113
|
-
# @param config [Miteru::Configuration]
|
114
|
-
attr_writer :configuration
|
115
|
-
|
116
|
-
# Modify Miteru's current configuration
|
117
|
-
# @yieldparam [Miteru::Configuration] config current Miteru config
|
118
|
-
def configure
|
119
|
-
yield configuration
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
data/lib/miteru/error.rb
DELETED
data/lib/miteru/feeds/feed.rb
DELETED
@@ -1,53 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Miteru
|
4
|
-
class Feeds
|
5
|
-
class Feed
|
6
|
-
include Mixins::URL
|
7
|
-
|
8
|
-
def source
|
9
|
-
@source ||= self.class.to_s.split("::").last
|
10
|
-
end
|
11
|
-
|
12
|
-
#
|
13
|
-
# Return URLs
|
14
|
-
#
|
15
|
-
# @return [Array<String>] URLs
|
16
|
-
#
|
17
|
-
def urls
|
18
|
-
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
19
|
-
end
|
20
|
-
|
21
|
-
#
|
22
|
-
# Return entries
|
23
|
-
#
|
24
|
-
# @return [Array<Miteru::Entry>]
|
25
|
-
#
|
26
|
-
def entries
|
27
|
-
breakdowend_urls.map do |url|
|
28
|
-
Entry.new(url, source)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
#
|
33
|
-
# Return breakdowned URLs
|
34
|
-
#
|
35
|
-
# @return [Array<String>] Breakdowned URLs
|
36
|
-
#
|
37
|
-
def breakdowend_urls
|
38
|
-
urls.select { |url| url.start_with?("http://", "https://") }.map do |url|
|
39
|
-
breakdown(url, Miteru.configuration.directory_traveling?)
|
40
|
-
end.flatten.uniq
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
def get(url)
|
46
|
-
res = HTTPClient.get(url)
|
47
|
-
raise HTTPResponseError if res.code != 200
|
48
|
-
|
49
|
-
res.body.to_s
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "json"
|
4
|
-
require "uri"
|
5
|
-
|
6
|
-
module Miteru
|
7
|
-
class Feeds
|
8
|
-
class PhishStats < Feed
|
9
|
-
URL = "https://phishstats.info:2096/api/phishing?_sort=-id&size=100"
|
10
|
-
|
11
|
-
def urls
|
12
|
-
json = JSON.parse(get(URL))
|
13
|
-
json.map do |entry|
|
14
|
-
entry["url"]
|
15
|
-
end
|
16
|
-
rescue HTTPResponseError, HTTP::Error, JSON::ParserError => e
|
17
|
-
Miteru.logger.error "Failed to load PhishStats feed (#{e})"
|
18
|
-
[]
|
19
|
-
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
def url_for(path)
|
24
|
-
URI(URL + path)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
data/lib/miteru/feeds.rb
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "./feeds/feed"
|
4
|
-
require_relative "./feeds/phishing_database"
|
5
|
-
require_relative "./feeds/phishstats"
|
6
|
-
require_relative "./feeds/ayashige"
|
7
|
-
require_relative "./feeds/urlscan"
|
8
|
-
require_relative "./feeds/urlscan_pro"
|
9
|
-
|
10
|
-
module Miteru
|
11
|
-
class Entry
|
12
|
-
# @return [String]
|
13
|
-
attr_reader :url
|
14
|
-
# @return [String]
|
15
|
-
attr_reader :source
|
16
|
-
|
17
|
-
def initialize(url, source)
|
18
|
-
@url = url
|
19
|
-
@source = source
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
class Feeds
|
24
|
-
IGNORE_EXTENSIONS = %w[.htm .html .php .asp .aspx .exe .txt].freeze
|
25
|
-
|
26
|
-
def initialize
|
27
|
-
@feeds = [
|
28
|
-
PhishingDatabase.new,
|
29
|
-
PhishStats.new,
|
30
|
-
UrlScan.new(Miteru.configuration.size),
|
31
|
-
UrlScanPro.new,
|
32
|
-
Miteru.configuration.ayashige? ? Ayashige.new : nil
|
33
|
-
].compact
|
34
|
-
end
|
35
|
-
|
36
|
-
#
|
37
|
-
# Returns a list of suspicious entries
|
38
|
-
#
|
39
|
-
# @return [Array<Entry>]
|
40
|
-
#
|
41
|
-
def suspicious_entries
|
42
|
-
@suspicious_entries ||= @feeds.map(&:entries).flatten.uniq(&:url)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
data/lib/miteru/http_client.rb
DELETED
@@ -1,85 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "down/http"
|
4
|
-
require "http"
|
5
|
-
require "uri"
|
6
|
-
|
7
|
-
module Miteru
|
8
|
-
class HTTPClient
|
9
|
-
DEFAULT_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36"
|
10
|
-
URLSCAN_UA = "miteru/#{Miteru::VERSION}"
|
11
|
-
|
12
|
-
attr_reader :ssl_context
|
13
|
-
|
14
|
-
def initialize
|
15
|
-
ctx = OpenSSL::SSL::SSLContext.new
|
16
|
-
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
17
|
-
@ssl_context = ctx
|
18
|
-
end
|
19
|
-
|
20
|
-
def download(url, destination)
|
21
|
-
down = Down::Http.new(**default_options) { |client| client.headers(**default_headers) }
|
22
|
-
down.download(url, destination: destination)
|
23
|
-
destination
|
24
|
-
end
|
25
|
-
|
26
|
-
def head(url, options = {})
|
27
|
-
options = options.merge default_options
|
28
|
-
|
29
|
-
HTTP.follow
|
30
|
-
.timeout(3)
|
31
|
-
.headers(urlscan_url?(url) ? urlscan_headers : default_headers)
|
32
|
-
.head(url, options)
|
33
|
-
end
|
34
|
-
|
35
|
-
def get(url, options = {})
|
36
|
-
options = options.merge default_options
|
37
|
-
|
38
|
-
HTTP.follow
|
39
|
-
.timeout(write: 2, connect: 5, read: 10)
|
40
|
-
.headers(urlscan_url?(url) ? urlscan_headers : default_headers)
|
41
|
-
.get(url, options)
|
42
|
-
end
|
43
|
-
|
44
|
-
def post(url, options = {})
|
45
|
-
HTTP.post url, options
|
46
|
-
end
|
47
|
-
|
48
|
-
class << self
|
49
|
-
def download(url, base_dir = "/tmp")
|
50
|
-
new.download(url, base_dir)
|
51
|
-
end
|
52
|
-
|
53
|
-
def get(url, options = {})
|
54
|
-
new.get url, options
|
55
|
-
end
|
56
|
-
|
57
|
-
def post(url, options = {})
|
58
|
-
new.post url, options
|
59
|
-
end
|
60
|
-
|
61
|
-
def head(url, options = {})
|
62
|
-
new.head url, options
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
private
|
67
|
-
|
68
|
-
def default_headers
|
69
|
-
{ user_agent: DEFAULT_UA }
|
70
|
-
end
|
71
|
-
|
72
|
-
def default_options
|
73
|
-
{ ssl_context: ssl_context }
|
74
|
-
end
|
75
|
-
|
76
|
-
def urlscan_headers
|
77
|
-
{ user_agent: URLSCAN_UA }
|
78
|
-
end
|
79
|
-
|
80
|
-
def urlscan_url?(url)
|
81
|
-
uri = URI(url)
|
82
|
-
uri.hostname == "urlscan.io"
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|