mihari 5.1.1 → 5.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.gitmodules +0 -3
  3. data/.rubocop.yml +6 -0
  4. data/README.md +0 -1
  5. data/lib/mihari/analyzers/base.rb +32 -27
  6. data/lib/mihari/analyzers/binaryedge.rb +8 -2
  7. data/lib/mihari/analyzers/censys.rb +7 -49
  8. data/lib/mihari/analyzers/circl.rb +5 -2
  9. data/lib/mihari/analyzers/crtsh.rb +6 -0
  10. data/lib/mihari/analyzers/dnstwister.rb +4 -2
  11. data/lib/mihari/analyzers/feed.rb +21 -0
  12. data/lib/mihari/analyzers/greynoise.rb +5 -28
  13. data/lib/mihari/analyzers/onyphe.rb +8 -33
  14. data/lib/mihari/analyzers/otx.rb +3 -0
  15. data/lib/mihari/analyzers/passivetotal.rb +3 -0
  16. data/lib/mihari/analyzers/pulsedive.rb +3 -0
  17. data/lib/mihari/analyzers/rule.rb +0 -1
  18. data/lib/mihari/analyzers/securitytrails.rb +8 -10
  19. data/lib/mihari/analyzers/shodan.rb +13 -81
  20. data/lib/mihari/analyzers/urlscan.rb +9 -0
  21. data/lib/mihari/analyzers/virustotal.rb +4 -0
  22. data/lib/mihari/analyzers/virustotal_intelligence.rb +8 -2
  23. data/lib/mihari/analyzers/zoomeye.rb +9 -0
  24. data/lib/mihari/clients/binaryedge.rb +5 -0
  25. data/lib/mihari/clients/censys.rb +4 -4
  26. data/lib/mihari/clients/circl.rb +3 -3
  27. data/lib/mihari/clients/greynoise.rb +6 -1
  28. data/lib/mihari/clients/misp.rb +6 -1
  29. data/lib/mihari/clients/onyphe.rb +13 -1
  30. data/lib/mihari/clients/otx.rb +20 -0
  31. data/lib/mihari/clients/passivetotal.rb +6 -2
  32. data/lib/mihari/clients/publsedive.rb +18 -1
  33. data/lib/mihari/clients/securitytrails.rb +94 -0
  34. data/lib/mihari/clients/shodan.rb +14 -3
  35. data/lib/mihari/clients/the_hive.rb +6 -1
  36. data/lib/mihari/clients/urlscan.rb +3 -1
  37. data/lib/mihari/clients/virustotal.rb +9 -3
  38. data/lib/mihari/clients/zoomeye.rb +7 -1
  39. data/lib/mihari/commands/database.rb +1 -6
  40. data/lib/mihari/commands/searcher.rb +1 -2
  41. data/lib/mihari/database.rb +9 -0
  42. data/lib/mihari/structs/censys.rb +62 -0
  43. data/lib/mihari/structs/greynoise.rb +43 -0
  44. data/lib/mihari/structs/onyphe.rb +45 -0
  45. data/lib/mihari/structs/shodan.rb +83 -0
  46. data/lib/mihari/version.rb +1 -1
  47. data/lib/mihari/web/middleware/connection_adapter.rb +1 -3
  48. data/lib/mihari/web/public/assets/{index-63900d73.js → index-7d0fb8c4.js} +2 -2
  49. data/lib/mihari/web/public/index.html +1 -1
  50. data/lib/mihari/web/public/redoc-static.html +2 -2
  51. data/lib/mihari.rb +1 -3
  52. data/mihari.gemspec +2 -3
  53. metadata +9 -25
  54. data/lib/mihari/analyzers/dnpedia.rb +0 -33
  55. data/lib/mihari/clients/dnpedia.rb +0 -64
  56. data/lib/mihari/mixins/database.rb +0 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e19317302956178dc4302543d81a0920018aa384595f91c69dd70110086d575a
4
- data.tar.gz: 80d6314c2df13a4a28ec71a0d4b358e74ab0ee9778d818658a997c1fd821f062
3
+ metadata.gz: 5415ee0f5bb820e8073383e4771589bbbaa39fc313af3aa434c5754f34bb9056
4
+ data.tar.gz: a4da4ce859fa718c900572b865518110f92649698a76ca64c55e38ebad2f9856
5
5
  SHA512:
6
- metadata.gz: b54bffc456bc114a2a8b52e5acbcc3f30d7571124fc9431fbda209bb57910eabe7950d86c50ef750c41d5df604a5aa08c9affb25d434db8a4f53d85ba8ae4921
7
- data.tar.gz: 4bce535d8d6d2573102b0197984854b84628a08ab37855c2a7f7fb1574b701e6b4ca816d3b23b3719d7cde0b7de89753af3b386d43af39e422c7c06b1d65448c
6
+ metadata.gz: c0b95e672f17d5ba0035624b035da278c863dc0b1a0860e3393cd8f001125964270233da7bf5ffb08c9057889b4a51698829de059089ed4d31a0f4fd7d0aa3e8
7
+ data.tar.gz: 30cbba0170104212e9244dfeac820a1a223468bae940719c242778d5bffd95b147f482548aebcc2386713fe43cc0c1b97bf2d71d08d318ad62a4b1a707f3e605
data/.gitmodules CHANGED
@@ -1,3 +0,0 @@
1
- [submodule "vendor/rbs/gem_rbs_collection"]
2
- path = vendor/rbs/gem_rbs_collection
3
- url = https://github.com/ruby/gem_rbs_collection.git
data/.rubocop.yml CHANGED
@@ -2,3 +2,9 @@ Style/HashSyntax:
2
2
  EnforcedShorthandSyntax: either
3
3
  Style/StringLiterals:
4
4
  EnforcedStyle: double_quotes
5
+ Metrics/BlockLength:
6
+ Exclude:
7
+ - "spec/**/*"
8
+ - "*.gemspec"
9
+ Metrics/ClassLength:
10
+ Enabled: false
data/README.md CHANGED
@@ -45,7 +45,6 @@ Mihari supports the following services by default.
45
45
  - [Censys](http://censys.io)
46
46
  - [CIRCL passive DNS](https://www.circl.lu/services/passive-dns/) / [passive SSL](https://www.circl.lu/services/passive-ssl/)
47
47
  - [crt.sh](https://crt.sh/)
48
- - [DN Pedia](https://dnpedia.com/)
49
48
  - [dnstwister](https://dnstwister.report/)
50
49
  - [GreyNoise](https://www.greynoise.io/)
51
50
  - [Onyphe](https://onyphe.io)
@@ -7,7 +7,6 @@ module Mihari
7
7
 
8
8
  option :rule, default: proc {}
9
9
 
10
- include Mixins::AutonomousSystem
11
10
  include Mixins::Configurable
12
11
  include Mixins::Retriable
13
12
 
@@ -20,6 +19,15 @@ module Mihari
20
19
  @base_time = Time.now.utc
21
20
  end
22
21
 
22
+ #
23
+ # Load/overwrite rule
24
+ #
25
+ # @param [String] path_or_id
26
+ #
27
+ def load_rule(path_or_id)
28
+ @rule = Structs::Rule.from_path_or_id path_or_id
29
+ end
30
+
23
31
  # @return [Array<String>, Array<Mihari::Artifact>]
24
32
  def artifacts
25
33
  raise NotImplementedError, "You must implement #{self.class}##{__method__}"
@@ -30,35 +38,41 @@ module Mihari
30
38
  self.class.to_s.split("::").last.to_s
31
39
  end
32
40
 
41
+ # @return [String]
42
+ def class_name
43
+ self.class.to_s.split("::").last
44
+ end
45
+
33
46
  #
34
47
  # Set artifacts & run emitters in parallel
35
48
  #
36
49
  # @return [Mihari::Alert, nil]
37
50
  #
38
51
  def run
39
- unless configured?
40
- class_name = self.class.to_s.split("::").last
41
- raise ConfigurationError, "#{class_name} is not configured correctly"
42
- end
43
-
44
- set_enriched_artifacts
45
-
46
- responses = Parallel.map(valid_emitters) do |emitter|
47
- run_emitter emitter
48
- end
52
+ raise ConfigurationError, "#{class_name} is not configured correctly" unless configured?
49
53
 
54
+ alert_or_something = bulk_emit
50
55
  # returns Mihari::Alert created by the database emitter
51
- responses.find { |res| res.is_a?(Mihari::Alert) }
56
+ alert_or_something.find { |res| res.is_a?(Mihari::Alert) }
52
57
  end
53
58
 
54
59
  #
55
- # Run emitter
60
+ # Bulk emit
61
+ #
62
+ # @return [Array<Mihari::Alert>]
63
+ #
64
+ def bulk_emit
65
+ Parallel.map(valid_emitters) { |emitter| emit emitter }.compact
66
+ end
67
+
68
+ #
69
+ # Emit an alert
56
70
  #
57
71
  # @param [Mihari::Emitters::Base] emitter
58
72
  #
59
73
  # @return [Mihari::Alert, nil]
60
74
  #
61
- def run_emitter(emitter)
75
+ def emit(emitter)
62
76
  return if enriched_artifacts.empty?
63
77
 
64
78
  alert_or_something = emitter.run(artifacts: enriched_artifacts, rule: rule)
@@ -80,6 +94,7 @@ module Mihari
80
94
  #
81
95
  # Normalize artifacts
82
96
  # - Convert data (string) into an artifact
97
+ # - Set rule ID
83
98
  # - Reject an invalid artifact
84
99
  # - Uniquefy artifacts by data
85
100
  #
@@ -89,17 +104,16 @@ module Mihari
89
104
  @normalized_artifacts ||= artifacts.compact.sort.map do |artifact|
90
105
  # No need to set data_type manually
91
106
  # It is set automatically in #initialize
92
- artifact.is_a?(Artifact) ? artifact : Artifact.new(data: artifact, source: source)
93
- end.select(&:valid?).uniq(&:data).map do |artifact|
107
+ artifact = artifact.is_a?(Artifact) ? artifact : Artifact.new(data: artifact, source: source)
94
108
  artifact.rule_id = rule&.id
95
109
  artifact
96
- end
110
+ end.select(&:valid?).uniq(&:data)
97
111
  end
98
112
 
99
113
  private
100
114
 
101
115
  #
102
- # Uniquefy artifacts
116
+ # Uniquefy artifacts (assure rule level uniqueness)
103
117
  #
104
118
  # @return [Array<Mihari::Artifact>]
105
119
  #
@@ -121,15 +135,6 @@ module Mihari
121
135
  end
122
136
  end
123
137
 
124
- #
125
- # Set enriched artifacts
126
- #
127
- # @return [nil]
128
- #
129
- def set_enriched_artifacts
130
- retry_on_error { enriched_artifacts }
131
- end
132
-
133
138
  #
134
139
  # Select valid emitters
135
140
  #
@@ -10,6 +10,12 @@ module Mihari
10
10
  # @return [String, nil]
11
11
  attr_reader :api_key
12
12
 
13
+ # @return [String]
14
+ attr_reader :query
15
+
16
+ # @return [Integer]
17
+ attr_reader :interval
18
+
13
19
  def initialize(*args, **kwargs)
14
20
  super(*args, **kwargs)
15
21
 
@@ -41,7 +47,7 @@ module Mihari
41
47
  #
42
48
  # @return [Hash]
43
49
  #
44
- def search_with_page(query, page: 1)
50
+ def search_with_page(page: 1)
45
51
  client.search(query, page: page)
46
52
  rescue UnsuccessfulStatusCodeError => e
47
53
  raise RetryableError, e if e.message.include?("Request time limit exceeded")
@@ -57,7 +63,7 @@ module Mihari
57
63
  def search
58
64
  responses = []
59
65
  (1..500).each do |page|
60
- res = search_with_page(query, page: page)
66
+ res = search_with_page(page: page)
61
67
  total = res["total"].to_i
62
68
 
63
69
  responses << res
@@ -13,6 +13,12 @@ module Mihari
13
13
  # @return [String, nil]
14
14
  attr_reader :secret
15
15
 
16
+ # @return [Integer]
17
+ attr_reader :interval
18
+
19
+ # @return [String]
20
+ attr_reader :query
21
+
16
22
  def initialize(*args, **kwargs)
17
23
  super(*args, **kwargs)
18
24
 
@@ -41,10 +47,7 @@ module Mihari
41
47
  cursor = nil
42
48
  loop do
43
49
  response = client.search(query, cursor: cursor)
44
- response = Structs::Censys::Response.from_dynamic!(response)
45
-
46
- artifacts << response_to_artifacts(response)
47
-
50
+ artifacts << response.result.to_artifacts(source)
48
51
  cursor = response.result.links.next
49
52
  break if cursor == ""
50
53
 
@@ -55,51 +58,6 @@ module Mihari
55
58
  artifacts.flatten.uniq(&:data)
56
59
  end
57
60
 
58
- #
59
- # Extract IPv4s from Censys search API response
60
- #
61
- # @param [Structs::Censys::Response] response
62
- #
63
- # @return [Array<String>]
64
- #
65
- def response_to_artifacts(response)
66
- response.result.hits.map { |hit| build_artifact(hit) }
67
- end
68
-
69
- #
70
- # Build an artifact from a Shodan search API response
71
- #
72
- # @param [Structs::Censys::Hit] hit
73
- #
74
- # @return [Artifact]
75
- #
76
- def build_artifact(hit)
77
- as = AutonomousSystem.new(asn: normalize_asn(hit.autonomous_system.asn))
78
-
79
- # sometimes Censys overlooks country
80
- # then set geolocation as nil
81
- geolocation = nil
82
- unless hit.location.country.nil?
83
- geolocation = Geolocation.new(
84
- country: hit.location.country,
85
- country_code: hit.location.country_code
86
- )
87
- end
88
-
89
- ports = hit.services.map(&:port).map do |port|
90
- Port.new(port: port)
91
- end
92
-
93
- Artifact.new(
94
- data: hit.ip,
95
- source: source,
96
- metadata: hit.metadata,
97
- autonomous_system: as,
98
- geolocation: geolocation,
99
- ports: ports
100
- )
101
- end
102
-
103
61
  def configuration_keys
104
62
  %w[censys_id censys_secret]
105
63
  end
@@ -16,6 +16,9 @@ module Mihari
16
16
  # @return [String, nil]
17
17
  attr_reader :password
18
18
 
19
+ # @return [String]
20
+ attr_reader :query
21
+
19
22
  def initialize(*args, **kwargs)
20
23
  super
21
24
 
@@ -66,7 +69,7 @@ module Mihari
66
69
  # @return [Array<String>]
67
70
  #
68
71
  def passive_dns_search
69
- results = client.dns_query(@query)
72
+ results = client.dns_query(query)
70
73
  results.filter_map do |result|
71
74
  type = result["rrtype"]
72
75
  (type == "A") ? result["rdata"] : nil
@@ -79,7 +82,7 @@ module Mihari
79
82
  # @return [Array<String>]
80
83
  #
81
84
  def passive_ssl_search
82
- result = client.ssl_cquery(@query)
85
+ result = client.ssl_cquery(query)
83
86
  seen = result["seen"] || []
84
87
  seen.uniq
85
88
  end
@@ -7,6 +7,12 @@ module Mihari
7
7
 
8
8
  option :exclude_expired, default: proc { true }
9
9
 
10
+ # @return [Boolean]
11
+ attr_reader :exclude_expired
12
+
13
+ # @return [String]
14
+ attr_reader :query
15
+
10
16
  def artifacts
11
17
  results = search
12
18
  results.map do |result|
@@ -7,10 +7,12 @@ module Mihari
7
7
 
8
8
  param :query
9
9
 
10
- option :tags, default: proc { [] }
11
-
10
+ # @return [String]
12
11
  attr_reader :type
13
12
 
13
+ # @return [String]
14
+ attr_reader :query
15
+
14
16
  def initialize(*args, **kwargs)
15
17
  super
16
18
 
@@ -16,6 +16,27 @@ module Mihari
16
16
 
17
17
  option :selector, default: proc { "" }
18
18
 
19
+ # @return [Hash, nil]
20
+ attr_reader :data
21
+
22
+ # @return [Hash, nil]
23
+ attr_reader :json
24
+
25
+ # @return [Hash, nil]
26
+ attr_reader :params
27
+
28
+ # @return [Hash, nil]
29
+ attr_reader :headers
30
+
31
+ # @return [String]
32
+ attr_reader :method
33
+
34
+ # @return [String]
35
+ attr_reader :selector
36
+
37
+ # @return [String]
38
+ attr_reader :query
39
+
19
40
  def artifacts
20
41
  Mihari::Feed::Parser.new(results).parse selector
21
42
  end
@@ -8,6 +8,9 @@ module Mihari
8
8
  # @return [String, nil]
9
9
  attr_reader :api_key
10
10
 
11
+ # @return [String]
12
+ attr_reader :query
13
+
11
14
  def initialize(*args, **kwargs)
12
15
  super(*args, **kwargs)
13
16
 
@@ -15,10 +18,8 @@ module Mihari
15
18
  end
16
19
 
17
20
  def artifacts
18
- res = Structs::GreyNoise::Response.from_dynamic!(search)
19
- res.data.map do |datum|
20
- build_artifact datum
21
- end
21
+ res = search
22
+ res.to_artifacts
22
23
  end
23
24
 
24
25
  private
@@ -41,30 +42,6 @@ module Mihari
41
42
  def search
42
43
  client.gnql_search(query, size: PAGE_SIZE)
43
44
  end
44
-
45
- #
46
- # Build an artifact from a GreyNoise search API response
47
- #
48
- # @param [Structs::GreyNoise::Datum] datum
49
- #
50
- # @return [Artifact]
51
- #
52
- def build_artifact(datum)
53
- as = AutonomousSystem.new(asn: normalize_asn(datum.metadata.asn))
54
-
55
- geolocation = Geolocation.new(
56
- country: datum.metadata.country,
57
- country_code: datum.metadata.country_code
58
- )
59
-
60
- Artifact.new(
61
- data: datum.ip,
62
- source: source,
63
- metadata: datum.metadata_,
64
- autonomous_system: as,
65
- geolocation: geolocation
66
- )
67
- end
68
45
  end
69
46
  end
70
47
  end
@@ -12,6 +12,12 @@ module Mihari
12
12
  # @return [String, nil]
13
13
  attr_reader :api_key
14
14
 
15
+ # @return [String]
16
+ attr_reader :query
17
+
18
+ # @return [Integer]
19
+ attr_reader :interval
20
+
15
21
  def initialize(*args, **kwargs)
16
22
  super(*args, **kwargs)
17
23
 
@@ -22,10 +28,7 @@ module Mihari
22
28
  responses = search
23
29
  return [] unless responses
24
30
 
25
- results = responses.map(&:results).flatten
26
- results.map do |result|
27
- build_artifact result
28
- end
31
+ responses.map { |response| response.to_artifacts(source) }.flatten
29
32
  end
30
33
 
31
34
  private
@@ -49,8 +52,7 @@ module Mihari
49
52
  # @return [Structs::Onyphe::Response]
50
53
  #
51
54
  def search_with_page(query, page: 1)
52
- res = client.datascan(query, page: page)
53
- Structs::Onyphe::Response.from_dynamic!(res)
55
+ client.datascan(query, page: page)
54
56
  end
55
57
 
56
58
  #
@@ -72,33 +74,6 @@ module Mihari
72
74
  end
73
75
  responses
74
76
  end
75
-
76
- #
77
- # Build an artifact from an Onyphe search API result
78
- #
79
- # @param [Structs::Onyphe::Result] result
80
- #
81
- # @return [Artifact]
82
- #
83
- def build_artifact(result)
84
- as = AutonomousSystem.new(asn: normalize_asn(result.asn))
85
-
86
- geolocation = nil
87
- unless result.country_code.nil?
88
- geolocation = Geolocation.new(
89
- country: NormalizeCountry(result.country_code, to: :short),
90
- country_code: result.country_code
91
- )
92
- end
93
-
94
- Artifact.new(
95
- data: result.ip,
96
- source: source,
97
- metadata: result.metadata,
98
- autonomous_system: as,
99
- geolocation: geolocation
100
- )
101
- end
102
77
  end
103
78
  end
104
79
  end
@@ -13,6 +13,9 @@ module Mihari
13
13
  # @return [String, nil]
14
14
  attr_reader :api_key
15
15
 
16
+ # @return [String]
17
+ attr_reader :query
18
+
16
19
  def initialize(*args, **kwargs)
17
20
  super
18
21
 
@@ -16,6 +16,9 @@ module Mihari
16
16
  # @return [String, nil]
17
17
  attr_reader :api_key
18
18
 
19
+ # @return [String]
20
+ attr_reader :query
21
+
19
22
  def initialize(*args, **kwargs)
20
23
  super(*args, **kwargs)
21
24
 
@@ -13,6 +13,9 @@ module Mihari
13
13
  # @return [String, nil]
14
14
  attr_reader :api_key
15
15
 
16
+ # @return [Integer]
17
+ attr_reader :query
18
+
16
19
  def initialize(*args, **kwargs)
17
20
  super
18
21
 
@@ -7,7 +7,6 @@ module Mihari
7
7
  "censys" => Censys,
8
8
  "circl" => CIRCL,
9
9
  "crtsh" => Crtsh,
10
- "dnpedia" => DNPedia,
11
10
  "dnstwister" => DNSTwister,
12
11
  "feed" => Feed,
13
12
  "greynoise" => GreyNoise,
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "securitytrails"
4
-
5
3
  module Mihari
6
4
  module Analyzers
7
5
  class SecurityTrails < Base
@@ -15,6 +13,9 @@ module Mihari
15
13
  # @return [String, nil]
16
14
  attr_reader :api_key
17
15
 
16
+ # @return [String]
17
+ attr_reader :query
18
+
18
19
  def initialize(*args, **kwargs)
19
20
  super
20
21
 
@@ -34,8 +35,8 @@ module Mihari
34
35
  %w[securitytrails_api_key]
35
36
  end
36
37
 
37
- def api
38
- @api ||= ::SecurityTrails::API.new(api_key)
38
+ def client
39
+ @client ||= Clients::SecurityTrails.new(api_key: api_key)
39
40
  end
40
41
 
41
42
  #
@@ -71,8 +72,7 @@ module Mihari
71
72
  # @return [Array<String>]
72
73
  #
73
74
  def domain_search
74
- result = api.history.get_all_dns_history(query, type: "a")
75
- records = result["records"] || []
75
+ records = client.get_all_dns_history(query, type: "a")
76
76
  records.map do |record|
77
77
  (record["values"] || []).map { |value| value["ip"] }
78
78
  end.flatten.compact.uniq
@@ -84,8 +84,7 @@ module Mihari
84
84
  # @return [Array<Mihari::Artifact>]
85
85
  #
86
86
  def ip_search
87
- result = api.domains.search(filter: { ipv4: query })
88
- records = result["records"] || []
87
+ records = client.search_by_ip(query)
89
88
  records.filter_map do |record|
90
89
  data = record["hostname"]
91
90
  Artifact.new(data: data, source: source, metadata: record)
@@ -98,8 +97,7 @@ module Mihari
98
97
  # @return [Array<String>]
99
98
  #
100
99
  def mail_search
101
- result = api.domains.search(filter: { whois_email: query })
102
- records = result["records"] || []
100
+ records = client.search_by_mail(query)
103
101
  records.filter_map do |record|
104
102
  data = record["hostname"]
105
103
  Artifact.new(data: data, source: source, metadata: record)