mihari 0.17.5 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.rubocop.yml +155 -0
- data/.travis.yml +1 -0
- data/Gemfile +2 -0
- data/README.md +30 -72
- data/config/pre_commit.yml +3 -0
- data/lib/mihari.rb +12 -8
- data/lib/mihari/alert_viewer.rb +6 -28
- data/lib/mihari/analyzers/base.rb +7 -19
- data/lib/mihari/analyzers/basic.rb +3 -1
- data/lib/mihari/analyzers/binaryedge.rb +2 -2
- data/lib/mihari/analyzers/censys.rb +2 -2
- data/lib/mihari/analyzers/circl.rb +2 -2
- data/lib/mihari/analyzers/onyphe.rb +3 -3
- data/lib/mihari/analyzers/passivetotal.rb +2 -2
- data/lib/mihari/analyzers/pulsedive.rb +2 -2
- data/lib/mihari/analyzers/securitytrails.rb +2 -2
- data/lib/mihari/analyzers/securitytrails_domain_feed.rb +2 -2
- data/lib/mihari/analyzers/shodan.rb +2 -2
- data/lib/mihari/analyzers/virustotal.rb +2 -2
- data/lib/mihari/analyzers/zoomeye.rb +2 -2
- data/lib/mihari/cli.rb +2 -2
- data/lib/mihari/config.rb +68 -2
- data/lib/mihari/configurable.rb +1 -1
- data/lib/mihari/database.rb +45 -0
- data/lib/mihari/emitters/base.rb +1 -1
- data/lib/mihari/emitters/misp.rb +8 -1
- data/lib/mihari/emitters/slack.rb +2 -2
- data/lib/mihari/emitters/sqlite.rb +29 -0
- data/lib/mihari/emitters/stdout.rb +2 -1
- data/lib/mihari/emitters/the_hive.rb +28 -14
- data/lib/mihari/models/alert.rb +11 -0
- data/lib/mihari/models/artifact.rb +27 -0
- data/lib/mihari/models/tag.rb +10 -0
- data/lib/mihari/models/tagging.rb +10 -0
- data/lib/mihari/notifiers/slack.rb +4 -4
- data/lib/mihari/serializers/alert.rb +12 -0
- data/lib/mihari/serializers/artifact.rb +9 -0
- data/lib/mihari/serializers/tag.rb +9 -0
- data/lib/mihari/status.rb +1 -1
- data/lib/mihari/type_checker.rb +1 -1
- data/lib/mihari/version.rb +1 -1
- data/mihari.gemspec +11 -5
- metadata +120 -31
- data/lib/mihari/artifact.rb +0 -36
- data/lib/mihari/cache.rb +0 -35
- data/lib/mihari/the_hive.rb +0 -42
- data/lib/mihari/the_hive/alert.rb +0 -25
- data/lib/mihari/the_hive/artifact.rb +0 -33
- data/lib/mihari/the_hive/base.rb +0 -14
@@ -6,12 +6,14 @@ module Mihari
|
|
6
6
|
attr_accessor :title
|
7
7
|
attr_reader :description
|
8
8
|
attr_reader :artifacts
|
9
|
+
attr_reader :source
|
9
10
|
attr_reader :tags
|
10
11
|
|
11
|
-
def initialize(title:, description:, artifacts:, tags: [])
|
12
|
+
def initialize(title:, description:, artifacts:, source:, tags: [])
|
12
13
|
@title = title
|
13
14
|
@description = description
|
14
15
|
@artifacts = artifacts
|
16
|
+
@source = source
|
15
17
|
@tags = tags
|
16
18
|
end
|
17
19
|
end
|
@@ -52,11 +52,11 @@ module Mihari
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def config_keys
|
55
|
-
|
55
|
+
[Mihari.config.binaryedge_api_key]
|
56
56
|
end
|
57
57
|
|
58
58
|
def api
|
59
|
-
@api ||= ::BinaryEdge::API.new
|
59
|
+
@api ||= ::BinaryEdge::API.new(Mihari.config.binaryedge_api_key)
|
60
60
|
end
|
61
61
|
end
|
62
62
|
end
|
@@ -86,11 +86,11 @@ module Mihari
|
|
86
86
|
end
|
87
87
|
|
88
88
|
def config_keys
|
89
|
-
|
89
|
+
[Mihari.config.censys_id, Mihari.config.censys_secret]
|
90
90
|
end
|
91
91
|
|
92
92
|
def api
|
93
|
-
@api ||= ::Censys::API.new
|
93
|
+
@api ||= ::Censys::API.new(Mihari.config.censys_id, Mihari.config.censys_secret)
|
94
94
|
end
|
95
95
|
end
|
96
96
|
end
|
@@ -27,11 +27,11 @@ module Mihari
|
|
27
27
|
private
|
28
28
|
|
29
29
|
def config_keys
|
30
|
-
|
30
|
+
[Mihari.config.circl_passive_password, Mihari.config.circl_passive_username]
|
31
31
|
end
|
32
32
|
|
33
33
|
def api
|
34
|
-
@api ||= ::PassiveCIRCL::API.new
|
34
|
+
@api ||= ::PassiveCIRCL::API.new(username: Mihari.config.circl_passive_username, password: Mihari.config.circl_passive_password)
|
35
35
|
end
|
36
36
|
|
37
37
|
def lookup
|
@@ -35,15 +35,15 @@ module Mihari
|
|
35
35
|
PAGE_SIZE = 10
|
36
36
|
|
37
37
|
def config_keys
|
38
|
-
|
38
|
+
[Mihari.config.onyphe_api_key]
|
39
39
|
end
|
40
40
|
|
41
41
|
def api
|
42
|
-
@api ||= ::Onyphe::API.new
|
42
|
+
@api ||= ::Onyphe::API.new(Mihari.config.onyphe_api_key)
|
43
43
|
end
|
44
44
|
|
45
45
|
def search_with_page(query, page: 1)
|
46
|
-
api.datascan(query, page: page)
|
46
|
+
api.simple.datascan(query, page: page)
|
47
47
|
end
|
48
48
|
|
49
49
|
def search
|
@@ -30,11 +30,11 @@ module Mihari
|
|
30
30
|
private
|
31
31
|
|
32
32
|
def config_keys
|
33
|
-
|
33
|
+
[Mihari.config.passivetotal_username, Mihari.config.passivetotal_api_key]
|
34
34
|
end
|
35
35
|
|
36
36
|
def api
|
37
|
-
@api ||= ::PassiveTotal::API.new
|
37
|
+
@api ||= ::PassiveTotal::API.new(username: Mihari.config.passivetotal_username, api_key: Mihari.config.passivetotal_api_key)
|
38
38
|
end
|
39
39
|
|
40
40
|
def valid_type?
|
@@ -30,11 +30,11 @@ module Mihari
|
|
30
30
|
private
|
31
31
|
|
32
32
|
def config_keys
|
33
|
-
|
33
|
+
[Mihari.config.pulsedive_api_key]
|
34
34
|
end
|
35
35
|
|
36
36
|
def api
|
37
|
-
@api ||= ::Pulsedive::API.new
|
37
|
+
@api ||= ::Pulsedive::API.new(Mihari.config.pulsedive_api_key)
|
38
38
|
end
|
39
39
|
|
40
40
|
def valid_type?
|
@@ -30,11 +30,11 @@ module Mihari
|
|
30
30
|
private
|
31
31
|
|
32
32
|
def config_keys
|
33
|
-
|
33
|
+
[Mihari.config.securitytrails_api_key]
|
34
34
|
end
|
35
35
|
|
36
36
|
def api
|
37
|
-
@api ||= ::SecurityTrails::API.new
|
37
|
+
@api ||= ::SecurityTrails::API.new(Mihari.config.securitytrails_api_key)
|
38
38
|
end
|
39
39
|
|
40
40
|
def valid_type?
|
@@ -32,11 +32,11 @@ module Mihari
|
|
32
32
|
private
|
33
33
|
|
34
34
|
def config_keys
|
35
|
-
|
35
|
+
[Mihari.config.securitytrails_api_key]
|
36
36
|
end
|
37
37
|
|
38
38
|
def api
|
39
|
-
@api ||= ::SecurityTrails::API.new
|
39
|
+
@api ||= ::SecurityTrails::API.new(Mihari.config.securitytrails_api_key)
|
40
40
|
end
|
41
41
|
|
42
42
|
def valid_type?
|
@@ -36,11 +36,11 @@ module Mihari
|
|
36
36
|
PAGE_SIZE = 100
|
37
37
|
|
38
38
|
def config_keys
|
39
|
-
|
39
|
+
[Mihari.config.shodan_api_key]
|
40
40
|
end
|
41
41
|
|
42
42
|
def api
|
43
|
-
@api ||= ::Shodan::API.new
|
43
|
+
@api ||= ::Shodan::API.new(key: Mihari.config.shodan_api_key)
|
44
44
|
end
|
45
45
|
|
46
46
|
def search_with_page(query, page: 1)
|
@@ -30,11 +30,11 @@ module Mihari
|
|
30
30
|
private
|
31
31
|
|
32
32
|
def config_keys
|
33
|
-
|
33
|
+
[Mihari.config.virustotal_api_key]
|
34
34
|
end
|
35
35
|
|
36
36
|
def api
|
37
|
-
@api = ::VirusTotal::API.new
|
37
|
+
@api = ::VirusTotal::API.new(key: Mihari.config.virustotal_api_key)
|
38
38
|
end
|
39
39
|
|
40
40
|
def valid_type?
|
@@ -41,11 +41,11 @@ module Mihari
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def config_keys
|
44
|
-
|
44
|
+
[Mihari.config.zoomeye_password, Mihari.config.zoomeye_username]
|
45
45
|
end
|
46
46
|
|
47
47
|
def api
|
48
|
-
@api ||= ::ZoomEye::API.new
|
48
|
+
@api ||= ::ZoomEye::API.new(username: Mihari.config.zoomeye_username, password: Mihari.config.zoomeye_password)
|
49
49
|
end
|
50
50
|
|
51
51
|
def convert_responses(responses)
|
data/lib/mihari/cli.rb
CHANGED
@@ -242,7 +242,7 @@ module Mihari
|
|
242
242
|
artifacts = json.dig("artifacts")
|
243
243
|
tags = json.dig("tags") || []
|
244
244
|
|
245
|
-
basic = Analyzers::Basic.new(title: title, description: description, artifacts: artifacts, tags: tags)
|
245
|
+
basic = Analyzers::Basic.new(title: title, description: description, artifacts: artifacts, source: "json", tags: tags)
|
246
246
|
basic.run
|
247
247
|
end
|
248
248
|
end
|
@@ -286,7 +286,7 @@ module Mihari
|
|
286
286
|
|
287
287
|
def load_configuration
|
288
288
|
config = options["config"]
|
289
|
-
Config.
|
289
|
+
Config.load_configuration(config) if config
|
290
290
|
end
|
291
291
|
|
292
292
|
def run_analyzer(analyzer_class, query:, options:)
|
data/lib/mihari/config.rb
CHANGED
@@ -4,6 +4,58 @@ require "yaml"
|
|
4
4
|
|
5
5
|
module Mihari
|
6
6
|
class Config
|
7
|
+
attr_accessor :binaryedge_api_key
|
8
|
+
attr_accessor :censys_id
|
9
|
+
attr_accessor :censys_secret
|
10
|
+
attr_accessor :circl_passive_password
|
11
|
+
attr_accessor :circl_passive_username
|
12
|
+
attr_accessor :misp_api_endpoint
|
13
|
+
attr_accessor :misp_api_key
|
14
|
+
attr_accessor :onyphe_api_key
|
15
|
+
attr_accessor :passivetotal_api_key
|
16
|
+
attr_accessor :passivetotal_username
|
17
|
+
attr_accessor :pulsedive_api_key
|
18
|
+
attr_accessor :securitytrails_api_key
|
19
|
+
attr_accessor :shodan_api_key
|
20
|
+
attr_accessor :slack_channel
|
21
|
+
attr_accessor :slack_webhook_url
|
22
|
+
attr_accessor :thehive_api_endpoint
|
23
|
+
attr_accessor :thehive_api_key
|
24
|
+
attr_accessor :virustotal_api_key
|
25
|
+
attr_accessor :zoomeye_password
|
26
|
+
attr_accessor :zoomeye_username
|
27
|
+
|
28
|
+
attr_accessor :database
|
29
|
+
|
30
|
+
def initialize
|
31
|
+
load_from_env
|
32
|
+
end
|
33
|
+
|
34
|
+
def load_from_env
|
35
|
+
@binaryedge_api_key = ENV["BINARYEDGE_API_KEY"]
|
36
|
+
@censys_id = ENV["CENSYS_ID"]
|
37
|
+
@censys_secret = ENV["CENSYS_SECRET"]
|
38
|
+
@circl_passive_password = ENV["CIRCL_PASSIVE_PASSWORD"]
|
39
|
+
@circl_passive_username = ENV["CIRCL_PASSIVE_USERNAME"]
|
40
|
+
@misp_api_endpoint = ENV["MISP_API_ENDPOINT"]
|
41
|
+
@misp_api_key = ENV["MISP_API_KEY"]
|
42
|
+
@onyphe_api_key = ENV["ONYPHE_API_KEY"]
|
43
|
+
@passivetotal_api_key = ENV["PASSIVETOTAL_API_KEY"]
|
44
|
+
@passivetotal_username = ENV["PASSIVETOTAL_USERNAME"]
|
45
|
+
@pulsedive_api_key = ENV["PULSEDIVE_API_KEY"]
|
46
|
+
@securitytrails_api_key = ENV["SECURITYTRAILS_API_KEY"]
|
47
|
+
@shodan_api_key = ENV["SHODAN_API_KEY"]
|
48
|
+
@slack_channel = ENV["SLACK_CHANNEL"]
|
49
|
+
@slack_webhook_url = ENV["SLACK_WEBHOOK_URL"]
|
50
|
+
@thehive_api_endpoint = ENV["THEHIVE_API_ENDPOINT"]
|
51
|
+
@thehive_api_key = ENV["THEHIVE_API_KEY"]
|
52
|
+
@virustotal_api_key = ENV["VIRUSTOTAL_API_KEY"]
|
53
|
+
@zoomeye_password = ENV["ZOOMEYE_PASSWORD"]
|
54
|
+
@zoomeye_username = ENV["ZOOMEYE_USERNAME"]
|
55
|
+
|
56
|
+
@database = ENV["DATABASE"] || "mihari.db"
|
57
|
+
end
|
58
|
+
|
7
59
|
class << self
|
8
60
|
def load_from_yaml(path)
|
9
61
|
raise ArgumentError, "#{path} does not exist." unless File.exist?(path)
|
@@ -15,10 +67,24 @@ module Mihari
|
|
15
67
|
return
|
16
68
|
end
|
17
69
|
|
18
|
-
|
19
|
-
|
70
|
+
Mihari.configure do |config|
|
71
|
+
yaml.each do |key, value|
|
72
|
+
config.send("#{key.downcase}=".to_sym, value)
|
73
|
+
end
|
20
74
|
end
|
21
75
|
end
|
22
76
|
end
|
23
77
|
end
|
78
|
+
|
79
|
+
class << self
|
80
|
+
def config
|
81
|
+
@config ||= Config.new
|
82
|
+
end
|
83
|
+
|
84
|
+
attr_writer :config
|
85
|
+
|
86
|
+
def configure
|
87
|
+
yield config
|
88
|
+
end
|
89
|
+
end
|
24
90
|
end
|
data/lib/mihari/configurable.rb
CHANGED
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record"
|
4
|
+
|
5
|
+
ActiveRecord::Base.establish_connection(
|
6
|
+
adapter: "sqlite3",
|
7
|
+
database: Mihari.config.database
|
8
|
+
)
|
9
|
+
|
10
|
+
class InitialSchema < ActiveRecord::Migration[6.0]
|
11
|
+
def change
|
12
|
+
create_table :tags, if_not_exists: true do |t|
|
13
|
+
t.string :name, null: false
|
14
|
+
end
|
15
|
+
|
16
|
+
create_table :alerts, if_not_exists: true do |t|
|
17
|
+
t.string :title, null: false
|
18
|
+
t.string :description, null: true
|
19
|
+
t.string :source, null: false
|
20
|
+
t.timestamps
|
21
|
+
end
|
22
|
+
|
23
|
+
create_table :artifacts, if_not_exists: true do |t|
|
24
|
+
t.string :data, null: false
|
25
|
+
t.string :data_type, null: false
|
26
|
+
t.belongs_to :alert, foreign_key: true
|
27
|
+
t.timestamps
|
28
|
+
end
|
29
|
+
|
30
|
+
create_table :taggings, if_not_exists: true do |t|
|
31
|
+
t.integer :tag_id
|
32
|
+
t.integer :alert_id
|
33
|
+
end
|
34
|
+
|
35
|
+
add_index :taggings, :tag_id, if_not_exists: true
|
36
|
+
add_index :taggings, [:tag_id, :alert_id], unique: true, if_not_exists: true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
begin
|
41
|
+
ActiveRecord::Migration.verbose = false
|
42
|
+
InitialSchema.migrate(:up)
|
43
|
+
rescue StandardError
|
44
|
+
# Do nothing
|
45
|
+
end
|
data/lib/mihari/emitters/base.rb
CHANGED
data/lib/mihari/emitters/misp.rb
CHANGED
@@ -6,6 +6,13 @@ require "net/ping"
|
|
6
6
|
module Mihari
|
7
7
|
module Emitters
|
8
8
|
class MISP < Base
|
9
|
+
def initialize
|
10
|
+
::MISP.configure do |config|
|
11
|
+
config.api_endpoint = Mihari.config.misp_api_endpoint
|
12
|
+
config.api_key = Mihari.config.misp_api_key
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
9
16
|
# @return [true, false]
|
10
17
|
def valid?
|
11
18
|
api_endpoint? && api_key? && ping?
|
@@ -28,7 +35,7 @@ module Mihari
|
|
28
35
|
private
|
29
36
|
|
30
37
|
def config_keys
|
31
|
-
|
38
|
+
[Mihari.config.misp_api_endpoint, Mihari.config.misp_api_key]
|
32
39
|
end
|
33
40
|
|
34
41
|
def build_attribute(artifact)
|
@@ -123,7 +123,7 @@ module Mihari
|
|
123
123
|
].join("\n")
|
124
124
|
end
|
125
125
|
|
126
|
-
def emit(title:, description:, artifacts:, tags: [])
|
126
|
+
def emit(title:, description:, artifacts:, tags: [], **_options)
|
127
127
|
return if artifacts.empty?
|
128
128
|
|
129
129
|
attachments = to_attachments(artifacts)
|
@@ -135,7 +135,7 @@ module Mihari
|
|
135
135
|
private
|
136
136
|
|
137
137
|
def config_keys
|
138
|
-
|
138
|
+
[Mihari.config.slack_webhook_url]
|
139
139
|
end
|
140
140
|
end
|
141
141
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Emitters
|
5
|
+
class SQLite < Base
|
6
|
+
def valid?
|
7
|
+
true
|
8
|
+
end
|
9
|
+
|
10
|
+
def emit(title:, description:, artifacts:, source:, tags: [])
|
11
|
+
return if artifacts.empty?
|
12
|
+
|
13
|
+
tags = tags.map { |name| Tag.find_or_create_by(name: name) }.compact.uniq
|
14
|
+
taggings = tags.map { |tag| Tagging.new(tag_id: tag.id) }
|
15
|
+
|
16
|
+
alert = Alert.new(
|
17
|
+
title: title,
|
18
|
+
description: description,
|
19
|
+
artifacts: artifacts,
|
20
|
+
source: source,
|
21
|
+
taggings: taggings
|
22
|
+
)
|
23
|
+
|
24
|
+
alert.save
|
25
|
+
alert
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -9,11 +9,12 @@ module Mihari
|
|
9
9
|
true
|
10
10
|
end
|
11
11
|
|
12
|
-
def emit(title:, description:, artifacts:, tags:)
|
12
|
+
def emit(title:, description:, artifacts:, source:, tags:)
|
13
13
|
h = {
|
14
14
|
title: title,
|
15
15
|
description: description,
|
16
16
|
artifacts: artifacts.map(&:data),
|
17
|
+
source: source,
|
17
18
|
tags: tags
|
18
19
|
}
|
19
20
|
puts JSON.pretty_generate(h)
|
@@ -1,42 +1,56 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "hachi"
|
4
|
+
require "net/ping"
|
5
|
+
|
3
6
|
module Mihari
|
4
7
|
module Emitters
|
5
8
|
class TheHive < Base
|
6
9
|
# @return [true, false]
|
7
10
|
def valid?
|
8
|
-
|
11
|
+
api_endpont? && api_key? && ping?
|
9
12
|
end
|
10
13
|
|
11
|
-
def emit(title:, description:, artifacts:, tags: [])
|
14
|
+
def emit(title:, description:, artifacts:, tags: [], **_options)
|
12
15
|
return if artifacts.empty?
|
13
16
|
|
14
|
-
|
17
|
+
api.alert.create(
|
15
18
|
title: title,
|
16
19
|
description: description,
|
17
|
-
artifacts: artifacts.map
|
18
|
-
tags: tags
|
20
|
+
artifacts: artifacts.map { |artifact| { data: artifact.data, data_type: artifact.data_type, message: description } },
|
21
|
+
tags: tags,
|
22
|
+
type: "external",
|
23
|
+
source: "mihari"
|
19
24
|
)
|
20
|
-
|
21
|
-
save_as_cache artifacts.map(&:data)
|
22
25
|
end
|
23
26
|
|
24
27
|
private
|
25
28
|
|
26
29
|
def config_keys
|
27
|
-
|
30
|
+
[Mihari.config.thehive_api_endpoint, Mihari.config.thehive_api_key]
|
28
31
|
end
|
29
32
|
|
30
|
-
def
|
31
|
-
@
|
33
|
+
def api
|
34
|
+
@api ||= Hachi::API.new(api_endpoint: Mihari.config.thehive_api_endpoint, api_key: Mihari.config.thehive_api_key)
|
32
35
|
end
|
33
36
|
|
34
|
-
|
35
|
-
|
37
|
+
# @return [true, false]
|
38
|
+
def api_endpont?
|
39
|
+
!Mihari.config.thehive_api_endpoint.nil?
|
36
40
|
end
|
37
41
|
|
38
|
-
|
39
|
-
|
42
|
+
# @return [true, false]
|
43
|
+
def api_key?
|
44
|
+
!Mihari.config.thehive_api_key.nil?
|
45
|
+
end
|
46
|
+
|
47
|
+
def ping?
|
48
|
+
base_url = Mihari.config.thehive_api_endpoint
|
49
|
+
base_url = base_url.end_with?("/") ? base_url[0..-2] : base_url
|
50
|
+
url = "#{base_url}/index.html"
|
51
|
+
|
52
|
+
http = Net::Ping::HTTP.new(url)
|
53
|
+
http.ping?
|
40
54
|
end
|
41
55
|
end
|
42
56
|
end
|