mihari 2.1.0 → 2.2.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/.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
|
+

|
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|
|