mihari 1.4.1 → 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/.github/ISSUE_TEMPLATE/bug_report.md +43 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +15 -0
- data/.github/workflows/test.yml +68 -0
- data/.rubocop.yml +6 -0
- data/.standard.yml +4 -0
- data/README.md +24 -270
- data/Rakefile +1 -0
- data/bin/console +1 -0
- data/build_frontend.sh +14 -0
- data/docker/Dockerfile +5 -3
- data/examples/ipinfo_hosted_domains.rb +1 -1
- data/{screenshots → images}/alert.png +0 -0
- data/images/logo.png +0 -0
- data/{screenshots → images}/misp.png +0 -0
- data/{screenshots/eyecatch.png → images/overview.png} +0 -0
- data/{screenshots → images}/slack.png +0 -0
- data/images/web_alerts.png +0 -0
- data/images/web_config.png +0 -0
- data/lib/mihari.rb +2 -2
- data/lib/mihari/analyzers/base.rb +10 -1
- data/lib/mihari/analyzers/basic.rb +3 -4
- data/lib/mihari/analyzers/binaryedge.rb +4 -7
- data/lib/mihari/analyzers/censys.rb +3 -7
- data/lib/mihari/analyzers/circl.rb +6 -8
- data/lib/mihari/analyzers/crtsh.rb +2 -6
- data/lib/mihari/analyzers/dnpedia.rb +3 -6
- data/lib/mihari/analyzers/dnstwister.rb +4 -9
- data/lib/mihari/analyzers/free_text.rb +2 -6
- data/lib/mihari/analyzers/http_hash.rb +3 -11
- data/lib/mihari/analyzers/onyphe.rb +5 -8
- data/lib/mihari/analyzers/otx.rb +4 -9
- data/lib/mihari/analyzers/passive_dns.rb +4 -9
- data/lib/mihari/analyzers/passive_ssl.rb +4 -9
- data/lib/mihari/analyzers/passivetotal.rb +9 -14
- data/lib/mihari/analyzers/pulsedive.rb +7 -12
- data/lib/mihari/analyzers/reverse_whois.rb +4 -9
- data/lib/mihari/analyzers/securitytrails.rb +12 -17
- data/lib/mihari/analyzers/securitytrails_domain_feed.rb +3 -7
- data/lib/mihari/analyzers/shodan.rb +9 -8
- data/lib/mihari/analyzers/spyse.rb +6 -11
- data/lib/mihari/analyzers/ssh_fingerprint.rb +2 -6
- data/lib/mihari/analyzers/urlscan.rb +4 -12
- data/lib/mihari/analyzers/virustotal.rb +6 -11
- data/lib/mihari/analyzers/zoomeye.rb +7 -11
- data/lib/mihari/cli.rb +70 -300
- data/lib/mihari/commands/binaryedge.rb +21 -0
- data/lib/mihari/commands/censys.rb +22 -0
- data/lib/mihari/commands/circl.rb +21 -0
- data/lib/mihari/commands/config.rb +27 -0
- data/lib/mihari/commands/crtsh.rb +22 -0
- data/lib/mihari/commands/dnpedia.rb +21 -0
- data/lib/mihari/commands/dnstwister.rb +21 -0
- data/lib/mihari/commands/free_text.rb +21 -0
- data/lib/mihari/commands/http_hash.rb +25 -0
- data/lib/mihari/commands/json.rb +42 -0
- data/lib/mihari/commands/onyphe.rb +21 -0
- data/lib/mihari/commands/otx.rb +21 -0
- data/lib/mihari/commands/passive_dns.rb +21 -0
- data/lib/mihari/commands/passive_ssl.rb +21 -0
- data/lib/mihari/commands/passivetotal.rb +21 -0
- data/lib/mihari/commands/pulsedive.rb +21 -0
- data/lib/mihari/commands/reverse_whois.rb +21 -0
- data/lib/mihari/commands/securitytrails.rb +22 -0
- data/lib/mihari/commands/securitytrails_domain_feed.rb +23 -0
- data/lib/mihari/commands/shodan.rb +21 -0
- data/lib/mihari/commands/spyse.rb +22 -0
- data/lib/mihari/commands/ssh_fingerprint.rb +21 -0
- data/lib/mihari/commands/urlscan.rb +25 -0
- data/lib/mihari/commands/virustotal.rb +21 -0
- data/lib/mihari/commands/web.rb +22 -0
- data/lib/mihari/commands/zoomeye.rb +22 -0
- data/lib/mihari/config.rb +13 -25
- data/lib/mihari/configurable.rb +4 -5
- data/lib/mihari/database.rb +7 -1
- data/lib/mihari/emitters/misp.rb +4 -2
- data/lib/mihari/emitters/slack.rb +18 -7
- data/lib/mihari/emitters/the_hive.rb +1 -1
- data/lib/mihari/errors.rb +2 -0
- data/lib/mihari/models/alert.rb +51 -0
- data/lib/mihari/models/artifact.rb +14 -3
- data/lib/mihari/notifiers/exception_notifier.rb +1 -1
- data/lib/mihari/serializers/alert.rb +1 -1
- data/lib/mihari/serializers/artifact.rb +1 -1
- data/lib/mihari/serializers/tag.rb +1 -1
- data/lib/mihari/status.rb +6 -14
- data/lib/mihari/type_checker.rb +4 -4
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/app.rb +49 -0
- 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 +21 -0
- data/lib/mihari/web/public/redoc-static.html +519 -0
- data/lib/mihari/web/public/static/favicon.ico +0 -0
- data/lib/mihari/web/public/static/fonts/fa-brands-400.099a9556.woff +0 -0
- data/lib/mihari/web/public/static/fonts/fa-brands-400.30cc681d.eot +0 -0
- data/lib/mihari/web/public/static/fonts/fa-brands-400.3b89dd10.ttf +0 -0
- data/lib/mihari/web/public/static/fonts/fa-brands-400.f7307680.woff2 +0 -0
- data/lib/mihari/web/public/static/fonts/fa-regular-400.1f77739c.ttf +0 -0
- data/lib/mihari/web/public/static/fonts/fa-regular-400.7124eb50.woff +0 -0
- data/lib/mihari/web/public/static/fonts/fa-regular-400.7630483d.eot +0 -0
- data/lib/mihari/web/public/static/fonts/fa-regular-400.f0f82301.woff2 +0 -0
- data/lib/mihari/web/public/static/fonts/fa-solid-900.1042e8ca.eot +0 -0
- data/lib/mihari/web/public/static/fonts/fa-solid-900.605ed792.ttf +0 -0
- data/lib/mihari/web/public/static/fonts/fa-solid-900.9fe5a17c.woff +0 -0
- data/lib/mihari/web/public/static/fonts/fa-solid-900.e8a427e1.woff2 +0 -0
- data/lib/mihari/web/public/static/img/fa-brands-400.ba7ed552.svg +3717 -0
- data/lib/mihari/web/public/static/img/fa-regular-400.0bb42845.svg +801 -0
- data/lib/mihari/web/public/static/img/fa-solid-900.376c1f97.svg +5034 -0
- data/lib/mihari/web/public/static/js/app.bcc595df.js +12 -0
- data/lib/mihari/web/public/static/js/app.bcc595df.js.map +1 -0
- data/mihari.gemspec +28 -21
- metadata +217 -45
- data/.travis.yml +0 -13
- data/lib/mihari/alert_viewer.rb +0 -23
@@ -1,12 +1,15 @@
|
|
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)
|
7
10
|
return if record.data_type
|
8
11
|
|
9
|
-
record.errors
|
12
|
+
record.errors.add :data, "#{record.data} is not supported"
|
10
13
|
end
|
11
14
|
end
|
12
15
|
|
@@ -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
|
@@ -19,7 +19,7 @@ module Mihari
|
|
19
19
|
def notify(exception)
|
20
20
|
notify_to_stdout exception
|
21
21
|
|
22
|
-
clean_message = exception.message.tr(
|
22
|
+
clean_message = exception.message.tr("`", "'")
|
23
23
|
attachments = to_attachments(exception, clean_message)
|
24
24
|
notify_to_slack(text: clean_message, attachments: attachments) if @slack.valid?
|
25
25
|
end
|
@@ -4,7 +4,7 @@ require "active_model_serializers"
|
|
4
4
|
|
5
5
|
module Mihari
|
6
6
|
class AlertSerializer < ActiveModel::Serializer
|
7
|
-
attributes :title, :description, :source, :created_at
|
7
|
+
attributes :id, :title, :description, :source, :created_at
|
8
8
|
|
9
9
|
has_many :artifacts
|
10
10
|
has_many :tags, through: :taggings
|
data/lib/mihari/status.rb
CHANGED
@@ -3,9 +3,7 @@
|
|
3
3
|
module Mihari
|
4
4
|
class Status
|
5
5
|
def check
|
6
|
-
statuses
|
7
|
-
[key, convert(**value)]
|
8
|
-
end.to_h
|
6
|
+
statuses
|
9
7
|
end
|
10
8
|
|
11
9
|
def self.check
|
@@ -14,16 +12,9 @@ module Mihari
|
|
14
12
|
|
15
13
|
private
|
16
14
|
|
17
|
-
def convert(status:, message:)
|
18
|
-
{
|
19
|
-
status: status ? "OK" : "Bad",
|
20
|
-
message: message
|
21
|
-
}
|
22
|
-
end
|
23
|
-
|
24
15
|
def statuses
|
25
16
|
(Mihari.analyzers + Mihari.emitters).map do |klass|
|
26
|
-
name = klass.to_s.
|
17
|
+
name = klass.to_s.split("::").last.to_s
|
27
18
|
|
28
19
|
[name, build_status(klass)]
|
29
20
|
end.to_h.compact
|
@@ -33,10 +24,11 @@ module Mihari
|
|
33
24
|
is_analyzer = klass.ancestors.include?(Mihari::Analyzers::Base)
|
34
25
|
|
35
26
|
instance = is_analyzer ? klass.new("dummy") : klass.new
|
36
|
-
|
37
|
-
|
27
|
+
is_configured = instance.configured?
|
28
|
+
values = instance.configuration_values
|
29
|
+
type = is_analyzer ? "Analyzer" : "Emitter"
|
38
30
|
|
39
|
-
|
31
|
+
values ? { is_configured: is_configured, values: values, type: type } : nil
|
40
32
|
rescue ArgumentError => _e
|
41
33
|
nil
|
42
34
|
end
|
data/lib/mihari/type_checker.rb
CHANGED
@@ -80,22 +80,22 @@ module Mihari
|
|
80
80
|
|
81
81
|
# @return [true, false]
|
82
82
|
def md5?
|
83
|
-
data.match?
|
83
|
+
data.match?(/^[A-Fa-f0-9]{32}$/)
|
84
84
|
end
|
85
85
|
|
86
86
|
# @return [true, false]
|
87
87
|
def sha1?
|
88
|
-
data.match?
|
88
|
+
data.match?(/^[A-Fa-f0-9]{40}$/)
|
89
89
|
end
|
90
90
|
|
91
91
|
# @return [true, false]
|
92
92
|
def sha256?
|
93
|
-
data.match?
|
93
|
+
data.match?(/^[A-Fa-f0-9]{64}$/)
|
94
94
|
end
|
95
95
|
|
96
96
|
# @return [true, false]
|
97
97
|
def sha512?
|
98
|
-
data.match?
|
98
|
+
data.match?(/^[A-Fa-f0-9]{128}$/)
|
99
99
|
end
|
100
100
|
end
|
101
101
|
end
|
data/lib/mihari/version.rb
CHANGED
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "launchy"
|
4
|
+
require "rack"
|
5
|
+
require "rack/handler/puma"
|
6
|
+
require "sinatra"
|
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"
|
16
|
+
|
17
|
+
module Mihari
|
18
|
+
class App < Sinatra::Base
|
19
|
+
set :root, File.dirname(__FILE__)
|
20
|
+
set :public_folder, File.join(root, "public")
|
21
|
+
|
22
|
+
get "/" do
|
23
|
+
send_file File.join(settings.public_folder, "index.html")
|
24
|
+
end
|
25
|
+
|
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
|
32
|
+
|
33
|
+
class << self
|
34
|
+
def run!(port: 9292, host: "localhost")
|
35
|
+
url = "http://#{host}:#{port}"
|
36
|
+
|
37
|
+
Rack::Handler::Puma.run self, Port: port, Host: host do |server|
|
38
|
+
Launchy.open url
|
39
|
+
|
40
|
+
[:INT, :TERM].each do |sig|
|
41
|
+
trap(sig) do
|
42
|
+
server.shutdown
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sinatra"
|
4
|
+
|
5
|
+
module Mihari
|
6
|
+
module Controllers
|
7
|
+
class AlertsController < Sinatra::Base
|
8
|
+
get "/api/alerts" do
|
9
|
+
page = params["page"] || 1
|
10
|
+
page = page.to_i
|
11
|
+
limit = 10
|
12
|
+
|
13
|
+
artifact_data = params["artifact"]
|
14
|
+
description = params["description"]
|
15
|
+
source = params["source"]
|
16
|
+
tag_name = params["tag"]
|
17
|
+
title = params["title"]
|
18
|
+
|
19
|
+
from_at = params["from_at"] || params["fromAt"]
|
20
|
+
from_at = DateTime.parse(from_at) if from_at
|
21
|
+
to_at = params["to_at"] || params["toAt"]
|
22
|
+
to_at = DateTime.parse(to_at) if to_at
|
23
|
+
|
24
|
+
alerts = Mihari::Alert.search(
|
25
|
+
artifact_data: artifact_data,
|
26
|
+
description: description,
|
27
|
+
from_at: from_at,
|
28
|
+
limit: limit,
|
29
|
+
page: page,
|
30
|
+
source: source,
|
31
|
+
tag_name: tag_name,
|
32
|
+
title: title,
|
33
|
+
to_at: to_at
|
34
|
+
)
|
35
|
+
total = Mihari::Alert.count(
|
36
|
+
artifact_data: artifact_data,
|
37
|
+
description: description,
|
38
|
+
from_at: from_at,
|
39
|
+
source: source,
|
40
|
+
tag_name: tag_name,
|
41
|
+
title: title,
|
42
|
+
to_at: to_at
|
43
|
+
)
|
44
|
+
|
45
|
+
json({ alerts: alerts, total: total, current_page: page, page_size: limit })
|
46
|
+
end
|
47
|
+
|
48
|
+
delete "/api/alerts/:id" do
|
49
|
+
id = params["id"]
|
50
|
+
id = id.to_i
|
51
|
+
|
52
|
+
begin
|
53
|
+
alert = Mihari::Alert.find(id)
|
54
|
+
alert.destroy
|
55
|
+
|
56
|
+
status 204
|
57
|
+
body ""
|
58
|
+
rescue ActiveRecord::RecordNotFound
|
59
|
+
status 404
|
60
|
+
|
61
|
+
json({ message: "ID:#{id} is not found" })
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sinatra"
|
4
|
+
|
5
|
+
module Mihari
|
6
|
+
module Controllers
|
7
|
+
class ArtifactsController < Sinatra::Base
|
8
|
+
delete "/api/artifacts/:id" do
|
9
|
+
id = params["id"]
|
10
|
+
id = id.to_i
|
11
|
+
|
12
|
+
begin
|
13
|
+
alert = Mihari::Artifact.find(id)
|
14
|
+
alert.delete
|
15
|
+
|
16
|
+
status 204
|
17
|
+
body ""
|
18
|
+
rescue ActiveRecord::RecordNotFound
|
19
|
+
status 404
|
20
|
+
|
21
|
+
json({ message: "ID:#{id} is not found" })
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "safe_shell"
|
4
|
+
require "sinatra"
|
5
|
+
|
6
|
+
module Mihari
|
7
|
+
module Controllers
|
8
|
+
class CommandController < Sinatra::Base
|
9
|
+
post "/api/command" do
|
10
|
+
payload = JSON.parse(request.body.read)
|
11
|
+
|
12
|
+
command = payload["command"]
|
13
|
+
if command.nil?
|
14
|
+
status 400
|
15
|
+
return json({ message: "command is required" })
|
16
|
+
end
|
17
|
+
|
18
|
+
command = command.split
|
19
|
+
|
20
|
+
output = SafeShell.execute("mihari", *command)
|
21
|
+
success = $?.success?
|
22
|
+
|
23
|
+
json({ output: output, success: success })
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sinatra"
|
4
|
+
|
5
|
+
module Mihari
|
6
|
+
module Controllers
|
7
|
+
class TagsController < Sinatra::Base
|
8
|
+
get "/api/tags" do
|
9
|
+
tags = Mihari::Tag.distinct.pluck(:name)
|
10
|
+
json tags
|
11
|
+
end
|
12
|
+
|
13
|
+
delete "/api/tags/:name" do
|
14
|
+
name = params["name"]
|
15
|
+
|
16
|
+
begin
|
17
|
+
Mihari::Tag.where(name: name).destroy_all
|
18
|
+
|
19
|
+
status 204
|
20
|
+
body ""
|
21
|
+
rescue ActiveRecord::RecordNotFound
|
22
|
+
status 404
|
23
|
+
|
24
|
+
message = { message: "Name:#{name} is not found" }
|
25
|
+
json message
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "awrence"
|
2
|
+
require "multi_json"
|
3
|
+
require "sinatra/base"
|
4
|
+
|
5
|
+
module Sinatra
|
6
|
+
module JSON
|
7
|
+
class << self
|
8
|
+
def encode(object)
|
9
|
+
::MultiJson.dump(object)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def json(object, options = {})
|
14
|
+
object = object.to_camelback_keys
|
15
|
+
|
16
|
+
content_type resolve_content_type(options)
|
17
|
+
resolve_encoder_action object, resolve_encoder(options)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def resolve_content_type(options = {})
|
23
|
+
options[:content_type] || settings.json_content_type
|
24
|
+
end
|
25
|
+
|
26
|
+
def resolve_encoder(options = {})
|
27
|
+
options[:json_encoder] || settings.json_encoder
|
28
|
+
end
|
29
|
+
|
30
|
+
def resolve_encoder_action(object, encoder)
|
31
|
+
[:encode, :generate].each do |method|
|
32
|
+
return encoder.send(method, object) if encoder.respond_to? method
|
33
|
+
end
|
34
|
+
|
35
|
+
if encoder.is_a? Symbol
|
36
|
+
object.__send__(encoder)
|
37
|
+
else
|
38
|
+
fail "#{encoder} does not respond to #generate nor #encode"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
Base.set :json_encoder do
|
44
|
+
::MultiJson
|
45
|
+
end
|
46
|
+
|
47
|
+
Base.set :json_content_type, :json
|
48
|
+
|
49
|
+
# Load the JSON helpers in modular style automatically
|
50
|
+
Base.helpers JSON
|
51
|
+
end
|