mihari 7.3.2 → 7.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (131) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +8 -0
  3. data/.rubocop.yml +0 -2
  4. data/.shadowenv.d/.gitignore +2 -0
  5. data/.shadowenv.d/000_unset_all.lisp +39 -0
  6. data/README.md +0 -8
  7. data/lib/mihari/analyzers/base.rb +2 -2
  8. data/lib/mihari/analyzers/binaryedge.rb +5 -5
  9. data/lib/mihari/analyzers/censys.rb +6 -6
  10. data/lib/mihari/analyzers/circl.rb +2 -2
  11. data/lib/mihari/analyzers/crtsh.rb +3 -3
  12. data/lib/mihari/analyzers/dnstwister.rb +2 -2
  13. data/lib/mihari/analyzers/feed.rb +12 -18
  14. data/lib/mihari/analyzers/fofa.rb +6 -6
  15. data/lib/mihari/analyzers/greynoise.rb +5 -5
  16. data/lib/mihari/analyzers/hunterhow.rb +4 -4
  17. data/lib/mihari/analyzers/onyphe.rb +5 -5
  18. data/lib/mihari/analyzers/otx.rb +2 -2
  19. data/lib/mihari/analyzers/passivetotal.rb +3 -3
  20. data/lib/mihari/analyzers/pulsedive.rb +3 -3
  21. data/lib/mihari/analyzers/securitytrails.rb +4 -4
  22. data/lib/mihari/analyzers/shodan.rb +5 -5
  23. data/lib/mihari/analyzers/urlscan.rb +5 -5
  24. data/lib/mihari/analyzers/virustotal.rb +4 -4
  25. data/lib/mihari/analyzers/virustotal_intelligence.rb +5 -5
  26. data/lib/mihari/analyzers/zoomeye.rb +5 -5
  27. data/lib/mihari/cli/application.rb +1 -1
  28. data/lib/mihari/clients/base.rb +7 -6
  29. data/lib/mihari/clients/binaryedge.rb +6 -6
  30. data/lib/mihari/clients/censys.rb +4 -4
  31. data/lib/mihari/clients/circl.rb +2 -2
  32. data/lib/mihari/clients/crtsh.rb +2 -2
  33. data/lib/mihari/clients/dnstwister.rb +1 -1
  34. data/lib/mihari/clients/fofa.rb +4 -4
  35. data/lib/mihari/clients/google_public_dns.rb +2 -2
  36. data/lib/mihari/clients/greynoise.rb +4 -4
  37. data/lib/mihari/clients/hunterhow.rb +10 -10
  38. data/lib/mihari/clients/misp.rb +1 -1
  39. data/lib/mihari/clients/mmdb.rb +1 -1
  40. data/lib/mihari/clients/onyphe.rb +4 -4
  41. data/lib/mihari/clients/otx.rb +1 -1
  42. data/lib/mihari/clients/passivetotal.rb +5 -5
  43. data/lib/mihari/clients/publsedive.rb +3 -3
  44. data/lib/mihari/clients/securitytrails.rb +6 -6
  45. data/lib/mihari/clients/shodan.rb +6 -6
  46. data/lib/mihari/clients/shodan_internet_db.rb +1 -1
  47. data/lib/mihari/clients/the_hive.rb +2 -2
  48. data/lib/mihari/clients/urlscan.rb +4 -4
  49. data/lib/mihari/clients/virustotal.rb +4 -4
  50. data/lib/mihari/clients/whois.rb +118 -0
  51. data/lib/mihari/clients/yeti.rb +38 -0
  52. data/lib/mihari/clients/zoomeye.rb +12 -12
  53. data/lib/mihari/commands/alert.rb +1 -1
  54. data/lib/mihari/commands/artifact.rb +1 -1
  55. data/lib/mihari/commands/rule.rb +1 -1
  56. data/lib/mihari/commands/tag.rb +1 -1
  57. data/lib/mihari/concerns/autonomous_system_normalizable.rb +1 -4
  58. data/lib/mihari/concerns/configurable.rb +1 -1
  59. data/lib/mihari/concerns/database_connectable.rb +2 -2
  60. data/lib/mihari/concerns/retriable.rb +1 -1
  61. data/lib/mihari/config.rb +14 -2
  62. data/lib/mihari/constants.rb +2 -2
  63. data/lib/mihari/data_type.rb +1 -3
  64. data/lib/mihari/emitters/base.rb +2 -2
  65. data/lib/mihari/emitters/database.rb +1 -1
  66. data/lib/mihari/emitters/misp.rb +12 -4
  67. data/lib/mihari/emitters/slack.rb +9 -9
  68. data/lib/mihari/emitters/the_hive.rb +9 -4
  69. data/lib/mihari/emitters/webhook.rb +4 -4
  70. data/lib/mihari/emitters/yeti.rb +107 -0
  71. data/lib/mihari/enrichers/base.rb +1 -1
  72. data/lib/mihari/enrichers/google_public_dns.rb +1 -1
  73. data/lib/mihari/enrichers/mmdb.rb +1 -1
  74. data/lib/mihari/enrichers/shodan.rb +3 -3
  75. data/lib/mihari/enrichers/whois.rb +6 -91
  76. data/lib/mihari/entities/alert.rb +6 -6
  77. data/lib/mihari/entities/artifact.rb +17 -17
  78. data/lib/mihari/entities/autonomous_system.rb +1 -1
  79. data/lib/mihari/entities/config.rb +8 -4
  80. data/lib/mihari/entities/cpe.rb +2 -2
  81. data/lib/mihari/entities/dns.rb +3 -3
  82. data/lib/mihari/entities/geolocation.rb +3 -3
  83. data/lib/mihari/entities/ip_address.rb +3 -3
  84. data/lib/mihari/entities/messages.rb +3 -3
  85. data/lib/mihari/entities/pagination.rb +3 -3
  86. data/lib/mihari/entities/port.rb +2 -2
  87. data/lib/mihari/entities/reverse_dns.rb +2 -2
  88. data/lib/mihari/entities/rule.rb +8 -8
  89. data/lib/mihari/entities/tag.rb +3 -3
  90. data/lib/mihari/entities/vulnerability.rb +2 -2
  91. data/lib/mihari/entities/whois.rb +7 -7
  92. data/lib/mihari/errors.rb +1 -1
  93. data/lib/mihari/models/artifact.rb +2 -2
  94. data/lib/mihari/models/port.rb +1 -1
  95. data/lib/mihari/models/tag.rb +3 -0
  96. data/lib/mihari/rule.rb +10 -14
  97. data/lib/mihari/schemas/emitter.rb +9 -0
  98. data/lib/mihari/services/feed.rb +3 -3
  99. data/lib/mihari/services/getters.rb +1 -1
  100. data/lib/mihari/services/proxies.rb +1 -1
  101. data/lib/mihari/services/renderer.rb +2 -0
  102. data/lib/mihari/services/searchers.rb +1 -1
  103. data/lib/mihari/sidekiq/application.rb +2 -2
  104. data/lib/mihari/structs/censys.rb +4 -4
  105. data/lib/mihari/structs/google_public_dns.rb +3 -3
  106. data/lib/mihari/structs/greynoise.rb +2 -2
  107. data/lib/mihari/structs/onyphe.rb +3 -3
  108. data/lib/mihari/structs/shodan.rb +10 -10
  109. data/lib/mihari/structs/urlscan.rb +1 -1
  110. data/lib/mihari/structs/virustotal_intelligence.rb +2 -2
  111. data/lib/mihari/version.rb +1 -1
  112. data/lib/mihari/web/api.rb +1 -1
  113. data/lib/mihari/web/application.rb +1 -1
  114. data/lib/mihari/web/endpoints/alerts.rb +12 -12
  115. data/lib/mihari/web/endpoints/artifacts.rb +11 -11
  116. data/lib/mihari/web/endpoints/configs.rb +7 -2
  117. data/lib/mihari/web/endpoints/ip_addresses.rb +5 -5
  118. data/lib/mihari/web/endpoints/rules.rb +26 -26
  119. data/lib/mihari/web/endpoints/tags.rb +4 -4
  120. data/lib/mihari/web/public/assets/{index-ReF8ffd-.css → index-80oZkhZG.css} +1 -1
  121. data/lib/mihari/web/public/assets/index-BNLbw8nG.js +1783 -0
  122. data/lib/mihari/web/public/index.html +2 -2
  123. data/lib/mihari/web/public/redoc-static.html +2 -2
  124. data/lib/mihari.rb +4 -1
  125. data/mihari.gemspec +19 -19
  126. data/renovate.json +1 -3
  127. data/requirements.txt +1 -1
  128. metadata +48 -44
  129. data/.standard.yml +0 -4
  130. data/lib/mihari/web/public/assets/index-lRP933ks.js +0 -1787
  131. /data/lib/mihari/web/public/assets/{mode-yaml-BC4MIiYj.js → mode-yaml-ELgwiJiP.js} +0 -0
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "whois-parser"
4
+
5
+ module Mihari
6
+ module Clients
7
+ #
8
+ # Whois client
9
+ #
10
+ class Whois
11
+ # @return [Integer, nil]
12
+ attr_reader :timeout
13
+
14
+ # @return [::Whois::Client]
15
+ attr_reader :client
16
+
17
+ #
18
+ # @param [Integer, nil] timeout
19
+ #
20
+ def initialize(timeout: nil)
21
+ @timeout = timeout
22
+
23
+ @client = lambda do
24
+ return ::Whois::Client.new if timeout.nil?
25
+
26
+ ::Whois::Client.new(timeout:)
27
+ end.call
28
+ end
29
+
30
+ #
31
+ # Query IAIA Whois API
32
+ #
33
+ # @param [Mihari::Models::Artifact] artifact
34
+ #
35
+ # @param [Object] domain
36
+ def lookup(domain)
37
+ record = client.lookup(domain)
38
+ return if record.parser.available?
39
+
40
+ Models::WhoisRecord.new(
41
+ domain:,
42
+ created_on: get_created_on(record.parser),
43
+ updated_on: get_updated_on(record.parser),
44
+ expires_on: get_expires_on(record.parser),
45
+ registrar: get_registrar(record.parser),
46
+ contacts: get_contacts(record.parser)
47
+ )
48
+ end
49
+
50
+ private
51
+
52
+ #
53
+ # Get created_on
54
+ #
55
+ # @param [::Whois::Parser] parser
56
+ #
57
+ # @return [Date, nil]
58
+ #
59
+ def get_created_on(parser)
60
+ parser.created_on
61
+ rescue ::Whois::AttributeNotImplemented
62
+ nil
63
+ end
64
+
65
+ #
66
+ # Get updated_on
67
+ #
68
+ # @param [::Whois::Parser] parser
69
+ #
70
+ # @return [Date, nil]
71
+ #
72
+ def get_updated_on(parser)
73
+ parser.updated_on
74
+ rescue ::Whois::AttributeNotImplemented
75
+ nil
76
+ end
77
+
78
+ #
79
+ # Get expires_on
80
+ #
81
+ # @param [::Whois::Parser] parser
82
+ #
83
+ # @return [Date, nil]
84
+ #
85
+ def get_expires_on(parser)
86
+ parser.expires_on
87
+ rescue ::Whois::AttributeNotImplemented
88
+ nil
89
+ end
90
+
91
+ #
92
+ # Get registrar
93
+ #
94
+ # @param [::Whois::Parser] parser
95
+ #
96
+ # @return [Hash, nil]
97
+ #
98
+ def get_registrar(parser)
99
+ parser.registrar&.to_h
100
+ rescue ::Whois::AttributeNotImplemented
101
+ nil
102
+ end
103
+
104
+ #
105
+ # Get contacts
106
+ #
107
+ # @param [::Whois::Parser] parser
108
+ #
109
+ # @return [Array<Hash>, nil]
110
+ #
111
+ def get_contacts(parser)
112
+ parser.contacts.map(&:to_h)
113
+ rescue ::Whois::AttributeNotImplemented
114
+ nil
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Clients
5
+ #
6
+ # Yeti API client
7
+ #
8
+ class Yeti < Base
9
+ #
10
+ # @param [String] base_url
11
+ # @param [String, nil] api_key
12
+ # @param [Hash] headers
13
+ # @param [Integer, nil] timeout
14
+ #
15
+ def initialize(base_url, api_key:, headers: {}, timeout: nil)
16
+ raise(ArgumentError, "api_key is required") unless api_key
17
+
18
+ headers["x-yeti-apikey"] = api_key
19
+ super(base_url, headers:, timeout:)
20
+ end
21
+
22
+ def get_token
23
+ res = post_json("/api/v2/auth/api-token")
24
+ res["access_token"]
25
+ end
26
+
27
+ #
28
+ # @param [Hash] json
29
+ #
30
+ # @return [Hash]
31
+ #
32
+ def create_observables(json)
33
+ token = get_token
34
+ post_json("/api/v2/observables/bulk", json:, headers: {authorization: "Bearer #{token}"})
35
+ end
36
+ end
37
+ end
38
+ end
@@ -27,14 +27,14 @@ module Mihari
27
27
  raise(ArgumentError, "api_key is required") unless api_key
28
28
 
29
29
  headers["api-key"] = api_key
30
- super(base_url, headers: headers, pagination_interval: pagination_interval, timeout: timeout)
30
+ super(base_url, headers:, pagination_interval:, timeout:)
31
31
  end
32
32
 
33
33
  #
34
34
  # @return [::HTTP::Client]
35
35
  #
36
36
  def http
37
- @http ||= HTTP::Factory.build(headers: headers, timeout: timeout, raise_exception: false)
37
+ @http ||= HTTP::Factory.build(headers:, timeout:, raise_exception: false)
38
38
  end
39
39
 
40
40
  #
@@ -48,11 +48,11 @@ module Mihari
48
48
  #
49
49
  def host_search(query, page: nil, facets: nil)
50
50
  params = {
51
- query: query,
52
- page: page,
53
- facets: facets
51
+ query:,
52
+ page:,
53
+ facets:
54
54
  }.compact
55
- get_json "/host/search", params: params
55
+ get_json "/host/search", params:
56
56
  end
57
57
 
58
58
  #
@@ -65,7 +65,7 @@ module Mihari
65
65
  def host_search_with_pagination(query, facets: nil, pagination_limit: Mihari.config.pagination_limit)
66
66
  Enumerator.new do |y|
67
67
  (1..pagination_limit).each do |page|
68
- res = host_search(query, facets: facets, page: page)
68
+ res = host_search(query, facets:, page:)
69
69
 
70
70
  break if res.nil?
71
71
 
@@ -90,11 +90,11 @@ module Mihari
90
90
  #
91
91
  def web_search(query, page: nil, facets: nil)
92
92
  params = {
93
- query: query,
94
- page: page,
95
- facets: facets
93
+ query:,
94
+ page:,
95
+ facets:
96
96
  }.compact
97
- get_json "/web/search", params: params
97
+ get_json "/web/search", params:
98
98
  end
99
99
 
100
100
  #
@@ -107,7 +107,7 @@ module Mihari
107
107
  def web_search_with_pagination(query, facets: nil, pagination_limit: Mihari.config.pagination_limit)
108
108
  Enumerator.new do |y|
109
109
  (1..pagination_limit).each do |page|
110
- res = web_search(query, facets: facets, page: page)
110
+ res = web_search(query, facets:, page:)
111
111
 
112
112
  break if res.nil?
113
113
 
@@ -21,7 +21,7 @@ module Mihari
21
21
  # @return [Mihari::Services::ResultValue]
22
22
  #
23
23
  def _search(q, page: 1, limit: 10)
24
- filter = Structs::Filters::Search.new(q: q, page: page, limit: limit)
24
+ filter = Structs::Filters::Search.new(q:, page:, limit:)
25
25
  Services::AlertSearcher.result(filter).value!
26
26
  end
27
27
  end
@@ -20,7 +20,7 @@ module Mihari
20
20
  # @return [Mihari::Services::ResultValue]
21
21
  #
22
22
  def _search(q, page: 1, limit: 10)
23
- filter = Structs::Filters::Search.new(q: q, page: page, limit: limit)
23
+ filter = Structs::Filters::Search.new(q:, page:, limit:)
24
24
  Services::ArtifactSearcher.result(filter).value!
25
25
  end
26
26
  end
@@ -21,7 +21,7 @@ module Mihari
21
21
  # @return [Mihari::Services::ResultValue]
22
22
  #
23
23
  def _search(q, page: 1, limit: 10)
24
- filter = Structs::Filters::Search.new(q: q, page: page, limit: limit)
24
+ filter = Structs::Filters::Search.new(q:, page:, limit:)
25
25
  Services::RuleSearcher.result(filter).value!
26
26
  end
27
27
  end
@@ -20,7 +20,7 @@ module Mihari
20
20
  # @return [Mihari::Services::ResultValue]
21
21
  #
22
22
  def _search(q, page: 1, limit: 10)
23
- filter = Structs::Filters::Search.new(q: q, page: page, limit: limit)
23
+ filter = Structs::Filters::Search.new(q:, page:, limit:)
24
24
  Services::TagSearcher.result(filter).value!
25
25
  end
26
26
  end
@@ -16,10 +16,7 @@ module Mihari
16
16
  # @return [Integer]
17
17
  #
18
18
  def normalize_asn(asn)
19
- return asn if asn.is_a?(Integer)
20
- return asn.to_i unless asn.start_with?("AS")
21
-
22
- asn.delete_prefix("AS").to_i
19
+ asn.to_s.delete_prefix("AS").to_i
23
20
  end
24
21
  end
25
22
  end
@@ -40,7 +40,7 @@ module Mihari
40
40
  configuration_keys.map do |key|
41
41
  value = Mihari.config.send(key)
42
42
  value = "REDACTED" if value && Mihari.config.hide_config_values
43
- { key: key.upcase, value: value }
43
+ {key: key.upcase, value:}
44
44
  end
45
45
  end
46
46
 
@@ -8,8 +8,8 @@ module Mihari
8
8
  module DatabaseConnectable
9
9
  extend ActiveSupport::Concern
10
10
 
11
- def with_db_connection(&block)
12
- Database.with_db_connection(&block)
11
+ def with_db_connection(&)
12
+ Database.with_db_connection(&)
13
13
  end
14
14
  end
15
15
  end
@@ -41,7 +41,7 @@ module Mihari
41
41
  begin
42
42
  try += 1
43
43
  yield
44
- rescue StandardError => e
44
+ rescue => e
45
45
  # Raise error if it's not a retriable error
46
46
  raise e unless condition.call(e)
47
47
 
data/lib/mihari/config.rb CHANGED
@@ -34,6 +34,8 @@ module Mihari
34
34
  thehive_url: nil,
35
35
  urlscan_api_key: nil,
36
36
  virustotal_api_key: nil,
37
+ yeti_api_key: nil,
38
+ yeti_url: nil,
37
39
  zoomeye_api_key: nil,
38
40
  # sidekiq
39
41
  sidekiq_redis_url: nil,
@@ -42,7 +44,8 @@ module Mihari
42
44
  ignore_error: false,
43
45
  pagination_interval: 0,
44
46
  pagination_limit: 100,
45
- parallel: false,
47
+ analyzer_parallelism: false,
48
+ emitter_parallelism: true,
46
49
  retry_exponential_backoff: true,
47
50
  retry_interval: 5,
48
51
  retry_times: 3,
@@ -122,6 +125,12 @@ module Mihari
122
125
  # @!attribute [r] virustotal_api_key
123
126
  # @return [String, nil]
124
127
 
128
+ # @!attribute [r] yeti_url
129
+ # @return [String, nil]
130
+
131
+ # @!attribute [r] yeti_api_key
132
+ # @return [String, nil]
133
+
125
134
  # @!attribute [r] zoomeye_api_key
126
135
  # @return [String, nil]
127
136
 
@@ -146,7 +155,10 @@ module Mihari
146
155
  # @!attribute [r] pagination_limit
147
156
  # @return [Integer]
148
157
 
149
- # @!attribute [r] parallel
158
+ # @!attribute [r] analyzer_parallelism
159
+ # @return [Boolean]
160
+
161
+ # @!attribute [r] emitter_parallelism
150
162
  # @return [Boolean]
151
163
 
152
164
  # @!attribute [r] ignore_error
@@ -5,8 +5,8 @@ module Mihari
5
5
  DEFAULT_DATA_TYPES = Types::DataTypes.values.freeze
6
6
 
7
7
  # @return [Array<Hash>]
8
- DEFAULT_EMITTERS = Emitters::Database.keys.map { |name| { emitter: name.downcase } }.freeze
8
+ DEFAULT_EMITTERS = Emitters::Database.keys.map { |name| {emitter: name.downcase} }.freeze
9
9
 
10
10
  # @return [Array<Hash>]
11
- DEFAULT_ENRICHERS = Mihari.enricher_to_class.keys.map { |name| { enricher: name.downcase } }.freeze
11
+ DEFAULT_ENRICHERS = Mihari.enricher_to_class.keys.map { |name| {enricher: name.downcase} }.freeze
12
12
  end
@@ -26,9 +26,7 @@ module Mihari
26
26
 
27
27
  # @return [Boolean]
28
28
  def ip?
29
- Try[IPAddr::InvalidAddressError] do
30
- IPAddr.new(data).to_s == data
31
- end.recover { false }.value!
29
+ Try[IPAddr::InvalidAddressError] { IPAddr.new(data).to_s == data }.recover { false }.value!
32
30
  end
33
31
 
34
32
  # @return [Boolean]
@@ -14,7 +14,7 @@ module Mihari
14
14
  # @param [Hash, nil] options
15
15
  #
16
16
  def initialize(rule:, options: nil)
17
- super(options: options)
17
+ super(options:)
18
18
 
19
19
  @rule = rule
20
20
  end
@@ -23,7 +23,7 @@ module Mihari
23
23
  # @return [Boolean]
24
24
  #
25
25
  def parallel?
26
- options[:parallel] || Mihari.config.parallel
26
+ options[:parallel] || Mihari.config.emitter_parallelism
27
27
  end
28
28
 
29
29
  # A target to emit the data
@@ -16,7 +16,7 @@ module Mihari
16
16
  def call(artifacts)
17
17
  return if artifacts.empty?
18
18
 
19
- alert = Models::Alert.new(artifacts: artifacts, rule_id: rule.id)
19
+ alert = Models::Alert.new(artifacts:, rule_id: rule.id)
20
20
  alert.save
21
21
  alert
22
22
  end
@@ -12,6 +12,9 @@ module Mihari
12
12
  # @return [String, nil]
13
13
  attr_reader :api_key
14
14
 
15
+ # @return [Array<String>]
16
+ attr_reader :attribute_tags
17
+
15
18
  # @return [Mihari::Rule]
16
19
  attr_reader :rule
17
20
 
@@ -24,10 +27,11 @@ module Mihari
24
27
  # @param [Hash, nil] params
25
28
  #
26
29
  def initialize(rule:, options: nil, **params)
27
- super(rule: rule, options: options)
30
+ super(rule:, options:)
28
31
 
29
32
  @url = params[:url] || Mihari.config.misp_url
30
33
  @api_key = params[:api_key] || Mihari.config.misp_api_key
34
+ @attribute_tags = params[:attribute_tags] || []
31
35
 
32
36
  @artifacts = []
33
37
  end
@@ -51,7 +55,7 @@ module Mihari
51
55
  Event: {
52
56
  info: rule.title,
53
57
  Attribute: artifacts.map { |artifact| build_attribute(artifact) },
54
- Tag: rule.tags.map { |tag| { name: tag } }
58
+ Tag: rule.tags.map { |tag| {name: tag.name} }
55
59
  }
56
60
  })
57
61
  end
@@ -66,7 +70,7 @@ module Mihari
66
70
  private
67
71
 
68
72
  def client
69
- @client ||= Clients::MISP.new(url, api_key: api_key, timeout: timeout)
73
+ @client ||= Clients::MISP.new(url, api_key:, timeout:)
70
74
  end
71
75
 
72
76
  #
@@ -77,7 +81,11 @@ module Mihari
77
81
  # @return [Hash]
78
82
  #
79
83
  def build_attribute(artifact)
80
- { value: artifact.data, type: to_misp_type(type: artifact.data_type, value: artifact.data) }
84
+ {
85
+ value: artifact.data,
86
+ type: to_misp_type(type: artifact.data_type, value: artifact.data),
87
+ Tag: attribute_tags.map { |tag| {name: tag} }
88
+ }
81
89
  end
82
90
 
83
91
  #
@@ -30,25 +30,25 @@ module Mihari
30
30
  def vt_link
31
31
  return nil unless _vt_link
32
32
 
33
- { type: "button", text: "VirusTotal", url: _vt_link }
33
+ {type: "button", text: "VirusTotal", url: _vt_link}
34
34
  end
35
35
 
36
36
  def urlscan_link
37
37
  return nil unless _urlscan_link
38
38
 
39
- { type: "button", text: "urlscan.io", url: _urlscan_link }
39
+ {type: "button", text: "urlscan.io", url: _urlscan_link}
40
40
  end
41
41
 
42
42
  def censys_link
43
43
  return nil unless _censys_link
44
44
 
45
- { type: "button", text: "Censys", url: _censys_link }
45
+ {type: "button", text: "Censys", url: _censys_link}
46
46
  end
47
47
 
48
48
  def shodan_link
49
49
  return nil unless _shodan_link
50
50
 
51
- { type: "button", text: "Shodan", url: _shodan_link }
51
+ {type: "button", text: "Shodan", url: _shodan_link}
52
52
  end
53
53
 
54
54
  # @return [Array]
@@ -57,7 +57,7 @@ module Mihari
57
57
  {
58
58
  text: defanged_data,
59
59
  fallback: "VT & urlscan.io links",
60
- actions: actions
60
+ actions:
61
61
  }
62
62
  ]
63
63
  end
@@ -140,7 +140,7 @@ module Mihari
140
140
  # @param [Hash, nil] params
141
141
  #
142
142
  def initialize(rule:, options: nil, **params)
143
- super(rule: rule, options: options)
143
+ super(rule:, options:)
144
144
 
145
145
  @webhook_url = params[:webhook_url] || Mihari.config.slack_webhook_url
146
146
  @channel = params[:channel] || Mihari.config.slack_channel || DEFAULT_CHANNEL
@@ -177,9 +177,9 @@ module Mihari
177
177
  #
178
178
  def notifier
179
179
  @notifier ||= lambda do
180
- return ::Slack::Notifier.new(webhook_url, channel: channel, username: username) if timeout.nil?
180
+ return ::Slack::Notifier.new(webhook_url, channel:, username:) if timeout.nil?
181
181
 
182
- ::Slack::Notifier.new(webhook_url, channel: channel, username: username, http_options: { timeout: timeout })
182
+ ::Slack::Notifier.new(webhook_url, channel:, username:, http_options: {timeout:})
183
183
  end.call
184
184
  end
185
185
 
@@ -215,7 +215,7 @@ module Mihari
215
215
 
216
216
  @artifacts = artifacts
217
217
 
218
- notifier.post(text: text, attachments: attachments, mrkdwn: true)
218
+ notifier.post(text:, attachments:, mrkdwn: true)
219
219
  end
220
220
  end
221
221
  end
@@ -9,6 +9,9 @@ module Mihari
9
9
  # @return [String, nil]
10
10
  attr_reader :api_key
11
11
 
12
+ # @return [Array<String>]
13
+ attr_reader :observable_tags
14
+
12
15
  # @return [Array<Mihari::Models::Artifact>]
13
16
  attr_accessor :artifacts
14
17
 
@@ -18,10 +21,11 @@ module Mihari
18
21
  # @param [Hash] params
19
22
  #
20
23
  def initialize(rule:, options: nil, **params)
21
- super(rule: rule, options: options)
24
+ super(rule:, options:)
22
25
 
23
26
  @url = params[:url] || Mihari.config.thehive_url
24
27
  @api_key = params[:api_key] || Mihari.config.thehive_api_key
28
+ @observable_tags = params[:observable_tags] || []
25
29
 
26
30
  @artifacts = []
27
31
  end
@@ -56,7 +60,7 @@ module Mihari
56
60
  private
57
61
 
58
62
  def client
59
- Clients::TheHive.new(url, api_key: api_key, api_version: "v1", timeout: timeout)
63
+ Clients::TheHive.new(url, api_key:, api_version: "v1", timeout:)
60
64
  end
61
65
 
62
66
  #
@@ -81,10 +85,11 @@ module Mihari
81
85
  {
82
86
  data: artifact.data,
83
87
  data_type: artifact.data_type,
84
- message: rule.description
88
+ message: rule.description,
89
+ tags: observable_tags
85
90
  }
86
91
  end,
87
- tags: rule.tags,
92
+ tags: rule.tags.map(&:name),
88
93
  type: "external",
89
94
  source: "mihari",
90
95
  source_ref: SecureRandom.uuid
@@ -36,7 +36,7 @@ module Mihari
36
36
  # @param [Hash, nil] params
37
37
  #
38
38
  def initialize(rule:, options: nil, **params)
39
- super(rule: rule, options: options)
39
+ super(rule:, options:)
40
40
 
41
41
  @url = Addressable::URI.parse(params[:url])
42
42
  @headers = params[:headers] || {}
@@ -75,14 +75,14 @@ module Mihari
75
75
  when "GET"
76
76
  http.get(url).body.to_s
77
77
  when "POST"
78
- http.post(url, json: json).body.to_s
78
+ http.post(url, json:).body.to_s
79
79
  end
80
80
  end
81
81
 
82
82
  private
83
83
 
84
84
  def http
85
- HTTP::Factory.build headers: headers, timeout: timeout
85
+ HTTP::Factory.build headers:, timeout:
86
86
  end
87
87
 
88
88
  #
@@ -91,7 +91,7 @@ module Mihari
91
91
  # @return [String]
92
92
  #
93
93
  def render
94
- Services::JbuilderRenderer.call(template, { rule: rule, artifacts: artifacts })
94
+ Services::JbuilderRenderer.call(template, {rule:, artifacts:})
95
95
  end
96
96
 
97
97
  #