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