mihari 3.0.1 → 3.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/README.md +7 -2
- data/build_frontend.sh +5 -0
- data/config.ru +6 -0
- data/docker/Dockerfile +1 -1
- data/images/overview.jpg +0 -0
- data/lib/mihari.rb +2 -0
- data/lib/mihari/analyzers/rule.rb +27 -2
- data/lib/mihari/cli/analyzer.rb +3 -0
- data/lib/mihari/cli/base.rb +0 -3
- data/lib/mihari/commands/search.rb +19 -8
- data/lib/mihari/commands/web.rb +4 -0
- data/lib/mihari/mixins/disallowed_data_value.rb +42 -0
- data/lib/mihari/mixins/rule.rb +5 -1
- data/lib/mihari/models/artifact.rb +1 -1
- data/lib/mihari/schemas/analyzer.rb +25 -0
- data/lib/mihari/schemas/rule.rb +14 -0
- data/lib/mihari/templates/rule.yml.erb +5 -1
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/app.rb +2 -0
- data/lib/mihari/web/controllers/analyzers_controller.rb +38 -0
- data/lib/mihari/web/public/index.html +1 -1
- data/lib/mihari/web/public/redoc-static.html +338 -461
- data/lib/mihari/web/public/static/js/app.365f1907.js +13 -0
- data/lib/mihari/web/public/static/js/app.365f1907.js.map +1 -0
- data/mihari.gemspec +4 -4
- metadata +17 -11
- data/images/overview.png +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 142df147927aee93e6c653b2eb29cca50f4ba11606d68f1302af21780d6f0dc5
|
4
|
+
data.tar.gz: 594f762c94e361cc53cab08b39abad5e3503555b51d93add3cbce744fcbff711
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8483d2d125e30f04bdaf74243877dd9a261511d7d857f6dba42c6ea54af5de95f63c23759d1937badfdb5ae75819b99e70270edb0fb2f466601b8eaf6912dc8e
|
7
|
+
data.tar.gz: 8ffca15aadc0bc783086dd11df9fd051933af31e6778fcd4a67ddf51af5532b452603526a0303eb7c1dfdb530636a6e91df8440210189af3dc0f2806a4e64218
|
data/README.md
CHANGED
@@ -14,11 +14,12 @@ Mihari is a framework for continuous OSINT based threat hunting.
|
|
14
14
|
|
15
15
|
## How it works
|
16
16
|
|
17
|
-

|
18
18
|
|
19
19
|
- Mihari makes a query against Shodan, Censys, VirusTotal, SecurityTrails, etc. and extracts artifacts (IP addresses, domains, URLs or hashes).
|
20
|
-
- Mihari checks whether
|
20
|
+
- Mihari checks whether the database (SQLite3, PostgreSQL or MySQL) contains the artifacts or not.
|
21
21
|
- If it doesn't contain the artifacts:
|
22
|
+
- Mihari saves artifacts in the database.
|
22
23
|
- Mihari creates an alert on TheHive.
|
23
24
|
- Mihari sends a notification to Slack.
|
24
25
|
- Mihari creates an event on MISP.
|
@@ -52,6 +53,10 @@ Mihari supports the following services by default.
|
|
52
53
|
|
53
54
|
- [Mihari Knowledge Base](https://www.notion.so/Mihari-Knowledge-Base-266994ff61204428ba6cfcebe40b0bd1)
|
54
55
|
|
56
|
+
## Presentations
|
57
|
+
|
58
|
+
- [Adversary Infrastructure Tracking with Mihari](https://ninoseki.github.io/presentations/Adversary%20Infrastructure%20Tracking%20with%20Mihari.pdf)
|
59
|
+
|
55
60
|
## License
|
56
61
|
|
57
62
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/build_frontend.sh
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
CURRENT_DIR=${PWD}
|
4
4
|
|
5
|
+
# build the frontend app
|
5
6
|
mkdir -p tmp
|
6
7
|
cd tmp
|
7
8
|
git clone https://github.com/ninoseki/mihari-frontend.git
|
@@ -11,4 +12,8 @@ npm run build
|
|
11
12
|
|
12
13
|
cp -r dist/* ${CURRENT_DIR}/lib/mihari/web/public
|
13
14
|
|
15
|
+
# replace favicon path
|
16
|
+
sed -i "" 's/href="\/favicon.ico"/href="\/static\/favicon.ico"/' ${CURRENT_DIR}/lib/mihari/web/public/index.html
|
17
|
+
|
18
|
+
# remove tmp dir
|
14
19
|
rm -rf ${CURRENT_DIR}/tmp/mihari-frontend
|
data/config.ru
ADDED
data/docker/Dockerfile
CHANGED
data/images/overview.jpg
ADDED
Binary file
|
data/lib/mihari.rb
CHANGED
@@ -9,6 +9,7 @@ require "yaml"
|
|
9
9
|
# Mixins
|
10
10
|
require "mihari/mixins/configurable"
|
11
11
|
require "mihari/mixins/configuration"
|
12
|
+
require "mihari/mixins/disallowed_data_value"
|
12
13
|
require "mihari/mixins/hash"
|
13
14
|
require "mihari/mixins/refang"
|
14
15
|
require "mihari/mixins/retriable"
|
@@ -94,6 +95,7 @@ require "mihari/type_checker"
|
|
94
95
|
require "mihari/constraints"
|
95
96
|
|
96
97
|
# Schemas
|
98
|
+
require "mihari/schemas/analyzer"
|
97
99
|
require "mihari/schemas/configuration"
|
98
100
|
require "mihari/schemas/rule"
|
99
101
|
|
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
require "uuidtools"
|
4
4
|
|
5
|
-
NIL = nil
|
6
|
-
|
7
5
|
module Mihari
|
8
6
|
module Analyzers
|
9
7
|
class Rule < Base
|
8
|
+
include Mihari::Mixins::DisallowedDataValue
|
9
|
+
|
10
10
|
option :title
|
11
11
|
option :description
|
12
12
|
option :queries
|
@@ -14,6 +14,7 @@ module Mihari
|
|
14
14
|
option :id, default: proc {}
|
15
15
|
option :tags, default: proc { [] }
|
16
16
|
option :allowed_data_types, default: proc { ALLOWED_DATA_TYPES }
|
17
|
+
option :disallowed_data_values, default: proc { [] }
|
17
18
|
|
18
19
|
attr_reader :source
|
19
20
|
|
@@ -70,12 +71,36 @@ module Mihari
|
|
70
71
|
# - Uniquefy artifacts by #uniq(&:data)
|
71
72
|
# - Reject an invalid artifact (for just in case)
|
72
73
|
# - Select artifacts with allowed data types
|
74
|
+
# - Reject artifacts with disallowed data values
|
73
75
|
#
|
74
76
|
# @return [Array<Mihari::Artifact>]
|
75
77
|
#
|
76
78
|
def normalized_artifacts
|
77
79
|
@normalized_artifacts ||= artifacts.uniq(&:data).select(&:valid?).select do |artifact|
|
78
80
|
allowed_data_types.include? artifact.data_type
|
81
|
+
end.reject do |artifact|
|
82
|
+
disallowed_data_value? artifact.data
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
# Normalized disallowed data values
|
88
|
+
#
|
89
|
+
# @return [Array<Regexp, String>]
|
90
|
+
#
|
91
|
+
def normalized_disallowed_data_values
|
92
|
+
@normalized_disallowed_data_values ||= disallowed_data_values.map { |v| normalize_disallowed_data_value v }
|
93
|
+
end
|
94
|
+
|
95
|
+
#
|
96
|
+
# Check whether a value is a disallowed data value or not
|
97
|
+
#
|
98
|
+
# @return [Boolean]
|
99
|
+
#
|
100
|
+
def disallowed_data_value?(value)
|
101
|
+
normalized_disallowed_data_values.any? do |disallowed_data_value|
|
102
|
+
return value == disallowed_data_value if disallowed_data_value.is_a?(String)
|
103
|
+
return disallowed_data_value.match?(value) if disallowed_data_value.is_a?(Regexp)
|
79
104
|
end
|
80
105
|
end
|
81
106
|
|
data/lib/mihari/cli/analyzer.rb
CHANGED
@@ -22,6 +22,9 @@ require "mihari/commands/json"
|
|
22
22
|
module Mihari
|
23
23
|
module CLI
|
24
24
|
class Analyzer < Base
|
25
|
+
class_option :ignore_old_artifacts, type: :boolean, default: false, desc: "Whether to ignore old artifacts from checking or not."
|
26
|
+
class_option :ignore_threshold, type: :numeric, default: 0, desc: "Number of days to define whether an artifact is old or not."
|
27
|
+
|
25
28
|
include Mihari::Commands::BinaryEdge
|
26
29
|
include Mihari::Commands::Censys
|
27
30
|
include Mihari::Commands::CIRCL
|
data/lib/mihari/cli/base.rb
CHANGED
@@ -14,9 +14,6 @@ module Mihari
|
|
14
14
|
|
15
15
|
class_option :config, type: :string, desc: "Path to the config file"
|
16
16
|
|
17
|
-
class_option :ignore_old_artifacts, type: :boolean, default: false, desc: "Whether to ignore old artifacts from checking or not. Only affects with analyze commands."
|
18
|
-
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."
|
19
|
-
|
20
17
|
class << self
|
21
18
|
def exit_on_failure?
|
22
19
|
true
|
@@ -13,12 +13,21 @@ module Mihari
|
|
13
13
|
rule = load_rule(rule)
|
14
14
|
|
15
15
|
# validate rule schema
|
16
|
-
validate_rule
|
16
|
+
rule = validate_rule(rule)
|
17
17
|
|
18
|
-
analyzer = build_rule_analyzer(
|
18
|
+
analyzer = build_rule_analyzer(
|
19
|
+
title: rule[:title],
|
20
|
+
description: rule[:description],
|
21
|
+
queries: rule[:queries],
|
22
|
+
tags: rule[:tags],
|
23
|
+
allowed_data_types: rule[:allowed_data_types],
|
24
|
+
disallowed_data_values: rule[:disallowed_data_values],
|
25
|
+
source: rule[:source],
|
26
|
+
id: rule[:id]
|
27
|
+
)
|
19
28
|
|
20
|
-
ignore_old_artifacts =
|
21
|
-
ignore_threshold =
|
29
|
+
ignore_old_artifacts = rule[:ignore_old_artifacts]
|
30
|
+
ignore_threshold = rule[:ignore_threshold]
|
22
31
|
|
23
32
|
with_error_handling do
|
24
33
|
run_rule_analyzer analyzer, ignore_old_artifacts: ignore_old_artifacts, ignore_threshold: ignore_threshold
|
@@ -37,13 +46,15 @@ module Mihari
|
|
37
46
|
# @param [Array<Hash>] queries
|
38
47
|
# @param [Array<String>, nil] tags
|
39
48
|
# @param [Array<String>, nil] allowed_data_types
|
49
|
+
# @param [Array<String>, nil] disallowed_data_values
|
40
50
|
# @param [String, nil] source
|
41
51
|
#
|
42
52
|
# @return [Mihari::Analyzers::Rule]
|
43
53
|
#
|
44
|
-
def build_rule_analyzer(title:, description:, queries:, tags: nil, allowed_data_types: nil, source: nil)
|
54
|
+
def build_rule_analyzer(title:, description:, queries:, tags: nil, allowed_data_types: nil, disallowed_data_values: nil, source: nil, id: nil)
|
45
55
|
tags = [] if tags.nil?
|
46
56
|
allowed_data_types = ALLOWED_DATA_TYPES if allowed_data_types.nil?
|
57
|
+
disallowed_data_values = [] if disallowed_data_values.nil?
|
47
58
|
|
48
59
|
Analyzers::Rule.new(
|
49
60
|
title: title,
|
@@ -51,7 +62,9 @@ module Mihari
|
|
51
62
|
tags: tags,
|
52
63
|
queries: queries,
|
53
64
|
allowed_data_types: allowed_data_types,
|
54
|
-
|
65
|
+
disallowed_data_values: disallowed_data_values,
|
66
|
+
source: source,
|
67
|
+
id: id
|
55
68
|
)
|
56
69
|
end
|
57
70
|
|
@@ -59,8 +72,6 @@ module Mihari
|
|
59
72
|
# Run rule analyzer
|
60
73
|
#
|
61
74
|
# @param [Mihari::Analyzer::Rule] analyzer
|
62
|
-
# @param [Boolean] ignore_old_artifacts
|
63
|
-
# @param [Integer] ignore_threshold
|
64
75
|
#
|
65
76
|
# @return [nil]
|
66
77
|
#
|
data/lib/mihari/commands/web.rb
CHANGED
@@ -0,0 +1,42 @@
|
|
1
|
+
require "mem"
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Mixins
|
5
|
+
module DisallowedDataValue
|
6
|
+
include Mem
|
7
|
+
|
8
|
+
#
|
9
|
+
# Normalize a value as a disallowed data value
|
10
|
+
#
|
11
|
+
# @param [String] value Data value
|
12
|
+
#
|
13
|
+
# @return [String, Regexp] Normalized value
|
14
|
+
#
|
15
|
+
def normalize_disallowed_data_value(value)
|
16
|
+
return value if !value.start_with?("/") || !value.end_with?("/")
|
17
|
+
|
18
|
+
# if a value is surrounded by slashes, take it as a regexp
|
19
|
+
value_without_slashes = value[1..-2]
|
20
|
+
Regexp.compile value_without_slashes
|
21
|
+
end
|
22
|
+
|
23
|
+
memoize :normalize_disallowed_data_value
|
24
|
+
|
25
|
+
#
|
26
|
+
# Check whetehr a value is valid format as a disallowed data value
|
27
|
+
#
|
28
|
+
# @param [String] value Data value
|
29
|
+
#
|
30
|
+
# @return [Boolean] true if it is valid, otherwise false
|
31
|
+
#
|
32
|
+
def valid_disallowed_data_value?(value)
|
33
|
+
begin
|
34
|
+
normalize_disallowed_data_value value
|
35
|
+
rescue RegexpError
|
36
|
+
return false
|
37
|
+
end
|
38
|
+
true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/mihari/mixins/rule.rb
CHANGED
@@ -20,10 +20,12 @@ module Mihari
|
|
20
20
|
end
|
21
21
|
|
22
22
|
#
|
23
|
-
# Validate rule schema
|
23
|
+
# Validate rule schema and return a normalized rule
|
24
24
|
#
|
25
25
|
# @param [Hash] rule
|
26
26
|
#
|
27
|
+
# @return [Hash]
|
28
|
+
#
|
27
29
|
def validate_rule(rule)
|
28
30
|
error_message = "Failed to parse the input as a rule!"
|
29
31
|
|
@@ -42,6 +44,8 @@ module Mihari
|
|
42
44
|
puts error_message.colorize(:red)
|
43
45
|
raise ArgumentError, "Invalid rule schema"
|
44
46
|
end
|
47
|
+
|
48
|
+
result.to_h
|
45
49
|
end
|
46
50
|
|
47
51
|
#
|
@@ -39,7 +39,7 @@ module Mihari
|
|
39
39
|
|
40
40
|
return false unless ignore_old_artifacts
|
41
41
|
|
42
|
-
days_before = (-ignore_threshold).days.from_now
|
42
|
+
days_before = (-ignore_threshold).days.from_now.utc
|
43
43
|
# if an artifact is created before {ignore_threshold} days, ignore it
|
44
44
|
# within {ignore_threshold} days, do not ignore it
|
45
45
|
artifact.created_at < days_before
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/schema"
|
4
|
+
require "dry/validation"
|
5
|
+
|
6
|
+
require "mihari/schemas/macros"
|
7
|
+
|
8
|
+
module Mihari
|
9
|
+
module Schemas
|
10
|
+
AnalyzerRun = Dry::Schema.Params do
|
11
|
+
required(:title).value(:string)
|
12
|
+
required(:description).value(:string)
|
13
|
+
required(:source).value(:string)
|
14
|
+
required(:artifacts).value(array[:string])
|
15
|
+
|
16
|
+
optional(:tags).value(array[:string]).default([])
|
17
|
+
optional(:ignoreOldArtifacts).value(:bool).default(false)
|
18
|
+
optional(:ignoreThreshold).value(:integer).default(0)
|
19
|
+
end
|
20
|
+
|
21
|
+
class AnalyzerRunContract < Dry::Validation::Contract
|
22
|
+
params(AnalyzerRun)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/mihari/schemas/rule.rb
CHANGED
@@ -63,10 +63,24 @@ module Mihari
|
|
63
63
|
required(:queries).value(:array).each { Analyzer | Spyse | ZoomEye | Urlscan | Crtsh }
|
64
64
|
|
65
65
|
optional(:allowed_data_types).value(array[DataTypes]).default(ALLOWED_DATA_TYPES)
|
66
|
+
optional(:disallowed_data_values).value(array[:string]).default([])
|
67
|
+
|
68
|
+
optional(:ignore_old_artifacts).value(:bool).default(false)
|
69
|
+
optional(:ignore_threshold).value(:integer).default(0)
|
66
70
|
end
|
67
71
|
|
68
72
|
class RuleContract < Dry::Validation::Contract
|
73
|
+
include Mihari::Mixins::DisallowedDataValue
|
74
|
+
|
69
75
|
params(Rule)
|
76
|
+
|
77
|
+
rule(:disallowed_data_values) do
|
78
|
+
value.each do |v|
|
79
|
+
unless valid_disallowed_data_value?(v)
|
80
|
+
key.failure("#{v} is not a valid format.")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
70
84
|
end
|
71
85
|
end
|
72
86
|
end
|
@@ -2,7 +2,7 @@ title: ... # String (required)
|
|
2
2
|
description: ... # String (required)
|
3
3
|
|
4
4
|
id: ... # String (optional)
|
5
|
-
author:
|
5
|
+
author: ... # String (optional)
|
6
6
|
created_on: <%= Date.today %> # Date (optional)
|
7
7
|
updated_on: <%= Date.today %> # Date (optional)
|
8
8
|
|
@@ -13,6 +13,10 @@ allowed_data_types: # Array<String> (Optional, defaults to ["hash", "ip", "domai
|
|
13
13
|
- domain
|
14
14
|
- url
|
15
15
|
- mail
|
16
|
+
disallowed_data_values: [] # Array<String> (Optional, defaults to [])
|
17
|
+
|
18
|
+
ignore_old_artifacts: true # Whether to ignore old artifacts from checking or not (Optional, defaults to true)
|
19
|
+
ignore_threshold: 0 # Number of days to define whether an artifact is old or not (Optional, defaults to 0)
|
16
20
|
|
17
21
|
queries: # Array<Hash> (required)
|
18
22
|
- analyzer: shodan # String (required)
|
data/lib/mihari/version.rb
CHANGED
data/lib/mihari/web/app.rb
CHANGED
@@ -10,6 +10,7 @@ require "mihari/web/helpers/json"
|
|
10
10
|
require "mihari/web/controllers/base_controller"
|
11
11
|
|
12
12
|
require "mihari/web/controllers/alerts_controller"
|
13
|
+
require "mihari/web/controllers/analyzers_controller"
|
13
14
|
require "mihari/web/controllers/artifacts_controller"
|
14
15
|
require "mihari/web/controllers/command_controller"
|
15
16
|
require "mihari/web/controllers/config_controller"
|
@@ -26,6 +27,7 @@ module Mihari
|
|
26
27
|
end
|
27
28
|
|
28
29
|
use Mihari::Controllers::AlertsController
|
30
|
+
use Mihari::Controllers::AnalyzersController
|
29
31
|
use Mihari::Controllers::ArtifactsController
|
30
32
|
use Mihari::Controllers::CommandController
|
31
33
|
use Mihari::Controllers::ConfigController
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Controllers
|
5
|
+
class AnalyzersController < BaseController
|
6
|
+
post "/api/analyzer" do
|
7
|
+
contract = Mihari::Schemas::AnalyzerRunContract.new
|
8
|
+
result = contract.call(params)
|
9
|
+
|
10
|
+
unless result.errors.empty?
|
11
|
+
status 400
|
12
|
+
|
13
|
+
return json(result.errors.to_h)
|
14
|
+
end
|
15
|
+
|
16
|
+
args = result.to_h
|
17
|
+
|
18
|
+
ignore_old_artifacts = args[:ignoreOldArtifacts]
|
19
|
+
ignore_threshold = args[:ignoreThreshold]
|
20
|
+
|
21
|
+
analyzer = Mihari::Analyzers::Basic.new(
|
22
|
+
title: args[:title],
|
23
|
+
description: args[:description],
|
24
|
+
source: args[:source],
|
25
|
+
artifacts: args[:artifacts],
|
26
|
+
tags: args[:tags]
|
27
|
+
)
|
28
|
+
analyzer.ignore_old_artifacts = ignore_old_artifacts
|
29
|
+
analyzer.ignore_threshold = ignore_threshold
|
30
|
+
|
31
|
+
analyzer.run
|
32
|
+
|
33
|
+
status 201
|
34
|
+
body ""
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|