mihari 3.0.1 → 3.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
![img](https://github.com/ninoseki/mihari/raw/master/images/overview.
|
17
|
+
![img](https://github.com/ninoseki/mihari/raw/master/images/overview.jpg)
|
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
|