mihari 6.3.0 → 7.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (161) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -10
  3. data/.rubocop.yml +2 -0
  4. data/Dockerfile +14 -0
  5. data/config.ru +5 -3
  6. data/docker-compose.yml +61 -0
  7. data/exe/mihari +2 -1
  8. data/lefthook.yml +8 -0
  9. data/lib/mihari/actor.rb +4 -4
  10. data/lib/mihari/analyzers/base.rb +16 -0
  11. data/lib/mihari/analyzers/binaryedge.rb +4 -2
  12. data/lib/mihari/analyzers/censys.rb +7 -5
  13. data/lib/mihari/analyzers/circl.rb +5 -3
  14. data/lib/mihari/analyzers/crtsh.rb +10 -2
  15. data/lib/mihari/analyzers/dnstwister.rb +1 -1
  16. data/lib/mihari/analyzers/feed.rb +12 -20
  17. data/lib/mihari/analyzers/fofa.rb +6 -8
  18. data/lib/mihari/analyzers/greynoise.rb +4 -2
  19. data/lib/mihari/analyzers/hunterhow.rb +4 -2
  20. data/lib/mihari/analyzers/onyphe.rb +4 -2
  21. data/lib/mihari/analyzers/otx.rb +5 -3
  22. data/lib/mihari/analyzers/passivetotal.rb +29 -12
  23. data/lib/mihari/analyzers/pulsedive.rb +5 -3
  24. data/lib/mihari/analyzers/securitytrails.rb +32 -8
  25. data/lib/mihari/analyzers/shodan.rb +4 -2
  26. data/lib/mihari/analyzers/urlscan.rb +4 -2
  27. data/lib/mihari/analyzers/virustotal.rb +5 -5
  28. data/lib/mihari/analyzers/virustotal_intelligence.rb +4 -2
  29. data/lib/mihari/analyzers/zoomeye.rb +4 -2
  30. data/lib/mihari/cli/{main.rb → application.rb} +17 -5
  31. data/lib/mihari/cli/artifact.rb +14 -0
  32. data/lib/mihari/cli/config.rb +14 -0
  33. data/lib/mihari/cli/rule.rb +1 -0
  34. data/lib/mihari/cli/tag.rb +14 -0
  35. data/lib/mihari/clients/base.rb +2 -2
  36. data/lib/mihari/clients/binaryedge.rb +2 -2
  37. data/lib/mihari/clients/crtsh.rb +3 -10
  38. data/lib/mihari/clients/fofa.rb +1 -1
  39. data/lib/mihari/clients/hunterhow.rb +1 -1
  40. data/lib/mihari/clients/mmdb.rb +28 -0
  41. data/lib/mihari/clients/passivetotal.rb +7 -20
  42. data/lib/mihari/clients/securitytrails.rb +19 -43
  43. data/lib/mihari/clients/shodan_internet_db.rb +28 -0
  44. data/lib/mihari/clients/the_hive.rb +7 -5
  45. data/lib/mihari/commands/alert.rb +53 -11
  46. data/lib/mihari/commands/artifact.rb +66 -0
  47. data/lib/mihari/commands/config.rb +23 -0
  48. data/lib/mihari/commands/database.rb +1 -1
  49. data/lib/mihari/commands/rule.rb +40 -27
  50. data/lib/mihari/commands/search.rb +10 -11
  51. data/lib/mihari/commands/sidekiq.rb +31 -0
  52. data/lib/mihari/commands/tag.rb +46 -0
  53. data/lib/mihari/commands/web.rb +6 -7
  54. data/lib/mihari/{mixins/autonomous_system.rb → concerns/autonomous_system_normalizable.rb} +5 -3
  55. data/lib/mihari/concerns/configurable.rb +72 -0
  56. data/lib/mihari/concerns/database_connectable.rb +16 -0
  57. data/lib/mihari/{mixins/unwrap_error.rb → concerns/error_unwrappable.rb} +5 -3
  58. data/lib/mihari/{mixins/falsepositive.rb → concerns/falsepositive_validatable.rb} +5 -3
  59. data/lib/mihari/{mixins/refang.rb → concerns/refangable.rb} +5 -3
  60. data/lib/mihari/{mixins → concerns}/retriable.rb +4 -2
  61. data/lib/mihari/config.rb +13 -12
  62. data/lib/mihari/database.rb +30 -42
  63. data/lib/mihari/emitters/database.rb +5 -6
  64. data/lib/mihari/emitters/misp.rb +4 -11
  65. data/lib/mihari/emitters/slack.rb +7 -5
  66. data/lib/mihari/emitters/the_hive.rb +8 -58
  67. data/lib/mihari/emitters/webhook.rb +6 -6
  68. data/lib/mihari/enrichers/google_public_dns.rb +1 -1
  69. data/lib/mihari/enrichers/mmdb.rb +28 -0
  70. data/lib/mihari/enrichers/shodan.rb +3 -5
  71. data/lib/mihari/enrichers/whois.rb +3 -3
  72. data/lib/mihari/entities/alert.rb +3 -10
  73. data/lib/mihari/entities/artifact.rb +6 -14
  74. data/lib/mihari/entities/config.rb +2 -2
  75. data/lib/mihari/entities/cpe.rb +1 -0
  76. data/lib/mihari/entities/dns.rb +1 -0
  77. data/lib/mihari/entities/geolocation.rb +1 -0
  78. data/lib/mihari/entities/ip_address.rb +1 -3
  79. data/lib/mihari/entities/messages.rb +17 -0
  80. data/lib/mihari/entities/pagination.rb +11 -0
  81. data/lib/mihari/entities/port.rb +1 -0
  82. data/lib/mihari/entities/reverse_dns.rb +1 -0
  83. data/lib/mihari/entities/rule.rb +2 -20
  84. data/lib/mihari/entities/tag.rb +2 -2
  85. data/lib/mihari/entities/whois.rb +1 -0
  86. data/lib/mihari/errors.rb +2 -4
  87. data/lib/mihari/http.rb +4 -0
  88. data/lib/mihari/models/alert.rb +21 -53
  89. data/lib/mihari/models/artifact.rb +61 -97
  90. data/lib/mihari/models/autonomous_system.rb +0 -24
  91. data/lib/mihari/models/concerns/searchable.rb +50 -0
  92. data/lib/mihari/models/cpe.rb +0 -23
  93. data/lib/mihari/models/dns.rb +0 -20
  94. data/lib/mihari/models/geolocation.rb +0 -24
  95. data/lib/mihari/models/port.rb +3 -10
  96. data/lib/mihari/models/reverse_dns.rb +0 -23
  97. data/lib/mihari/models/rule.rb +16 -57
  98. data/lib/mihari/models/tag.rb +17 -1
  99. data/lib/mihari/models/tagging.rb +1 -1
  100. data/lib/mihari/models/whois.rb +0 -17
  101. data/lib/mihari/rule.rb +35 -24
  102. data/lib/mihari/schemas/alert.rb +1 -0
  103. data/lib/mihari/schemas/analyzer.rb +3 -2
  104. data/lib/mihari/schemas/concerns/orrable.rb +24 -0
  105. data/lib/mihari/schemas/emitter.rb +1 -2
  106. data/lib/mihari/schemas/enricher.rb +3 -4
  107. data/lib/mihari/schemas/macros.rb +1 -1
  108. data/lib/mihari/schemas/options.rb +0 -2
  109. data/lib/mihari/schemas/rule.rb +1 -2
  110. data/lib/mihari/services/builders.rb +158 -0
  111. data/lib/mihari/services/creators.rb +22 -0
  112. data/lib/mihari/services/destroyers.rb +41 -0
  113. data/lib/mihari/services/enrichers.rb +25 -0
  114. data/lib/mihari/services/feed.rb +107 -0
  115. data/lib/mihari/services/getters.rb +58 -0
  116. data/lib/mihari/services/initializers.rb +22 -0
  117. data/lib/mihari/services/{alert_builder.rb → proxies.rb} +10 -40
  118. data/lib/mihari/services/searchers.rb +91 -0
  119. data/lib/mihari/sidekiq/application.rb +13 -0
  120. data/lib/mihari/sidekiq/jobs.rb +36 -0
  121. data/lib/mihari/structs/censys.rb +1 -1
  122. data/lib/mihari/structs/config.rb +10 -10
  123. data/lib/mihari/structs/filters.rb +12 -130
  124. data/lib/mihari/structs/google_public_dns.rb +1 -1
  125. data/lib/mihari/structs/greynoise.rb +1 -1
  126. data/lib/mihari/structs/mmdb.rb +115 -0
  127. data/lib/mihari/structs/onyphe.rb +1 -1
  128. data/lib/mihari/structs/shodan.rb +2 -2
  129. data/lib/mihari/version.rb +1 -1
  130. data/lib/mihari/web/{app.rb → application.rb} +28 -15
  131. data/lib/mihari/web/endpoints/alerts.rb +34 -73
  132. data/lib/mihari/web/endpoints/artifacts.rb +27 -111
  133. data/lib/mihari/web/endpoints/configs.rb +3 -5
  134. data/lib/mihari/web/endpoints/ip_addresses.rb +14 -15
  135. data/lib/mihari/web/endpoints/rules.rb +58 -130
  136. data/lib/mihari/web/endpoints/tags.rb +21 -17
  137. data/lib/mihari/web/middleware/capture_exceptions.rb +25 -0
  138. data/lib/mihari/web/middleware/{connection_adapter.rb → connection.rb} +4 -2
  139. data/lib/mihari/web/public/assets/index-cQUcyII5.js +1766 -0
  140. data/lib/mihari/web/public/assets/index-dVaNxqTC.css +1 -0
  141. data/lib/mihari/web/public/index.html +2 -2
  142. data/lib/mihari/web/public/redoc-static.html +385 -385
  143. data/lib/mihari.rb +56 -28
  144. data/mihari.gemspec +12 -4
  145. data/mkdocs.yml +5 -2
  146. data/requirements.txt +1 -1
  147. metadata +164 -34
  148. data/lib/mihari/commands/mixins.rb +0 -11
  149. data/lib/mihari/enrichers/ipinfo.rb +0 -52
  150. data/lib/mihari/entities/message.rb +0 -9
  151. data/lib/mihari/feed/parser.rb +0 -38
  152. data/lib/mihari/feed/reader.rb +0 -111
  153. data/lib/mihari/mixins/configurable.rb +0 -68
  154. data/lib/mihari/schemas/mixins.rb +0 -20
  155. data/lib/mihari/services/alert_runner.rb +0 -20
  156. data/lib/mihari/services/rule_builder.rb +0 -46
  157. data/lib/mihari/structs/ipinfo.rb +0 -53
  158. data/lib/mihari/web/endpoints/exports.rb +0 -0
  159. data/lib/mihari/web/middleware/error_notification_adapter.rb +0 -35
  160. data/lib/mihari/web/public/assets/index-81613_nX.js +0 -1763
  161. data/lib/mihari/web/public/assets/index-Wv6xUrTI.css +0 -1
@@ -6,7 +6,7 @@ module Mihari
6
6
  # VirusTotal analyzer
7
7
  #
8
8
  class VirusTotal < Base
9
- include Mixins::Refang
9
+ include Concerns::Refangable
10
10
 
11
11
  # @return [String]
12
12
  attr_reader :type
@@ -38,11 +38,11 @@ module Mihari
38
38
  end
39
39
  end
40
40
 
41
- def configuration_keys
42
- %w[virustotal_api_key]
43
- end
44
-
45
41
  class << self
42
+ def configuration_keys
43
+ %w[virustotal_api_key]
44
+ end
45
+
46
46
  #
47
47
  # @return [Array<String>, nil]
48
48
  #
@@ -24,8 +24,10 @@ module Mihari
24
24
  client.intel_search_with_pagination(query, pagination_limit: pagination_limit).map(&:artifacts).flatten
25
25
  end
26
26
 
27
- def configuration_keys
28
- %w[virustotal_api_key]
27
+ class << self
28
+ def configuration_keys
29
+ %w[virustotal_api_key]
30
+ end
29
31
  end
30
32
 
31
33
  class << self
@@ -40,8 +40,10 @@ module Mihari
40
40
  end
41
41
  end
42
42
 
43
- def configuration_keys
44
- %w[zoomeye_api_key]
43
+ class << self
44
+ def configuration_keys
45
+ %w[zoomeye_api_key]
46
+ end
45
47
  end
46
48
 
47
49
  private
@@ -4,11 +4,10 @@ require "thor"
4
4
  require "thor/hollaback"
5
5
 
6
6
  # Commands
7
- require "mihari/commands/mixins"
8
-
9
7
  require "mihari/commands/alert"
10
8
  require "mihari/commands/database"
11
9
  require "mihari/commands/search"
10
+ require "mihari/commands/sidekiq"
12
11
  require "mihari/commands/version"
13
12
  require "mihari/commands/web"
14
13
 
@@ -16,23 +15,27 @@ require "mihari/commands/web"
16
15
  require "mihari/cli/base"
17
16
 
18
17
  require "mihari/cli/alert"
18
+ require "mihari/cli/artifact"
19
+ require "mihari/cli/config"
19
20
  require "mihari/cli/database"
20
21
  require "mihari/cli/rule"
22
+ require "mihari/cli/tag"
21
23
 
22
24
  module Mihari
23
25
  module CLI
24
26
  #
25
27
  # Main CLI class
26
28
  #
27
- class Main < Base
28
- class_option :debug, desc: "Sets up debug mode", aliases: ["-d"], type: :boolean
29
+ class App < Base
30
+ class_option :debug, desc: "Set up debug mode", aliases: ["-d"], type: :boolean
29
31
  class_around :safe_execute
30
32
 
31
33
  include Mihari::Commands::Search
34
+ include Mihari::Commands::Sidekiq
32
35
  include Mihari::Commands::Version
33
36
  include Mihari::Commands::Web
34
37
 
35
- include Mihari::Mixins::UnwrapError
38
+ include Concerns::ErrorUnwrappable
36
39
 
37
40
  no_commands do
38
41
  def safe_execute
@@ -62,6 +65,15 @@ module Mihari
62
65
 
63
66
  desc "alert", "Sub commands for alert"
64
67
  subcommand "alert", Alert
68
+
69
+ desc "artifact", "Sub commands for artifact"
70
+ subcommand "artifact", Artifact
71
+
72
+ desc "tag", "Sub commands for tag"
73
+ subcommand "tag", Tag
74
+
75
+ desc "config", "Sub commands for config"
76
+ subcommand "config", Config
65
77
  end
66
78
  end
67
79
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mihari/commands/artifact"
4
+
5
+ module Mihari
6
+ module CLI
7
+ #
8
+ # Artifact CLI class (mihari artifact ...)
9
+ #
10
+ class Artifact < Base
11
+ include Mihari::Commands::Artifact
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mihari/commands/config"
4
+
5
+ module Mihari
6
+ module CLI
7
+ #
8
+ # Config CLI class (mihari config ...)
9
+ #
10
+ class Config < Base
11
+ include Mihari::Commands::Config
12
+ end
13
+ end
14
+ end
@@ -9,6 +9,7 @@ module Mihari
9
9
  #
10
10
  class Rule < Base
11
11
  include Mihari::Commands::Rule
12
+ include Mihari::Commands::Search
12
13
  end
13
14
  end
14
15
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mihari/commands/tag"
4
+
5
+ module Mihari
6
+ module CLI
7
+ #
8
+ # Tag CLI class (mihari tag ...)
9
+ #
10
+ class Tag < Base
11
+ include Mihari::Commands::Tag
12
+ end
13
+ end
14
+ end
@@ -47,10 +47,10 @@ module Mihari
47
47
  #
48
48
  # @param [String] path
49
49
  #
50
- # @return [String]
50
+ # @return [URI]
51
51
  #
52
52
  def url_for(path)
53
- base_url + path
53
+ URI.join base_url, path
54
54
  end
55
55
 
56
56
  #
@@ -14,7 +14,7 @@ module Mihari
14
14
  # @param [Integer] pagination_interval
15
15
  #
16
16
  def initialize(
17
- base_url = "https://api.binaryedge.io/v2",
17
+ base_url = "https://api.binaryedge.io",
18
18
  api_key:,
19
19
  headers: {},
20
20
  pagination_interval: Mihari.config.pagination_interval,
@@ -38,7 +38,7 @@ module Mihari
38
38
  page: page,
39
39
  only_ips: only_ips
40
40
  }.compact
41
- Structs::BinaryEdge::Response.from_dynamic! get_json("/query/search", params: params)
41
+ Structs::BinaryEdge::Response.from_dynamic! get_json("/v2/query/search", params: params)
42
42
  end
43
43
 
44
44
  #
@@ -19,20 +19,13 @@ module Mihari
19
19
  # Search crt.sh by a given identity
20
20
  #
21
21
  # @param [String] identity
22
- # @param [String, nil] match "=", "ILIKE", "LIKE", "single", "any" or nil
22
+ # @param [String, nil] match "=", "ILIKE", "LIKE", "single", "any", "FTS" or nil
23
23
  # @param [String, nil] exclude "expired" or nil
24
24
  #
25
- # @return [Array<Mihari::Models::Artifact>]
25
+ # @return [Array<Hash>]
26
26
  #
27
27
  def search(identity, match: nil, exclude: nil)
28
- params = { identity: identity, match: match, exclude: exclude, output: "json" }.compact
29
-
30
- # @type [Array[Hash]]
31
- parsed = get_json("/", params: params)
32
- parsed.map do |result|
33
- values = result["name_value"].to_s.lines.map(&:chomp)
34
- values.map { |value| Models::Artifact.new(data: value, metadata: result) }
35
- end.flatten
28
+ get_json("/", params: { identity: identity, match: match, exclude: exclude, output: "json" }.compact)
36
29
  end
37
30
  end
38
31
  end
@@ -26,7 +26,7 @@ module Mihari
26
26
  #
27
27
  # @param [Object] email
28
28
  def initialize(
29
- base_url = "https://fofa.info/",
29
+ base_url = "https://fofa.info",
30
30
  api_key:,
31
31
  email:,
32
32
  headers: {},
@@ -21,7 +21,7 @@ module Mihari
21
21
  # @param [Integer, nil] timeout
22
22
  #
23
23
  def initialize(
24
- base_url = "https://api.hunter.how/",
24
+ base_url = "https://api.hunter.how",
25
25
  api_key:,
26
26
  headers: {},
27
27
  pagination_interval: Mihari.config.pagination_interval,
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Clients
5
+ #
6
+ # MMDB API client
7
+ #
8
+ class MMDB < Base
9
+ #
10
+ # @param [String] base_url
11
+ # @param [Hash] headers
12
+ # @param [Integer, nil] timeout
13
+ #
14
+ def initialize(base_url = "https://ip.circl.lu", headers: {}, timeout: nil)
15
+ super(base_url, headers: headers, timeout: timeout)
16
+ end
17
+
18
+ #
19
+ # @param [String] ip
20
+ #
21
+ # @return [Mihari::Structs::MMDB::Response]
22
+ #
23
+ def query(ip)
24
+ Structs::MMDB::Response.from_dynamic! get_json("/geolookup/#{ip}")
25
+ end
26
+ end
27
+ end
28
+ end
@@ -29,12 +29,11 @@ module Mihari
29
29
  #
30
30
  # @param [String] query
31
31
  #
32
- # @return [Array<String>]
32
+ # @return [Hash]
33
33
  #
34
34
  def passive_dns_search(query)
35
35
  params = { query: query }
36
- res = get_json("/v2/dns/passive/unique", params: params)
37
- res["results"] || []
36
+ get_json("/v2/dns/passive/unique", params: params)
38
37
  end
39
38
 
40
39
  #
@@ -42,19 +41,13 @@ module Mihari
42
41
  #
43
42
  # @param [String] query
44
43
  #
45
- # @return [Array<Mihari::Models::Artifact>]
44
+ # @return [Hash]
46
45
  #
47
46
  def reverse_whois_search(query)
48
- params = {
47
+ get_json("/v2/whois/search", params: {
49
48
  query: query,
50
49
  field: "email"
51
- }.compact
52
- res = get_json("/v2/whois/search", params: params)
53
- results = res["results"] || []
54
- results.map do |result|
55
- data = result["domain"]
56
- Models::Artifact.new(data: data, metadata: result)
57
- end.flatten
50
+ }.compact)
58
51
  end
59
52
 
60
53
  #
@@ -62,16 +55,10 @@ module Mihari
62
55
  #
63
56
  # @param [String] query
64
57
  #
65
- # @return [Array<Mihari::Models::Artifact>]
58
+ # @return [Hash]
66
59
  #
67
60
  def ssl_search(query)
68
- params = { query: query }
69
- res = get_json("/v2/ssl-certificate/history", params: params)
70
- results = res["results"] || []
71
- results.map do |result|
72
- data = result["ipAddresses"]
73
- data.map { |d| Models::Artifact.new(data: d, metadata: result) }
74
- end.flatten
61
+ get_json("/v2/ssl-certificate/history", params: { query: query })
75
62
  end
76
63
  end
77
64
  end
@@ -20,33 +20,15 @@ module Mihari
20
20
  super(base_url, headers: headers, timeout: timeout)
21
21
  end
22
22
 
23
- #
24
- # Domain search
25
- #
26
- # @param [String] query
27
- #
28
- # @return [Array<String>]
29
- #
30
- def domain_search(query)
31
- records = get_all_dns_history(query, type: "a")
32
- records.map do |record|
33
- (record["values"] || []).map { |value| value["ip"] }
34
- end.flatten.compact.uniq
35
- end
36
-
37
23
  #
38
24
  # IP search
39
25
  #
40
26
  # @param [String] query
41
27
  #
42
- # @return [Array<Mihari::Models::Artifact>]
28
+ # @return [Hash]
43
29
  #
44
30
  def ip_search(query)
45
- records = search_by_ip(query)
46
- records.filter_map do |record|
47
- data = record["hostname"]
48
- Models::Artifact.new(data: data, metadata: record)
49
- end
31
+ search_by_ip(query)
50
32
  end
51
33
 
52
34
  #
@@ -54,54 +36,48 @@ module Mihari
54
36
  #
55
37
  # @param [String] query
56
38
  #
57
- # @return [Array<String>]
39
+ # @return [Hash]
58
40
  #
59
41
  def mail_search(query)
60
- records = search_by_mail(query)
61
- records.filter_map do |record|
62
- data = record["hostname"]
63
- Models::Artifact.new(data: data, metadata: record)
64
- end
42
+ search_by_mail(query)
65
43
  end
66
44
 
67
45
  #
68
46
  # @param [String] mail
69
47
  #
70
- # @return [Array<Hash>]
48
+ # @return [Hash]
71
49
  #
72
50
  def search_by_mail(mail)
73
- res = post_json "/v1/domains/list", json: { filter: { whois_email: mail } }
74
- res["records"] || []
51
+ post_json "/v1/domains/list", json: { filter: { whois_email: mail } }
75
52
  end
76
53
 
77
54
  #
78
55
  # @param [String] ip
79
56
  #
80
- # @return [Array<Hash>]
57
+ # @return [Hash]
81
58
  #
82
59
  def search_by_ip(ip)
83
- res = post_json "/v1/domains/list", json: { filter: { ipv4: ip } }
84
- res["records"] || []
60
+ post_json "/v1/domains/list", json: { filter: { ipv4: ip } }
85
61
  end
86
62
 
87
63
  #
88
64
  # @param [String] domain
89
65
  # @param [String] type
66
+ # @param [Integer] page
90
67
  #
91
- # @return [Array<Hash>]
68
+ # @return [Enumerable<Hash>]
92
69
  #
93
- def get_all_dns_history(domain, type:)
94
- first_page = get_dns_history(domain, type: type, page: 1)
70
+ def get_all_dns_history(domain, type:, page: 1)
71
+ Enumerator.new do |y|
72
+ res = get_dns_history(domain, type: type, page: page)
73
+ y.yield res
95
74
 
96
- pages = first_page["pages"].to_i
97
- records = first_page["records"] || []
75
+ pages = res["pages"].to_i
98
76
 
99
- (2..pages).each do |page_idx|
100
- next_page = get_dns_history(domain, type: type, page: page_idx)
101
- records << next_page["records"]
77
+ (page + 1..pages).each do |page|
78
+ y.yield get_dns_history(domain, type: type, page: page)
79
+ end
102
80
  end
103
-
104
- records.flatten
105
81
  end
106
82
 
107
83
  private
@@ -111,7 +87,7 @@ module Mihari
111
87
  # @param [String] type
112
88
  # @param [Integer] page
113
89
  #
114
- # @return [Array<Hash>]
90
+ # @return [Hash]
115
91
  #
116
92
  def get_dns_history(domain, type:, page:)
117
93
  get_json "/v1/history/#{domain}/dns/#{type}", params: { page: page }
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Clients
5
+ #
6
+ # Shodan Internet DB API client
7
+ #
8
+ class ShodanInternetDB < Base
9
+ #
10
+ # @param [String] base_url
11
+ # @param [Hash] headers
12
+ # @param [Integer, nil] timeout
13
+ #
14
+ def initialize(base_url = "https://internetdb.shodan.io", headers: {}, timeout: nil)
15
+ super(base_url, headers: headers, timeout: timeout)
16
+ end
17
+
18
+ #
19
+ # @param [String] ip
20
+ #
21
+ # @return [Mihari::Structs::Shodan::InternetDBResponse]
22
+ #
23
+ def query(ip)
24
+ Structs::Shodan::InternetDBResponse.from_dynamic! get_json("/#{ip}")
25
+ end
26
+ end
27
+ end
28
+ end
@@ -6,20 +6,22 @@ module Mihari
6
6
  # TheHive API client
7
7
  #
8
8
  class TheHive < Base
9
+ attr_reader :api_version
10
+
9
11
  #
10
12
  # @param [String] base_url
11
13
  # @param [String, nil] api_key
12
- # @param [String, nil] api_version
14
+ # @param [String] api_version
13
15
  # @param [Hash] headers
14
16
  # @param [Integer, nil] timeout
15
17
  #
16
- def initialize(base_url, api_key:, api_version:, headers: {}, timeout: nil)
18
+ def initialize(base_url, api_key:, api_version: "v1", headers: {}, timeout: nil)
17
19
  raise(ArgumentError, "api_key is required") unless api_key
18
20
 
19
- base_url += "/#{api_version}" unless api_version.nil?
20
21
  headers["authorization"] = "Bearer #{api_key}"
21
-
22
22
  super(base_url, headers: headers, timeout: timeout)
23
+
24
+ @api_version = api_version
23
25
  end
24
26
 
25
27
  #
@@ -29,7 +31,7 @@ module Mihari
29
31
  #
30
32
  def alert(json)
31
33
  json = json.to_camelback_keys.compact
32
- post_json("/alert", json: json)
34
+ post_json("/api/#{api_version}/alert", json: json)
33
35
  end
34
36
  end
35
37
  end
@@ -7,30 +7,72 @@ module Mihari
7
7
  #
8
8
  module Alert
9
9
  class << self
10
+ # rubocop:disable Metrics/AbcSize
10
11
  def included(thor)
11
12
  thor.class_eval do
12
- include Dry::Monads[:result, :try]
13
- include Mixins
13
+ include Concerns::DatabaseConnectable
14
14
 
15
- desc "add [PATH]", "Add an alert"
15
+ desc "create [PATH]", "Create an alert"
16
16
  around :with_db_connection
17
17
  #
18
18
  # @param [String] path
19
19
  #
20
- def add(path)
21
- result = Dry::Monads::Try[StandardError] do
22
- # @type [Mihari::Services::AlertProxy]
23
- proxy = Mihari::Services::AlertBuilder.call(path)
24
- Mihari::Services::AlertRunner.call proxy
25
- end.to_result
26
-
20
+ def create(path)
27
21
  # @type [Mihari::Models::Alert]
28
- alert = result.value!
22
+ alert = Dry::Monads::Try[StandardError] do
23
+ raise ArgumentError, "#{path} not found" unless Pathname(path).exist?
24
+
25
+ params = YAML.safe_load(
26
+ ERB.new(File.read(path)).result,
27
+ permitted_classes: [Date, Symbol]
28
+ )
29
+ Services::AlertCreator.call params
30
+ end.value!
29
31
  data = Entities::Alert.represent(alert)
30
32
  puts JSON.pretty_generate(data.as_json)
31
33
  end
34
+
35
+ desc "list [QUERY]", "List/search alerts"
36
+ around :with_db_connection
37
+ method_option :page, type: :numeric, default: 1
38
+ method_option :limit, type: :numeric, default: 10
39
+ #
40
+ # @param [String] q
41
+ #
42
+ def list(q = "")
43
+ filter = Structs::Filters::Search.new(q: q, page: options["page"], limit: options["limit"])
44
+ value = Services::AlertSearcher.result(filter).value!
45
+ data = Entities::AlertsWithPagination.represent(
46
+ results: value.results,
47
+ total: value.total,
48
+ current_page: value.filter[:page].to_i,
49
+ page_size: value.filter[:limit].to_i
50
+ )
51
+ puts JSON.pretty_generate(data.as_json)
52
+ end
53
+
54
+ desc "get [ID]", "Get an alert"
55
+ around :with_db_connection
56
+ #
57
+ # @param [Integer] id
58
+ #
59
+ def get(id)
60
+ value = Services::AlertGetter.result(id).value!
61
+ data = Entities::Alert.represent(value)
62
+ puts JSON.pretty_generate(data.as_json)
63
+ end
64
+
65
+ desc "delete [ID]", "Delete an alert"
66
+ around :with_db_connection
67
+ #
68
+ # @param [Integer] id
69
+ #
70
+ def delete(id)
71
+ Services::AlertDestroyer.result(id).value!
72
+ end
32
73
  end
33
74
  end
75
+ # rubocop:enable Metrics/AbcSize
34
76
  end
35
77
  end
36
78
  end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Commands
5
+ #
6
+ # Artifact sub-commands
7
+ #
8
+ module Artifact
9
+ class << self
10
+ def included(thor)
11
+ thor.class_eval do
12
+ include Concerns::DatabaseConnectable
13
+
14
+ desc "list [QUERY]", "List/search artifacts"
15
+ around :with_db_connection
16
+ method_option :page, type: :numeric, default: 1
17
+ method_option :limit, type: :numeric, default: 10
18
+ #
19
+ # @param [String] q
20
+ #
21
+ def list(q = "")
22
+ filter = Structs::Filters::Search.new(q: q, page: options["page"], limit: options["limit"])
23
+ value = Services::ArtifactSearcher.result(filter).value!
24
+ data = Entities::ArtifactsWithPagination.represent(
25
+ results: value.results,
26
+ total: value.total,
27
+ current_page: value.filter[:page].to_i,
28
+ page_size: value.filter[:limit].to_i
29
+ )
30
+ puts JSON.pretty_generate(data.as_json)
31
+ end
32
+
33
+ desc "get [ID]", "Get an artifact"
34
+ around :with_db_connection
35
+ #
36
+ # @param [Integer] id
37
+ #
38
+ def get(id)
39
+ value = Services::ArtifactGetter.result(id).value!
40
+ data = Entities::Artifact.represent(value)
41
+ puts JSON.pretty_generate(data.as_json)
42
+ end
43
+
44
+ desc "enrich [ID]", "Enrich an artifact"
45
+ around :with_db_connection
46
+ #
47
+ # @param [Integer] id
48
+ #
49
+ def enrich(id)
50
+ Services::ArtifactEnricher.result(id).value!
51
+ end
52
+
53
+ desc "delete [ID]", "Delete an artifact"
54
+ around :with_db_connection
55
+ #
56
+ # @param [Integer] id
57
+ #
58
+ def delete(id)
59
+ Services::ArtifactDestroyer.result(id).value!
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end