mihari 5.3.2 → 5.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -19,7 +19,7 @@
19
19
  "@fortawesome/vue-fontawesome": "^3.0.3",
20
20
  "@vueuse/core": "^10.3.0",
21
21
  "@vueuse/router": "^10.3.0",
22
- "ace-builds": "^1.23.4",
22
+ "ace-builds": "^1.24.0",
23
23
  "axios": "^1.4.0",
24
24
  "bulma": "^0.9.4",
25
25
  "bulma-helpers": "^0.4.3",
@@ -37,14 +37,14 @@
37
37
  "vue3-ace-editor": "^2.2.3"
38
38
  },
39
39
  "devDependencies": {
40
- "@redocly/cli": "1.0.0",
41
- "@rushstack/eslint-patch": "^1.3.2",
40
+ "@redocly/cli": "1.0.2",
41
+ "@rushstack/eslint-patch": "^1.3.3",
42
42
  "@tsconfig/node20": "^20.1.1",
43
43
  "@types/jsdom": "^21.1.1",
44
- "@types/node": "^20.4.8",
44
+ "@types/node": "^20.4.9",
45
45
  "@types/url-parse": "^1.4.8",
46
- "@typescript-eslint/eslint-plugin": "^6.2.1",
47
- "@typescript-eslint/parser": "^6.2.1",
46
+ "@typescript-eslint/eslint-plugin": "^6.3.0",
47
+ "@typescript-eslint/parser": "^6.3.0",
48
48
  "@vitejs/plugin-vue": "^4.2.3",
49
49
  "@vue/eslint-config-prettier": "^8.0.0",
50
50
  "@vue/eslint-config-typescript": "^11.0.3",
@@ -54,7 +54,7 @@
54
54
  "eslint-config-prettier": "^9.0.0",
55
55
  "eslint-plugin-prettier": "^5.0.0",
56
56
  "eslint-plugin-simple-import-sort": "^10.0.0",
57
- "eslint-plugin-vue": "^9.16.1",
57
+ "eslint-plugin-vue": "^9.17.0",
58
58
  "husky": "^8.0.3",
59
59
  "jsdom": "^22.1.0",
60
60
  "npm-run-all": "^4.1.5",
@@ -25,21 +25,28 @@ module Mihari
25
25
  # @return [Integer, nil]
26
26
  #
27
27
  def interval
28
- @interval = options[:interval]
28
+ @interval ||= options[:interval]
29
29
  end
30
30
 
31
31
  #
32
32
  # @return [Integer]
33
33
  #
34
34
  def retry_interval
35
- @retry_interval ||= options[:retry_interval] || DEFAULT_RETRY_INTERVAL
35
+ @retry_interval ||= options[:retry_interval] || Mihari.config.retry_interval
36
36
  end
37
37
 
38
38
  #
39
39
  # @return [Integer]
40
40
  #
41
41
  def retry_times
42
- @retry_times ||= options[:retry_times] || DEFAULT_RETRY_TIMES
42
+ @retry_times ||= options[:retry_times] || Mihari.config.retry_times
43
+ end
44
+
45
+ #
46
+ # @return [Integer]
47
+ #
48
+ def pagination_limit
49
+ @pagination_limit ||= options[:pagination_limit] || Mihari.config.pagination_limit
43
50
  end
44
51
 
45
52
  # @return [Array<String>, Array<Mihari::Artifact>]
@@ -30,6 +30,10 @@ module Mihari
30
30
  end.flatten
31
31
  end
32
32
 
33
+ def configuration_keys
34
+ %w[binaryedge_api_key]
35
+ end
36
+
33
37
  private
34
38
 
35
39
  PAGE_SIZE = 20
@@ -56,7 +60,7 @@ module Mihari
56
60
  #
57
61
  def search
58
62
  responses = []
59
- (1..500).each do |page|
63
+ (1..pagination_limit).each do |page|
60
64
  res = search_with_page(page: page)
61
65
  total = res["total"].to_i
62
66
 
@@ -69,10 +73,6 @@ module Mihari
69
73
  responses
70
74
  end
71
75
 
72
- def configuration_keys
73
- %w[binaryedge_api_key]
74
- end
75
-
76
76
  #
77
77
  #
78
78
  # @return [Mihari::Clients::BinaryEdge]
@@ -30,7 +30,7 @@ module Mihari
30
30
  artifacts = []
31
31
 
32
32
  cursor = nil
33
- loop do
33
+ pagination_limit.times do
34
34
  response = client.search(query, cursor: cursor)
35
35
  artifacts << response.result.to_artifacts
36
36
  cursor = response.result.links.next
@@ -55,8 +55,6 @@ module Mihari
55
55
  configuration_keys? || (id? && secret?)
56
56
  end
57
57
 
58
- private
59
-
60
58
  #
61
59
  # @return [Array<String>]
62
60
  #
@@ -64,6 +62,8 @@ module Mihari
64
62
  %w[censys_id censys_secret]
65
63
  end
66
64
 
65
+ private
66
+
67
67
  #
68
68
  # @return [Mihari::Clients::Censys]
69
69
  #
@@ -44,12 +44,12 @@ module Mihari
44
44
  configuration_keys? || (username? && password?)
45
45
  end
46
46
 
47
- private
48
-
49
47
  def configuration_keys
50
48
  %w[circl_passive_password circl_passive_username]
51
49
  end
52
50
 
51
+ private
52
+
53
53
  def client
54
54
  @client ||= Clients::CIRCL.new(username: username, password: password)
55
55
  end
@@ -23,12 +23,12 @@ module Mihari
23
23
  client.gnql_search(query, size: PAGE_SIZE).to_artifacts
24
24
  end
25
25
 
26
- private
27
-
28
26
  def configuration_keys
29
27
  %w[greynoise_api_key]
30
28
  end
31
29
 
30
+ private
31
+
32
32
  def client
33
33
  @client ||= Clients::GreyNoise.new(api_key: api_key)
34
34
  end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Analyzers
5
+ class HunterHow < Base
6
+ # @return [String, nil]
7
+ attr_reader :api_key
8
+
9
+ # @return [Date]
10
+ attr_reader :start_time
11
+
12
+ # @return [Date]
13
+ attr_reader :end_time
14
+
15
+ #
16
+ # @param [String] query
17
+ # @param [Hash, nil] options
18
+ # @param [String, nil] api_key
19
+ #
20
+ def initialize(query, start_time:, end_time:, options: nil, api_key: nil)
21
+ super(query, options: options)
22
+
23
+ @api_key = api_key || Mihari.config.hunterhow_api_key
24
+
25
+ @start_time = start_time
26
+ @end_time = end_time
27
+ end
28
+
29
+ #
30
+ # @return [Array<Mihari::Artifact>]
31
+ #
32
+ def artifacts
33
+ artifacts = []
34
+
35
+ (1..pagination_limit).each do |page|
36
+ res = client.search(
37
+ query,
38
+ page: page,
39
+ page_size: PAGE_SIZE,
40
+ start_time: start_time.strftime("%Y-%m-%d"),
41
+ end_time: end_time.strftime("%Y-%m-%d")
42
+ )
43
+
44
+ artifacts << res.data.artifacts
45
+
46
+ break if res.data.list.length < PAGE_SIZE
47
+
48
+ sleep_interval
49
+ end
50
+
51
+ artifacts.flatten
52
+ end
53
+
54
+ def configuration_keys
55
+ %w[hunterhow_api_key]
56
+ end
57
+
58
+ private
59
+
60
+ # @return [Integer]
61
+ PAGE_SIZE = 100
62
+
63
+ def client
64
+ @client ||= Clients::HunterHow.new(api_key: api_key)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -26,14 +26,14 @@ module Mihari
26
26
  responses.map(&:to_artifacts).flatten
27
27
  end
28
28
 
29
- private
30
-
31
- PAGE_SIZE = 10
32
-
33
29
  def configuration_keys
34
30
  %w[onyphe_api_key]
35
31
  end
36
32
 
33
+ private
34
+
35
+ PAGE_SIZE = 10
36
+
37
37
  def client
38
38
  @client ||= Clients::Onyphe.new(api_key: api_key)
39
39
  end
@@ -57,7 +57,7 @@ module Mihari
57
57
  #
58
58
  def search
59
59
  responses = []
60
- (1..Float::INFINITY).each do |page|
60
+ (1..pagination_limit).each do |page|
61
61
  res = search_with_page(query, page: page)
62
62
  responses << res
63
63
 
@@ -35,12 +35,12 @@ module Mihari
35
35
  end
36
36
  end
37
37
 
38
- private
39
-
40
38
  def configuration_keys
41
39
  %w[otx_api_key]
42
40
  end
43
41
 
42
+ private
43
+
44
44
  def client
45
45
  @client ||= Mihari::Clients::OTX.new(api_key: api_key)
46
46
  end
@@ -46,12 +46,12 @@ module Mihari
46
46
  configuration_keys? || (username? && api_key?)
47
47
  end
48
48
 
49
- private
50
-
51
49
  def configuration_keys
52
50
  %w[passivetotal_username passivetotal_api_key]
53
51
  end
54
52
 
53
+ private
54
+
55
55
  def client
56
56
  @client ||= Clients::PassiveTotal.new(username: username, api_key: api_key)
57
57
  end
@@ -40,12 +40,12 @@ module Mihari
40
40
  end
41
41
  end
42
42
 
43
- private
44
-
45
43
  def configuration_keys
46
44
  %w[pulsedive_api_key]
47
45
  end
48
46
 
47
+ private
48
+
49
49
  def client
50
50
  @client ||= Clients::PulseDive.new(api_key: api_key)
51
51
  end
@@ -10,6 +10,7 @@ module Mihari
10
10
  "dnstwister" => DNSTwister,
11
11
  "feed" => Feed,
12
12
  "greynoise" => GreyNoise,
13
+ "hunterhow" => HunterHow,
13
14
  "onyphe" => Onyphe,
14
15
  "otx" => OTX,
15
16
  "passivetotal" => PassiveTotal,
@@ -54,12 +55,15 @@ module Mihari
54
55
  end
55
56
 
56
57
  #
57
- # Returns a list of artifacts matched with queries/analyzers
58
+ # Returns a list of artifacts matched with queries/analyzers (with the rule ID)
58
59
  #
59
60
  # @return [Array<Mihari::Artifact>]
60
61
  #
61
62
  def artifacts
62
- analyzers.flat_map(&:normalized_artifacts)
63
+ analyzers.flat_map(&:normalized_artifacts).map do |artifact|
64
+ artifact.rule_id = rule.id
65
+ artifact
66
+ end
63
67
  end
64
68
 
65
69
  #
@@ -72,14 +76,9 @@ module Mihari
72
76
  # @return [Array<Mihari::Artifact>]
73
77
  #
74
78
  def normalized_artifacts
75
- @normalized_artifacts ||= artifacts.uniq(&:data).select(&:valid?).select do |artifact|
76
- rule.data_types.include? artifact.data_type
77
- end.reject do |artifact|
78
- falsepositive? artifact.data
79
- end.map do |artifact|
80
- artifact.rule_id = rule.id
81
- artifact
82
- end
79
+ valid_artifacts = artifacts.uniq(&:data).select(&:valid?)
80
+ date_type_allowed_artifacts = valid_artifacts.select { |artifact| rule.data_types.include? artifact.data_type }
81
+ date_type_allowed_artifacts.reject { |artifact| falsepositive? artifact.data }
83
82
  end
84
83
 
85
84
  #
@@ -88,7 +87,7 @@ module Mihari
88
87
  # @return [Array<Mihari::Artifact>]
89
88
  #
90
89
  def unique_artifacts
91
- @unique_artifacts ||= normalized_artifacts.select do |artifact|
90
+ normalized_artifacts.select do |artifact|
92
91
  artifact.unique?(base_time: base_time, artifact_lifetime: rule.artifact_lifetime)
93
92
  end
94
93
  end
@@ -216,7 +215,10 @@ module Mihari
216
215
  #
217
216
  def validate_analyzer_configurations
218
217
  analyzers.map do |analyzer|
219
- raise ConfigurationError, "#{analyzer.source} is not configured correctly" unless analyzer.configured?
218
+ next if analyzer.configured?
219
+
220
+ message = "#{analyzer.source} is not configured correctly. #{analyzer.configuration_keys.join(", ")} is/are missing."
221
+ raise ConfigurationError, message
220
222
  end
221
223
  end
222
224
  end
@@ -40,12 +40,12 @@ module Mihari
40
40
  end
41
41
  end
42
42
 
43
- private
44
-
45
43
  def configuration_keys
46
44
  %w[securitytrails_api_key]
47
45
  end
48
46
 
47
+ private
48
+
49
49
  def client
50
50
  @client ||= Clients::SecurityTrails.new(api_key: api_key)
51
51
  end
@@ -24,14 +24,14 @@ module Mihari
24
24
  results.map(&:to_artifacts).flatten.uniq(&:data)
25
25
  end
26
26
 
27
- private
28
-
29
- PAGE_SIZE = 100
30
-
31
27
  def configuration_keys
32
28
  %w[shodan_api_key]
33
29
  end
34
30
 
31
+ private
32
+
33
+ PAGE_SIZE = 100
34
+
35
35
  def client
36
36
  @client ||= Clients::Shodan.new(api_key: api_key)
37
37
  end
@@ -54,7 +54,7 @@ module Mihari
54
54
  #
55
55
  def search
56
56
  responses = []
57
- (1..Float::INFINITY).each do |page|
57
+ (1..pagination_limit).each do |page|
58
58
  res = search_with_page(page: page)
59
59
  responses << res
60
60
  break if res.total <= page * PAGE_SIZE
@@ -39,12 +39,12 @@ module Mihari
39
39
  end
40
40
  end
41
41
 
42
- private
43
-
44
42
  def configuration_keys
45
43
  %w[urlscan_api_key]
46
44
  end
47
45
 
46
+ private
47
+
48
48
  def client
49
49
  @client ||= Clients::UrlScan.new(api_key: api_key)
50
50
  end
@@ -68,7 +68,7 @@ module Mihari
68
68
  responses = []
69
69
 
70
70
  search_after = nil
71
- loop do
71
+ pagination_limit.times do
72
72
  res = search_with_search_after(search_after: search_after)
73
73
  responses << res
74
74
 
@@ -35,12 +35,12 @@ module Mihari
35
35
  end
36
36
  end
37
37
 
38
- private
39
-
40
38
  def configuration_keys
41
39
  %w[virustotal_api_key]
42
40
  end
43
41
 
42
+ private
43
+
44
44
  def client
45
45
  @client = Clients::VirusTotal.new(api_key: api_key)
46
46
  end
@@ -21,12 +21,12 @@ module Mihari
21
21
  search_with_cursor.map(&:to_artifacts).flatten
22
22
  end
23
23
 
24
- private
25
-
26
24
  def configuration_keys
27
25
  %w[virustotal_api_key]
28
26
  end
29
27
 
28
+ private
29
+
30
30
  #
31
31
  # VT API
32
32
  #
@@ -45,7 +45,7 @@ module Mihari
45
45
  cursor = nil
46
46
  responses = []
47
47
 
48
- loop do
48
+ pagination_limit.times do
49
49
  response = Structs::VirusTotalIntelligence::Response.from_dynamic!(client.intel_search(query,
50
50
  cursor: cursor))
51
51
  responses << response
@@ -33,6 +33,10 @@ module Mihari
33
33
  end
34
34
  end
35
35
 
36
+ def configuration_keys
37
+ %w[zoomeye_api_key]
38
+ end
39
+
36
40
  private
37
41
 
38
42
  PAGE_SIZE = 10
@@ -46,10 +50,6 @@ module Mihari
46
50
  %w[host web].include? type
47
51
  end
48
52
 
49
- def configuration_keys
50
- %w[zoomeye_api_key]
51
- end
52
-
53
53
  def client
54
54
  @client ||= Clients::ZoomEye.new(api_key: api_key)
55
55
  end
@@ -95,7 +95,7 @@ module Mihari
95
95
  #
96
96
  def host_search
97
97
  responses = []
98
- (1..Float::INFINITY).each do |page|
98
+ (1..pagination_limit).each do |page|
99
99
  res = _host_search(query, page: page)
100
100
  break unless res
101
101
 
@@ -128,7 +128,7 @@ module Mihari
128
128
  #
129
129
  def web_search
130
130
  responses = []
131
- (1..Float::INFINITY).each do |page|
131
+ (1..pagination_limit).each do |page|
132
132
  res = _web_search(query, page: page)
133
133
  break unless res
134
134
 
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+
5
+ module Mihari
6
+ module Clients
7
+ class HunterHow < Base
8
+ # @return [String]
9
+ attr_reader :api_key
10
+
11
+ #
12
+ # @param [String] base_url
13
+ # @param [String, nil] api_key
14
+ # @param [Hash] headers
15
+ #
16
+ def initialize(base_url = "https://api.hunter.how/", api_key:, headers: {})
17
+ raise(ArgumentError, "'api_key' argument is required") unless api_key
18
+
19
+ super(base_url, headers: headers)
20
+
21
+ @api_key = api_key
22
+ end
23
+
24
+ #
25
+ # @param [String] query String used to query our data
26
+ # @param [Integer] page Default 1, Maximum: 500
27
+ # @param [Integer] page_size Default 100, Maximum: 100
28
+ # @param [String] start_time
29
+ # @param [String] end_time
30
+ #
31
+ # @return [Structs::HunterHow::Response]
32
+ #
33
+ def search(query, start_time:, end_time:, page: 1, page_size: 10)
34
+ params = {
35
+ query: Base64.urlsafe_encode64(query),
36
+ page: page,
37
+ page_size: page_size,
38
+ start_time: start_time,
39
+ end_time: end_time,
40
+ "api-key": api_key
41
+ }.compact
42
+ res = get("/search", params: params)
43
+ Structs::HunterHow::Response.from_dynamic! JSON.parse(res.body.to_s)
44
+ end
45
+ end
46
+ end
47
+ end