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 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