mihari 2.1.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -0
- data/README.md +2 -0
- data/bin/console +1 -0
- data/docker/Dockerfile +1 -1
- data/examples/ipinfo_hosted_domains.rb +1 -1
- data/images/{eyecatch.png → overview.png} +0 -0
- data/images/web_alerts.png +0 -0
- data/images/web_config.png +0 -0
- data/lib/mihari/analyzers/base.rb +10 -1
- data/lib/mihari/analyzers/circl.rb +3 -3
- data/lib/mihari/analyzers/onyphe.rb +2 -2
- data/lib/mihari/cli.rb +9 -2
- data/lib/mihari/commands/config.rb +7 -1
- data/lib/mihari/commands/dnstwister.rb +2 -0
- data/lib/mihari/commands/json.rb +6 -0
- data/lib/mihari/commands/onyphe.rb +2 -0
- data/lib/mihari/commands/passive_dns.rb +2 -0
- data/lib/mihari/commands/securitytrails.rb +2 -0
- data/lib/mihari/commands/shodan.rb +2 -0
- data/lib/mihari/commands/spyse.rb +2 -0
- data/lib/mihari/commands/urlscan.rb +2 -0
- data/lib/mihari/commands/virustotal.rb +2 -0
- data/lib/mihari/commands/web.rb +2 -0
- data/lib/mihari/commands/zoomeye.rb +2 -0
- data/lib/mihari/config.rb +1 -1
- data/lib/mihari/models/artifact.rb +13 -2
- data/lib/mihari/status.rb +1 -9
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/app.rb +20 -137
- data/lib/mihari/web/controllers/alerts_controller.rb +66 -0
- data/lib/mihari/web/controllers/artifacts_controller.rb +26 -0
- data/lib/mihari/web/controllers/command_controller.rb +27 -0
- data/lib/mihari/web/controllers/config_controller.rb +15 -0
- data/lib/mihari/web/controllers/sources_controller.rb +14 -0
- data/lib/mihari/web/controllers/tags_controller.rb +30 -0
- data/lib/mihari/web/helpers/json.rb +51 -0
- data/lib/mihari/web/public/index.html +2 -2
- data/lib/mihari/web/public/redoc-static.html +519 -0
- data/lib/mihari/web/public/static/js/{app.280cbdb7.js → app.bcc595df.js} +3 -3
- data/lib/mihari/web/public/static/js/app.bcc595df.js.map +1 -0
- data/mihari.gemspec +2 -1
- metadata +41 -19
- data/lib/mihari/web/public/static/js/app.280cbdb7.js.map +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c5859ab6b5d161359ea07c4af14de087013befa3b51d91844a60127137882e28
|
4
|
+
data.tar.gz: 481f34619d14e04f3187a9d2c3a05b3a1d109c7809622accebc47b100a506bc4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 13693510f6f7d3560d207bb97dec50bb4851514e3b35918a82924e4e73d18e4bdcf963c3cd8928cd17559187b69419c8aa2a8e11e9d857965473dabfdf12ac59
|
7
|
+
data.tar.gz: 8786ad5973687848a89c8737eb92e52dd636e912b6af0ddf339dccccbff28c42f55e4111d1967b1d9a7b8aca07618788e82d98c24a56ca54102b36c84231d21e
|
data/.rubocop.yml
CHANGED
@@ -4,6 +4,9 @@
|
|
4
4
|
require:
|
5
5
|
- rubocop-performance
|
6
6
|
|
7
|
+
AllCops:
|
8
|
+
NewCops: enable
|
9
|
+
|
7
10
|
Style/Alias:
|
8
11
|
Enabled: false
|
9
12
|
StyleGuide: https://relaxed.ruby.style/#stylealias
|
@@ -151,5 +154,8 @@ Lint/AssignmentInCondition:
|
|
151
154
|
Layout/LineLength:
|
152
155
|
Enabled: false
|
153
156
|
|
157
|
+
Style/StringLiteralsInInterpolation:
|
158
|
+
Enabled: false
|
159
|
+
|
154
160
|
Metrics:
|
155
161
|
Enabled: false
|
data/README.md
CHANGED
@@ -12,6 +12,8 @@ Mihari is a framework for continuous OSINT based threat hunting.
|
|
12
12
|
|
13
13
|
## How it works
|
14
14
|
|
15
|
+
![img](https://github.com/ninoseki/mihari/raw/master/images/overview.png)
|
16
|
+
|
15
17
|
- Mihari makes a query against Shodan, Censys, VirusTotal, SecurityTrails, etc. and extracts artifacts (IP addresses, domains, URLs or hashes).
|
16
18
|
- Mihari checks whether a DB (SQLite3, PostgreSQL or MySQL) contains the artifacts or not.
|
17
19
|
- If it doesn't contain the artifacts:
|
data/bin/console
CHANGED
data/docker/Dockerfile
CHANGED
File without changes
|
data/images/web_alerts.png
CHANGED
Binary file
|
data/images/web_config.png
CHANGED
Binary file
|
@@ -8,6 +8,13 @@ module Mihari
|
|
8
8
|
include Configurable
|
9
9
|
include Retriable
|
10
10
|
|
11
|
+
attr_accessor :ignore_old_artifacts, :ignore_threshold
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@ignore_old_artifacts = false
|
15
|
+
@ignore_threshold = 0
|
16
|
+
end
|
17
|
+
|
11
18
|
# @return [Array<String>, Array<Mihari::Artifact>]
|
12
19
|
def artifacts
|
13
20
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
@@ -61,7 +68,9 @@ module Mihari
|
|
61
68
|
|
62
69
|
# @return [Array<Mihari::Artifact>]
|
63
70
|
def unique_artifacts
|
64
|
-
@unique_artifacts ||= normalized_artifacts.select
|
71
|
+
@unique_artifacts ||= normalized_artifacts.select do |artifact|
|
72
|
+
artifact.unique?(ignore_old_artifacts: ignore_old_artifacts, ignore_threshold: ignore_threshold)
|
73
|
+
end
|
65
74
|
end
|
66
75
|
|
67
76
|
def set_unique_artifacts
|
@@ -46,14 +46,14 @@ module Mihari
|
|
46
46
|
def passive_dns_lookup
|
47
47
|
results = api.dns.query(@query)
|
48
48
|
results.map do |result|
|
49
|
-
type = result
|
50
|
-
type == "A" ? result
|
49
|
+
type = result["rrtype"]
|
50
|
+
type == "A" ? result["rdata"] : nil
|
51
51
|
end.compact.uniq
|
52
52
|
end
|
53
53
|
|
54
54
|
def passive_ssl_lookup
|
55
55
|
result = api.ssl.cquery(@query)
|
56
|
-
seen = result
|
56
|
+
seen = result["seen"] || []
|
57
57
|
seen.uniq
|
58
58
|
end
|
59
59
|
end
|
@@ -21,10 +21,10 @@ module Mihari
|
|
21
21
|
return [] unless results
|
22
22
|
|
23
23
|
flat_results = results.map do |result|
|
24
|
-
result
|
24
|
+
result["results"]
|
25
25
|
end.flatten.compact
|
26
26
|
|
27
|
-
flat_results.map { |result| result
|
27
|
+
flat_results.map { |result| result["ip"] }.compact.uniq
|
28
28
|
end
|
29
29
|
|
30
30
|
private
|
data/lib/mihari/cli.rb
CHANGED
@@ -33,7 +33,10 @@ require "mihari/commands/web"
|
|
33
33
|
|
34
34
|
module Mihari
|
35
35
|
class CLI < Thor
|
36
|
-
class_option :config, type: :string, desc: "
|
36
|
+
class_option :config, type: :string, desc: "Path to the config file"
|
37
|
+
|
38
|
+
class_option :ignore_old_artifacts, type: :boolean, default: false, desc: "Whether to ignore old artifacts from checking or not. Only affects with analyze commands."
|
39
|
+
class_option :ignore_threshold, type: :numeric, default: 0, desc: "Number of days to define whether an artifact is old or not. Only affects with analyze commands."
|
37
40
|
|
38
41
|
include Mihari::Commands::BinaryEdge
|
39
42
|
include Mihari::Commands::Censys
|
@@ -96,11 +99,15 @@ module Mihari
|
|
96
99
|
options = normalize_options(options)
|
97
100
|
|
98
101
|
analyzer = analyzer_class.new(query, **options)
|
102
|
+
|
103
|
+
analyzer.ignore_old_artifacts = options["ignore_old_artifacts"] || false
|
104
|
+
analyzer.ignore_threshold = options["ignore_threshold"] || 0
|
105
|
+
|
99
106
|
analyzer.run
|
100
107
|
end
|
101
108
|
|
102
109
|
def symbolize_hash_keys(hash)
|
103
|
-
hash.
|
110
|
+
hash.transform_keys(&:to_sym)
|
104
111
|
end
|
105
112
|
|
106
113
|
def normalize_options(options)
|
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "colorize"
|
4
|
+
|
1
5
|
module Mihari
|
2
6
|
module Commands
|
3
7
|
module Config
|
@@ -8,11 +12,13 @@ module Mihari
|
|
8
12
|
def init_config
|
9
13
|
filename = options["filename"]
|
10
14
|
|
11
|
-
|
15
|
+
warning = "#{filename} exists. Do you want to overwrite it? (y/n)"
|
16
|
+
if File.exist?(filename) && !(yes? warning)
|
12
17
|
return
|
13
18
|
end
|
14
19
|
|
15
20
|
Mihari::Config.initialize_yaml filename
|
21
|
+
puts "The config file is initialized as #{filename}.".colorize(:blue)
|
16
22
|
end
|
17
23
|
end
|
18
24
|
end
|
data/lib/mihari/commands/json.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Mihari
|
2
4
|
module Commands
|
3
5
|
module JSON
|
@@ -18,6 +20,10 @@ module Mihari
|
|
18
20
|
tags = json["tags"] || []
|
19
21
|
|
20
22
|
basic = Analyzers::Basic.new(title: title, description: description, artifacts: artifacts, source: "json", tags: tags)
|
23
|
+
|
24
|
+
basic.ignore_old_artifacts = options["ignore_old_artifacts"] || false
|
25
|
+
basic.ignore_threshold = options["ignore_threshold"] || 0
|
26
|
+
|
21
27
|
basic.run
|
22
28
|
end
|
23
29
|
end
|
data/lib/mihari/commands/web.rb
CHANGED
data/lib/mihari/config.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_record"
|
4
|
+
require "active_record/filter"
|
5
|
+
require "active_support/core_ext/integer/time"
|
6
|
+
require "active_support/core_ext/numeric/time"
|
4
7
|
|
5
8
|
class ArtifactValidator < ActiveModel::Validator
|
6
9
|
def validate(record)
|
@@ -20,8 +23,16 @@ module Mihari
|
|
20
23
|
self.data_type = TypeChecker.type(data)
|
21
24
|
end
|
22
25
|
|
23
|
-
def unique?
|
24
|
-
self.class.
|
26
|
+
def unique?(ignore_old_artifacts: false, ignore_threshold: 0)
|
27
|
+
artifact = self.class.where(data: data).order(created_at: :desc).first
|
28
|
+
return true if artifact.nil?
|
29
|
+
|
30
|
+
return false unless ignore_old_artifacts
|
31
|
+
|
32
|
+
days_before = (-ignore_threshold).days.from_now
|
33
|
+
# if an artifact is created before {ignore_threshold} days, ignore it
|
34
|
+
# within {ignore_threshold} days, do not ignore it
|
35
|
+
artifact.created_at < days_before
|
25
36
|
end
|
26
37
|
end
|
27
38
|
end
|
data/lib/mihari/status.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module Mihari
|
4
4
|
class Status
|
5
5
|
def check
|
6
|
-
statuses
|
6
|
+
statuses
|
7
7
|
end
|
8
8
|
|
9
9
|
def self.check
|
@@ -12,14 +12,6 @@ module Mihari
|
|
12
12
|
|
13
13
|
private
|
14
14
|
|
15
|
-
def convert(is_configured:, values:, type:)
|
16
|
-
{
|
17
|
-
is_configured: is_configured,
|
18
|
-
values: values,
|
19
|
-
type: type
|
20
|
-
}
|
21
|
-
end
|
22
|
-
|
23
15
|
def statuses
|
24
16
|
(Mihari.analyzers + Mihari.emitters).map do |klass|
|
25
17
|
name = klass.to_s.split("::").last.to_s
|
data/lib/mihari/version.rb
CHANGED
data/lib/mihari/web/app.rb
CHANGED
@@ -1,16 +1,21 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
3
|
require "launchy"
|
4
4
|
require "rack"
|
5
|
-
require "
|
5
|
+
require "rack/handler/puma"
|
6
6
|
require "sinatra"
|
7
|
-
|
8
|
-
require "
|
7
|
+
|
8
|
+
require "mihari/web/helpers/json"
|
9
|
+
|
10
|
+
require "mihari/web/controllers/alerts_controller"
|
11
|
+
require "mihari/web/controllers/artifacts_controller"
|
12
|
+
require "mihari/web/controllers/command_controller"
|
13
|
+
require "mihari/web/controllers/config_controller"
|
14
|
+
require "mihari/web/controllers/sources_controller"
|
15
|
+
require "mihari/web/controllers/tags_controller"
|
9
16
|
|
10
17
|
module Mihari
|
11
18
|
class App < Sinatra::Base
|
12
|
-
register Sinatra::Reloader
|
13
|
-
|
14
19
|
set :root, File.dirname(__FILE__)
|
15
20
|
set :public_folder, File.join(root, "public")
|
16
21
|
|
@@ -18,140 +23,18 @@ module Mihari
|
|
18
23
|
send_file File.join(settings.public_folder, "index.html")
|
19
24
|
end
|
20
25
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
description = params["description"]
|
28
|
-
source = params["source"]
|
29
|
-
tag_name = params["tag"]
|
30
|
-
title = params["title"]
|
31
|
-
|
32
|
-
from_at = params["from_at"] || params["fromAt"]
|
33
|
-
from_at = DateTime.parse(from_at) if from_at
|
34
|
-
to_at = params["to_at"] || params["toAt"]
|
35
|
-
to_at = DateTime.parse(to_at) if to_at
|
36
|
-
|
37
|
-
alerts = Mihari::Alert.search(
|
38
|
-
artifact_data: artifact_data,
|
39
|
-
description: description,
|
40
|
-
from_at: from_at,
|
41
|
-
limit: limit,
|
42
|
-
page: page,
|
43
|
-
source: source,
|
44
|
-
tag_name: tag_name,
|
45
|
-
title: title,
|
46
|
-
to_at: to_at
|
47
|
-
)
|
48
|
-
total = Mihari::Alert.count(
|
49
|
-
artifact_data: artifact_data,
|
50
|
-
description: description,
|
51
|
-
from_at: from_at,
|
52
|
-
source: source,
|
53
|
-
tag_name: tag_name,
|
54
|
-
title: title,
|
55
|
-
to_at: to_at
|
56
|
-
)
|
57
|
-
|
58
|
-
json = { alerts: alerts, total: total, current_page: page, page_size: limit }
|
59
|
-
json json.to_camelback_keys
|
60
|
-
end
|
61
|
-
|
62
|
-
delete "/api/alerts/:id" do
|
63
|
-
id = params["id"]
|
64
|
-
id = id.to_i
|
65
|
-
|
66
|
-
begin
|
67
|
-
alert = Mihari::Alert.find(id)
|
68
|
-
alert.destroy
|
69
|
-
|
70
|
-
status 204
|
71
|
-
body ""
|
72
|
-
rescue ActiveRecord::RecordNotFound
|
73
|
-
status 404
|
74
|
-
|
75
|
-
message = { message: "ID:#{id} is not found" }
|
76
|
-
json message
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
delete "/api/artifacts/:id" do
|
81
|
-
id = params["id"]
|
82
|
-
id = id.to_i
|
83
|
-
|
84
|
-
begin
|
85
|
-
alert = Mihari::Artifact.find(id)
|
86
|
-
alert.delete
|
87
|
-
|
88
|
-
status 204
|
89
|
-
body ""
|
90
|
-
rescue ActiveRecord::RecordNotFound
|
91
|
-
status 404
|
92
|
-
|
93
|
-
message = { message: "ID:#{id} is not found" }
|
94
|
-
json message
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
delete "/api/tags/:name" do
|
99
|
-
name = params["name"]
|
100
|
-
|
101
|
-
begin
|
102
|
-
Mihari::Tag.where(name: name).destroy_all
|
103
|
-
|
104
|
-
status 204
|
105
|
-
body ""
|
106
|
-
rescue ActiveRecord::RecordNotFound
|
107
|
-
status 404
|
108
|
-
|
109
|
-
message = { message: "Name:#{name} is not found" }
|
110
|
-
json message
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
get "/api/sources" do
|
115
|
-
tags = Mihari::Alert.distinct.pluck(:source)
|
116
|
-
json tags
|
117
|
-
end
|
118
|
-
|
119
|
-
get "/api/tags" do
|
120
|
-
tags = Mihari::Tag.distinct.pluck(:name)
|
121
|
-
json tags
|
122
|
-
end
|
123
|
-
|
124
|
-
get "/api/config" do
|
125
|
-
report = Status.check
|
126
|
-
|
127
|
-
json report.to_camelback_keys
|
128
|
-
end
|
129
|
-
|
130
|
-
post "/api/command" do
|
131
|
-
payload = JSON.parse(request.body.read)
|
132
|
-
|
133
|
-
command = payload["command"]
|
134
|
-
if command.nil?
|
135
|
-
status 400
|
136
|
-
return json( { message: "command is required" })
|
137
|
-
end
|
138
|
-
|
139
|
-
command = command.split
|
140
|
-
|
141
|
-
output = SafeShell.execute("mihari", *command)
|
142
|
-
success = $?.success?
|
143
|
-
|
144
|
-
json({ output: output, success: success })
|
145
|
-
end
|
26
|
+
use Mihari::Controllers::AlertsController
|
27
|
+
use Mihari::Controllers::ArtifactsController
|
28
|
+
use Mihari::Controllers::CommandController
|
29
|
+
use Mihari::Controllers::ConfigController
|
30
|
+
use Mihari::Controllers::SourcesController
|
31
|
+
use Mihari::Controllers::TagsController
|
146
32
|
|
147
33
|
class << self
|
148
34
|
def run!(port: 9292, host: "localhost")
|
149
|
-
url =
|
150
|
-
|
151
|
-
puts "The app will be available at #{url}.".colorize(:blue)
|
152
|
-
puts "(Press Ctrl+C to quit)".colorize(:blue)
|
35
|
+
url = "http://#{host}:#{port}"
|
153
36
|
|
154
|
-
Rack::Handler::
|
37
|
+
Rack::Handler::Puma.run self, Port: port, Host: host do |server|
|
155
38
|
Launchy.open url
|
156
39
|
|
157
40
|
[:INT, :TERM].each do |sig|
|