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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e890d0871e67d6f67aaf432ca236152286e0b9035f26cc1320f150416580e59e
4
- data.tar.gz: 071d117cd29cd73f29f00c78dd42637248efc1073e43ce994393a8a7efeb75a6
3
+ metadata.gz: 142df147927aee93e6c653b2eb29cca50f4ba11606d68f1302af21780d6f0dc5
4
+ data.tar.gz: 594f762c94e361cc53cab08b39abad5e3503555b51d93add3cbce744fcbff711
5
5
  SHA512:
6
- metadata.gz: 1acbd69f2f744b9025da82b41e4c6dbeb42f29eb7563fc6a0b2c09a5c332bfcd7913cee0639540e92437672503e3770ff1051b252f79deae653bbaeafedda590
7
- data.tar.gz: 64492cabe3e7102e9614c915cf857db96cd2f1d9931174604131c2069f08d2cb1afc6136dcb4dc29c659f0e2c0d7f2b54836b8d81a0cae7389b00e37fee91b24
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.png)
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 a DB (SQLite3, PostgreSQL or MySQL) contains the artifacts or not.
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
@@ -0,0 +1,6 @@
1
+ require "./lib/mihari"
2
+
3
+ # set rack env as development
4
+ ENV["RACK_ENV"] ||= "development"
5
+
6
+ run Mihari::App
data/docker/Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- FROM ruby:3.0.1-alpine3.13
1
+ FROM ruby:3.0.2-alpine3.13
2
2
 
3
3
  RUN apk --no-cache add git build-base ruby-dev sqlite-dev postgresql-dev mysql-client mysql-dev \
4
4
  && gem install pg mysql2 \
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
 
@@ -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
@@ -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 rule
16
+ rule = validate_rule(rule)
17
17
 
18
- analyzer = build_rule_analyzer(**rule)
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 = options["ignore_old_artifacts"] || false
21
- ignore_threshold = options["ignore_threshold"] || 0
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
- source: source
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
  #
@@ -13,6 +13,10 @@ module Mihari
13
13
  host = options["host"] || "localhost"
14
14
 
15
15
  load_configuration
16
+
17
+ # set rack env as production
18
+ ENV["RACK_ENV"] ||= "production"
19
+
16
20
  Mihari::App.run!(port: port, host: host)
17
21
  end
18
22
  end
@@ -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
@@ -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
@@ -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: .. # String (optional)
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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mihari
4
- VERSION = "3.0.1"
4
+ VERSION = "3.4.0"
5
5
  end
@@ -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