mihari 2.1.0 → 2.4.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 +8 -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/tines.png +0 -0
- data/images/web_alerts.png +0 -0
- data/images/web_config.png +0 -0
- data/lib/mihari.rb +1 -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/analyzers/urlscan.rb +1 -6
- data/lib/mihari/analyzers/zoomeye.rb +2 -2
- data/lib/mihari/cli.rb +12 -3
- 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 -2
- 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 +5 -4
- data/lib/mihari/emitters/slack.rb +0 -3
- data/lib/mihari/emitters/webhook.rb +60 -0
- data/lib/mihari/models/artifact.rb +13 -2
- data/lib/mihari/notifiers/slack.rb +0 -1
- data/lib/mihari/status.rb +1 -9
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/app.rb +22 -137
- data/lib/mihari/web/controllers/alerts_controller.rb +75 -0
- data/lib/mihari/web/controllers/artifacts_controller.rb +24 -0
- data/lib/mihari/web/controllers/base_controller.rb +22 -0
- data/lib/mihari/web/controllers/command_controller.rb +26 -0
- data/lib/mihari/web/controllers/config_controller.rb +13 -0
- data/lib/mihari/web/controllers/sources_controller.rb +12 -0
- data/lib/mihari/web/controllers/tags_controller.rb +28 -0
- data/lib/mihari/web/helpers/json.rb +53 -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.cccddb2b.js} +3 -3
- data/lib/mihari/web/public/static/js/app.cccddb2b.js.map +1 -0
- data/mihari.gemspec +10 -6
- metadata +96 -30
- data/lib/mihari/slack_monkeypatch.rb +0 -16
- data/lib/mihari/web/public/static/js/app.280cbdb7.js.map +0 -1
@@ -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,23 @@
|
|
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/base_controller"
|
11
|
+
|
12
|
+
require "mihari/web/controllers/alerts_controller"
|
13
|
+
require "mihari/web/controllers/artifacts_controller"
|
14
|
+
require "mihari/web/controllers/command_controller"
|
15
|
+
require "mihari/web/controllers/config_controller"
|
16
|
+
require "mihari/web/controllers/sources_controller"
|
17
|
+
require "mihari/web/controllers/tags_controller"
|
9
18
|
|
10
19
|
module Mihari
|
11
20
|
class App < Sinatra::Base
|
12
|
-
register Sinatra::Reloader
|
13
|
-
|
14
21
|
set :root, File.dirname(__FILE__)
|
15
22
|
set :public_folder, File.join(root, "public")
|
16
23
|
|
@@ -18,140 +25,18 @@ module Mihari
|
|
18
25
|
send_file File.join(settings.public_folder, "index.html")
|
19
26
|
end
|
20
27
|
|
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
|
28
|
+
use Mihari::Controllers::AlertsController
|
29
|
+
use Mihari::Controllers::ArtifactsController
|
30
|
+
use Mihari::Controllers::CommandController
|
31
|
+
use Mihari::Controllers::ConfigController
|
32
|
+
use Mihari::Controllers::SourcesController
|
33
|
+
use Mihari::Controllers::TagsController
|
146
34
|
|
147
35
|
class << self
|
148
36
|
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)
|
37
|
+
url = "http://#{host}:#{port}"
|
153
38
|
|
154
|
-
Rack::Handler::
|
39
|
+
Rack::Handler::Puma.run self, Port: port, Host: host do |server|
|
155
40
|
Launchy.open url
|
156
41
|
|
157
42
|
[:INT, :TERM].each do |sig|
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Controllers
|
5
|
+
class AlertsController < BaseController
|
6
|
+
get "/api/alerts" do
|
7
|
+
param :page, Integer
|
8
|
+
param :artifact, String
|
9
|
+
param :description, String
|
10
|
+
param :source, String
|
11
|
+
param :tag, String
|
12
|
+
|
13
|
+
param :from_at, DateTime
|
14
|
+
param :fromAt, DateTime
|
15
|
+
param :to_at, DateTime
|
16
|
+
param :toAt, DateTime
|
17
|
+
|
18
|
+
page = params["page"] || 1
|
19
|
+
page = page.to_i
|
20
|
+
limit = 10
|
21
|
+
|
22
|
+
artifact_data = params["artifact"]
|
23
|
+
description = params["description"]
|
24
|
+
source = params["source"]
|
25
|
+
tag_name = params["tag"]
|
26
|
+
title = params["title"]
|
27
|
+
|
28
|
+
from_at = params["from_at"] || params["fromAt"]
|
29
|
+
from_at = DateTime.parse(from_at) if from_at
|
30
|
+
to_at = params["to_at"] || params["toAt"]
|
31
|
+
to_at = DateTime.parse(to_at) if to_at
|
32
|
+
|
33
|
+
alerts = Mihari::Alert.search(
|
34
|
+
artifact_data: artifact_data,
|
35
|
+
description: description,
|
36
|
+
from_at: from_at,
|
37
|
+
limit: limit,
|
38
|
+
page: page,
|
39
|
+
source: source,
|
40
|
+
tag_name: tag_name,
|
41
|
+
title: title,
|
42
|
+
to_at: to_at
|
43
|
+
)
|
44
|
+
total = Mihari::Alert.count(
|
45
|
+
artifact_data: artifact_data,
|
46
|
+
description: description,
|
47
|
+
from_at: from_at,
|
48
|
+
source: source,
|
49
|
+
tag_name: tag_name,
|
50
|
+
title: title,
|
51
|
+
to_at: to_at
|
52
|
+
)
|
53
|
+
|
54
|
+
json({ alerts: alerts, total: total, current_page: page, page_size: limit })
|
55
|
+
end
|
56
|
+
|
57
|
+
delete "/api/alerts/:id" do
|
58
|
+
id = params["id"]
|
59
|
+
id = id.to_i
|
60
|
+
|
61
|
+
begin
|
62
|
+
alert = Mihari::Alert.find(id)
|
63
|
+
alert.destroy
|
64
|
+
|
65
|
+
status 204
|
66
|
+
body ""
|
67
|
+
rescue ActiveRecord::RecordNotFound
|
68
|
+
status 404
|
69
|
+
|
70
|
+
json({ message: "ID:#{id} is not found" })
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Controllers
|
5
|
+
class ArtifactsController < BaseController
|
6
|
+
delete "/api/artifacts/:id" do
|
7
|
+
id = params["id"]
|
8
|
+
id = id.to_i
|
9
|
+
|
10
|
+
begin
|
11
|
+
alert = Mihari::Artifact.find(id)
|
12
|
+
alert.delete
|
13
|
+
|
14
|
+
status 204
|
15
|
+
body ""
|
16
|
+
rescue ActiveRecord::RecordNotFound
|
17
|
+
status 404
|
18
|
+
|
19
|
+
json({ message: "ID:#{id} is not found" })
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rack/contrib/json_body_parser"
|
4
|
+
require "sinatra"
|
5
|
+
require "sinatra/param"
|
6
|
+
|
7
|
+
module Mihari
|
8
|
+
module Controllers
|
9
|
+
class BaseController < Sinatra::Base
|
10
|
+
helpers Sinatra::Param
|
11
|
+
|
12
|
+
use Rack::JSONBodyParser
|
13
|
+
|
14
|
+
set :show_exceptions, false
|
15
|
+
set :raise_sinatra_param_exceptions, true
|
16
|
+
|
17
|
+
error Sinatra::Param::InvalidParameterError do
|
18
|
+
json({ error: "#{env['sinatra.error'].param} is invalid" })
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "safe_shell"
|
4
|
+
|
5
|
+
module Mihari
|
6
|
+
module Controllers
|
7
|
+
class CommandController < BaseController
|
8
|
+
post "/api/command" do
|
9
|
+
param :command, String, required: true
|
10
|
+
|
11
|
+
command = params["command"]
|
12
|
+
if command.nil?
|
13
|
+
status 400
|
14
|
+
return json({ message: "command is required" })
|
15
|
+
end
|
16
|
+
|
17
|
+
command = command.split
|
18
|
+
|
19
|
+
output = SafeShell.execute("mihari", *command)
|
20
|
+
success = $?.success?
|
21
|
+
|
22
|
+
json({ output: output, success: success })
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Controllers
|
5
|
+
class TagsController < BaseController
|
6
|
+
get "/api/tags" do
|
7
|
+
tags = Mihari::Tag.distinct.pluck(:name)
|
8
|
+
json tags
|
9
|
+
end
|
10
|
+
|
11
|
+
delete "/api/tags/:name" do
|
12
|
+
name = params["name"]
|
13
|
+
|
14
|
+
begin
|
15
|
+
Mihari::Tag.where(name: name).destroy_all
|
16
|
+
|
17
|
+
status 204
|
18
|
+
body ""
|
19
|
+
rescue ActiveRecord::RecordNotFound
|
20
|
+
status 404
|
21
|
+
|
22
|
+
message = { message: "Name:#{name} is not found" }
|
23
|
+
json message
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|