mihari 3.9.2 → 3.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/docker/Dockerfile +1 -1
  4. data/lib/mihari/analyzers/base.rb +1 -1
  5. data/lib/mihari/analyzers/binaryedge.rb +5 -0
  6. data/lib/mihari/analyzers/censys.rb +5 -0
  7. data/lib/mihari/analyzers/feed.rb +39 -0
  8. data/lib/mihari/analyzers/greynoise.rb +65 -0
  9. data/lib/mihari/analyzers/onyphe.rb +5 -0
  10. data/lib/mihari/analyzers/rule.rb +8 -0
  11. data/lib/mihari/analyzers/shodan.rb +16 -5
  12. data/lib/mihari/analyzers/urlscan.rb +37 -13
  13. data/lib/mihari/analyzers/virustotal_intelligence.rb +5 -0
  14. data/lib/mihari/analyzers/zoomeye.rb +8 -0
  15. data/lib/mihari/cli/analyzer.rb +5 -0
  16. data/lib/mihari/commands/feed.rb +26 -0
  17. data/lib/mihari/commands/greynoise.rb +21 -0
  18. data/lib/mihari/commands/urlscan.rb +1 -2
  19. data/lib/mihari/database.rb +4 -4
  20. data/lib/mihari/enrichers/ipinfo.rb +2 -0
  21. data/lib/mihari/errors.rb +4 -0
  22. data/lib/mihari/feed/parser.rb +34 -0
  23. data/lib/mihari/feed/reader.rb +113 -0
  24. data/lib/mihari/mixins/autonomous_system.rb +2 -0
  25. data/lib/mihari/mixins/configuration.rb +2 -2
  26. data/lib/mihari/mixins/disallowed_data_value.rb +2 -0
  27. data/lib/mihari/mixins/rule.rb +2 -0
  28. data/lib/mihari/models/alert.rb +4 -5
  29. data/lib/mihari/models/artifact.rb +2 -3
  30. data/lib/mihari/models/dns.rb +2 -2
  31. data/lib/mihari/schemas/configuration.rb +3 -2
  32. data/lib/mihari/schemas/rule.rb +21 -2
  33. data/lib/mihari/status.rb +2 -2
  34. data/lib/mihari/structs/alert.rb +3 -1
  35. data/lib/mihari/structs/censys.rb +2 -0
  36. data/lib/mihari/structs/greynoise.rb +57 -0
  37. data/lib/mihari/structs/ipinfo.rb +2 -0
  38. data/lib/mihari/structs/onyphe.rb +2 -0
  39. data/lib/mihari/structs/shodan.rb +8 -6
  40. data/lib/mihari/structs/urlscan.rb +53 -0
  41. data/lib/mihari/structs/virustotal_intelligence.rb +2 -0
  42. data/lib/mihari/types.rb +10 -0
  43. data/lib/mihari/version.rb +1 -1
  44. data/lib/mihari/web/api.rb +2 -0
  45. data/lib/mihari/web/entities/artifact.rb +2 -2
  46. data/lib/mihari/web/entities/whois.rb +1 -1
  47. data/lib/mihari/web/public/index.html +1 -1
  48. data/lib/mihari/web/public/redoc-static.html +181 -183
  49. data/lib/mihari/web/public/static/js/app.0a0cc502.js +21 -0
  50. data/lib/mihari/web/public/static/js/app.0a0cc502.js.map +1 -0
  51. data/lib/mihari/web/public/static/js/app.5dc97aae.js +21 -0
  52. data/lib/mihari/web/public/static/js/app.5dc97aae.js.map +1 -0
  53. data/lib/mihari/web/public/static/js/app.f2b8890f.js +21 -0
  54. data/lib/mihari/web/public/static/js/app.f2b8890f.js.map +1 -0
  55. data/lib/mihari/web/public/static/js/app.fbc19869.js +21 -0
  56. data/lib/mihari/web/public/static/js/app.fbc19869.js.map +1 -0
  57. data/lib/mihari.rb +7 -2
  58. data/mihari.gemspec +22 -21
  59. data/sig/lib/mihari/analyzers/binaryedge.rbs +2 -0
  60. data/sig/lib/mihari/analyzers/censys.rbs +2 -0
  61. data/sig/lib/mihari/analyzers/feed.rbs +23 -0
  62. data/sig/lib/mihari/analyzers/onyphe.rbs +2 -0
  63. data/sig/lib/mihari/analyzers/shodan.rbs +2 -0
  64. data/sig/lib/mihari/analyzers/urlscan.rbs +5 -2
  65. data/sig/lib/mihari/analyzers/virustotal_intelligence.rbs +2 -0
  66. data/sig/lib/mihari/analyzers/zoomeye.rbs +2 -0
  67. data/sig/lib/mihari/cli/analyzer.rbs +4 -0
  68. data/sig/lib/mihari/commands/feed.rbs +7 -0
  69. data/sig/lib/mihari/feed/parser.rbs +11 -0
  70. data/sig/lib/mihari/feed/reader.rbs +56 -0
  71. data/sig/lib/mihari/structs/alert.rbs +1 -1
  72. data/sig/lib/mihari/structs/greynoise.rbs +30 -0
  73. data/sig/lib/mihari/structs/shodan.rbs +3 -3
  74. data/sig/lib/mihari/structs/urlscan.rbs +28 -0
  75. data/sig/lib/mihari/types.rbs +4 -0
  76. metadata +120 -84
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c12a4ec4b0c1eee79deba3e5f3a511edcea4a13d7d72dc8142dbd85821095f55
4
- data.tar.gz: 4a1e388e55efff4b715caf2261990e6122afd4eb26aca0874a33d3025a16ba5b
3
+ metadata.gz: 5a17c14dffe68b2f82316858615371036996a3a2b6d31d3c6a5c431294c38634
4
+ data.tar.gz: 97afaf2f8b20985b0908cc1fe5778fe75ab7d0e1bf13b990a7b4165c92843604
5
5
  SHA512:
6
- metadata.gz: 8ead20235892bf77e222eff200f25539ffdd68cd21d14c5000d9196e518b9d2da2b461a2fe5d3dae4bc4bd899fb210f5f75b6330afad61826bc84dcfe40af23f
7
- data.tar.gz: 4b5679794091a79adb426ef34c5396819c702d67ad84791dec1430eba0f007d1dcab5f7defc53b5dab06e8a0e174963c777fb05606f79a5f16c4377f07847f75
6
+ metadata.gz: 418d7f278536e245196723d2ebbe0d99934b0db3278e8f8178470241c67f25e05d0aacf7bab2b766ce69d0589a79e87eca554a91fe4cfae5cfa12cd52d97fb61
7
+ data.tar.gz: 9e90193273ee40c9956a138c2c73b713eb610a3ebdc1c9def73a6d354c4124a97e6842e438004956b3edc3206dfee8bae5771a86e964b59a2a7065a1f910891f
data/README.md CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/mihari.svg)](https://badge.fury.io/rb/mihari)
4
4
  [![Ruby CI](https://github.com/ninoseki/mihari/actions/workflows/test.yml/badge.svg)](https://github.com/ninoseki/mihari/actions/workflows/test.yml)
5
- [![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/ninoseki/mihari)](https://hub.docker.com/r/ninoseki/mihari)
6
5
  [![Coverage Status](https://coveralls.io/repos/github/ninoseki/mihari/badge.svg?branch=master)](https://coveralls.io/github/ninoseki/mihari?branch=master)
7
6
  [![CodeFactor](https://www.codefactor.io/repository/github/ninoseki/mihari/badge)](https://www.codefactor.io/repository/github/ninoseki/mihari)
8
7
 
@@ -38,6 +37,7 @@ Mihari supports the following services by default.
38
37
  - [crt.sh](https://crt.sh/)
39
38
  - [DN Pedia](https://dnpedia.com/)
40
39
  - [dnstwister](https://dnstwister.report/)
40
+ - [GreyNoise](https://www.greynoise.io/)
41
41
  - [Onyphe](https://onyphe.io)
42
42
  - [OTX](https://otx.alienvault.com/)
43
43
  - [PassiveTotal](https://community.riskiq.com/)
data/docker/Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- FROM ruby:3.0.2-alpine3.13
1
+ FROM ruby:3.0.3-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 \
@@ -111,7 +111,7 @@ module Mihari
111
111
  # @return [Array<Mihari::Artifact>]
112
112
  #
113
113
  def enriched_artifacts
114
- @enriched_artifacts ||= unique_artifacts.map do |artifact|
114
+ @enriched_artifacts ||= Parallel.map(unique_artifacts) do |artifact|
115
115
  artifact.enrich_all
116
116
  artifact
117
117
  end
@@ -10,6 +10,8 @@ module Mihari
10
10
  option :description, default: proc { "query = #{query}" }
11
11
  option :tags, default: proc { [] }
12
12
 
13
+ option :interval, default: proc { 0 }
14
+
13
15
  def artifacts
14
16
  results = search
15
17
  return [] unless results || results.empty?
@@ -55,6 +57,9 @@ module Mihari
55
57
 
56
58
  responses << res
57
59
  break if total <= page * PAGE_SIZE
60
+
61
+ # sleep #{interval} seconds to avoid the rate limitation (if it is set)
62
+ sleep interval
58
63
  end
59
64
  responses
60
65
  end
@@ -10,6 +10,8 @@ module Mihari
10
10
  option :description, default: proc { "query = #{query}" }
11
11
  option :tags, default: proc { [] }
12
12
 
13
+ option :interval, default: proc { 0 }
14
+
13
15
  def artifacts
14
16
  search
15
17
  end
@@ -33,6 +35,9 @@ module Mihari
33
35
 
34
36
  cursor = response.result.links.next
35
37
  break if cursor == ""
38
+
39
+ # sleep #{interval} seconds to avoid the rate limitation (if it is set)
40
+ sleep interval
36
41
  end
37
42
 
38
43
  artifacts.flatten.uniq(&:data)
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mihari/feed/reader"
4
+ require "mihari/feed/parser"
5
+
6
+ module Mihari
7
+ module Analyzers
8
+ class Feed < Base
9
+ param :query
10
+ option :title, default: proc { "Feed" }
11
+ option :description, default: proc { "query = #{query}" }
12
+ option :tags, default: proc { [] }
13
+
14
+ option :http_request_method, default: proc { "GET" }
15
+ option :http_request_headers, default: proc { {} }
16
+ option :http_request_payload, default: proc { {} }
17
+ option :http_request_payload_type, default: proc {}
18
+
19
+ option :selector, default: proc { "" }
20
+
21
+ def artifacts
22
+ Mihari::Feed::Parser.new(data).parse selector
23
+ end
24
+
25
+ private
26
+
27
+ def data
28
+ reader = Mihari::Feed::Reader.new(
29
+ query,
30
+ http_request_method: http_request_method,
31
+ http_request_headers: http_request_headers,
32
+ http_request_payload: http_request_payload,
33
+ http_request_payload_type: http_request_payload_type
34
+ )
35
+ reader.read
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "greynoise"
4
+
5
+ module Mihari
6
+ module Analyzers
7
+ class GreyNoise < Base
8
+ param :query
9
+ option :title, default: proc { "GreyNoise search" }
10
+ option :description, default: proc { "query = #{query}" }
11
+ option :tags, default: proc { [] }
12
+
13
+ def artifacts
14
+ res = Structs::GreyNoise::Response.from_dynamic!(search)
15
+ res.data.map do |datum|
16
+ build_artifact datum
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ PAGE_SIZE = 10_000
23
+
24
+ def configuration_keys
25
+ %w[greynoise_api_key]
26
+ end
27
+
28
+ def api
29
+ @api ||= ::GreyNoise::API.new(key: Mihari.config.greynoise_api_key)
30
+ end
31
+
32
+ #
33
+ # Search
34
+ #
35
+ # @return [Hash]
36
+ #
37
+ def search
38
+ api.experimental.gnql(query, size: PAGE_SIZE)
39
+ end
40
+
41
+ #
42
+ # Build an artifact from a GreyNoise search API response
43
+ #
44
+ # @param [Structs::GreyNoise::Datum] datum
45
+ #
46
+ # @return [Artifact]
47
+ #
48
+ def build_artifact(datum)
49
+ as = AutonomousSystem.new(asn: normalize_asn(datum.metadata.asn))
50
+
51
+ geolocation = Geolocation.new(
52
+ country: datum.metadata.country,
53
+ country_code: datum.metadata.country_code
54
+ )
55
+
56
+ Artifact.new(
57
+ data: datum.ip,
58
+ source: source,
59
+ autonomous_system: as,
60
+ geolocation: geolocation
61
+ )
62
+ end
63
+ end
64
+ end
65
+ end
@@ -11,6 +11,8 @@ module Mihari
11
11
  option :description, default: proc { "query = #{query}" }
12
12
  option :tags, default: proc { [] }
13
13
 
14
+ option :interval, default: proc { 0 }
15
+
14
16
  def artifacts
15
17
  responses = search
16
18
  return [] unless responses
@@ -59,6 +61,9 @@ module Mihari
59
61
 
60
62
  total = res.total
61
63
  break if total <= page * PAGE_SIZE
64
+
65
+ # sleep #{interval} seconds to avoid the rate limitation (if it is set)
66
+ sleep interval
62
67
  end
63
68
  responses
64
69
  end
@@ -11,6 +11,8 @@ module Mihari
11
11
  "crtsh" => Crtsh,
12
12
  "dnpedia" => DNPedia,
13
13
  "dnstwister" => DNSTwister,
14
+ "feed" => Feed,
15
+ "greynoise" => GreyNoise,
14
16
  "onyphe" => Onyphe,
15
17
  "otx" => OTX,
16
18
  "passivetotal" => PassiveTotal,
@@ -63,6 +65,12 @@ module Mihari
63
65
  klass = get_analyzer_class(analyzer_name)
64
66
 
65
67
  query = params[:query]
68
+
69
+ # set interval in the top level
70
+ options = params[:options] || {}
71
+ interval = options[:interval]
72
+ params[:interval] = interval
73
+
66
74
  analyzer = klass.new(query, **params)
67
75
 
68
76
  # Use #normalized_artifacts method to get atrifacts as Array<Mihari::Artifact>
@@ -10,6 +10,8 @@ module Mihari
10
10
  option :description, default: proc { "query = #{query}" }
11
11
  option :tags, default: proc { [] }
12
12
 
13
+ option :interval, default: proc { 0 }
14
+
13
15
  def artifacts
14
16
  results = search
15
17
  return [] unless results || results.empty?
@@ -58,10 +60,14 @@ module Mihari
58
60
  responses = []
59
61
  (1..Float::INFINITY).each do |page|
60
62
  res = search_with_page(query, page: page)
63
+
61
64
  break unless res
62
65
 
63
66
  responses << res
64
67
  break if res["total"].to_i <= page * PAGE_SIZE
68
+
69
+ # sleep #{interval} seconds to avoid the rate limitation (if it is set)
70
+ sleep interval
65
71
  rescue JSON::ParserError
66
72
  # ignore JSON::ParserError
67
73
  # ref. https://github.com/ninoseki/mihari/issues/197
@@ -78,11 +84,16 @@ module Mihari
78
84
  # @return [Artifact]
79
85
  #
80
86
  def build_artifact(match)
81
- as = AutonomousSystem.new(asn: normalize_asn(match.asn))
82
- geolocation = Geolocation.new(
83
- country: match.location.country_name,
84
- country_code: match.location.country_code
85
- )
87
+ as = nil
88
+ as = AutonomousSystem.new(asn: normalize_asn(match.asn)) unless match.asn.nil?
89
+
90
+ geolocation = nil
91
+ if !match.location.country_name.nil? && !match.location.country_code.nil?
92
+ geolocation = Geolocation.new(
93
+ country: match.location.country_name,
94
+ country_code: match.location.country_code
95
+ )
96
+ end
86
97
 
87
98
  Artifact.new(
88
99
  data: match.ip_str,
@@ -2,8 +2,6 @@
2
2
 
3
3
  require "urlscan"
4
4
 
5
- SUPPORTED_DATA_TYPES = %w[url domain ip].freeze
6
-
7
5
  module Mihari
8
6
  module Analyzers
9
7
  class Urlscan < Base
@@ -12,7 +10,11 @@ module Mihari
12
10
  option :description, default: proc { "query = #{query}" }
13
11
  option :tags, default: proc { [] }
14
12
  option :allowed_data_types, default: proc { SUPPORTED_DATA_TYPES }
15
- option :use_similarity, default: proc { false }
13
+
14
+ option :interval, default: proc { 0 }
15
+
16
+ SUPPORTED_DATA_TYPES = %w[url domain ip].freeze
17
+ SIZE = 1000
16
18
 
17
19
  def initialize(*args, **kwargs)
18
20
  super
@@ -21,16 +23,15 @@ module Mihari
21
23
  end
22
24
 
23
25
  def artifacts
24
- result = search
25
- return [] unless result
26
-
27
- results = result["results"] || []
26
+ responses = search
27
+ results = responses.map(&:results).flatten
28
28
 
29
29
  allowed_data_types.map do |type|
30
- results.filter_map do |match|
31
- match.dig "page", type
30
+ results.filter_map do |result|
31
+ page = result.page
32
+ page.send(type.to_sym)
32
33
  end.uniq
33
- end.flatten
34
+ end.flatten.compact
34
35
  end
35
36
 
36
37
  private
@@ -43,15 +44,38 @@ module Mihari
43
44
  @api ||= ::UrlScan::API.new(Mihari.config.urlscan_api_key)
44
45
  end
45
46
 
47
+ #
48
+ # Search with search_after option
49
+ #
50
+ # @return [Structs::Urlscan::Response]
51
+ #
52
+ def search_with_search_after(search_after: nil)
53
+ res = api.search(query, size: SIZE, search_after: search_after)
54
+ Structs::Urlscan::Response.from_dynamic! res
55
+ end
56
+
46
57
  #
47
58
  # Search
48
59
  #
49
- # @return [Array<Hash>]
60
+ # @return [Array<Structs::Urlscan::Response>]
50
61
  #
51
62
  def search
52
- return api.pro.similar(query) if use_similarity
63
+ responses = []
64
+
65
+ search_after = nil
66
+ loop do
67
+ res = search_with_search_after(search_after: search_after)
68
+ responses << res
69
+
70
+ break if res.results.length < SIZE
71
+
72
+ search_after = res.results.last.sort.join(",")
73
+
74
+ # sleep #{interval} seconds to avoid the rate limitation (if it is set)
75
+ sleep interval
76
+ end
53
77
 
54
- api.search(query, size: 10_000)
78
+ responses
55
79
  end
56
80
 
57
81
  #
@@ -10,6 +10,8 @@ module Mihari
10
10
  option :description, default: proc { "query = #{query}" }
11
11
  option :tags, default: proc { [] }
12
12
 
13
+ option :interval, default: proc { 0 }
14
+
13
15
  def initialize(*args, **kwargs)
14
16
  super
15
17
 
@@ -54,6 +56,9 @@ module Mihari
54
56
  break if response.meta.cursor.nil?
55
57
 
56
58
  cursor = response.meta.cursor
59
+
60
+ # sleep #{interval} seconds to avoid the rate limitation (if it is set)
61
+ sleep interval
57
62
  end
58
63
 
59
64
  responses
@@ -11,6 +11,8 @@ module Mihari
11
11
  option :tags, default: proc { [] }
12
12
  option :type, default: proc { "host" }
13
13
 
14
+ option :interval, default: proc { 0 }
15
+
14
16
  def artifacts
15
17
  case type
16
18
  when "host"
@@ -87,6 +89,9 @@ module Mihari
87
89
  total = res["total"].to_i
88
90
  responses << res
89
91
  break if total <= page * PAGE_SIZE
92
+
93
+ # sleep #{interval} seconds to avoid the rate limitation (if it is set)
94
+ sleep interval
90
95
  end
91
96
  convert_responses responses.compact
92
97
  end
@@ -119,6 +124,9 @@ module Mihari
119
124
  total = res["total"].to_i
120
125
  responses << res
121
126
  break if total <= page * PAGE_SIZE
127
+
128
+ # sleep #{interval} seconds to avoid the rate limitation (if it is set)
129
+ sleep interval
122
130
  end
123
131
  convert_responses responses.compact
124
132
  end
@@ -6,6 +6,8 @@ require "mihari/commands/circl"
6
6
  require "mihari/commands/crtsh"
7
7
  require "mihari/commands/dnpedia"
8
8
  require "mihari/commands/dnstwister"
9
+ require "mihari/commands/feed"
10
+ require "mihari/commands/greynoise"
9
11
  require "mihari/commands/onyphe"
10
12
  require "mihari/commands/otx"
11
13
  require "mihari/commands/passivetotal"
@@ -25,6 +27,7 @@ module Mihari
25
27
  class Analyzer < Base
26
28
  class_option :ignore_old_artifacts, type: :boolean, default: false, desc: "Whether to ignore old artifacts from checking or not."
27
29
  class_option :ignore_threshold, type: :numeric, default: 0, desc: "Number of days to define whether an artifact is old or not."
30
+ class_option :interval, type: :numeric, default: 0, desc: "Seconds of the interval while calling API in a row."
28
31
  class_option :config, type: :string, desc: "Path to the config file"
29
32
 
30
33
  include Mihari::Commands::BinaryEdge
@@ -33,6 +36,8 @@ module Mihari
33
36
  include Mihari::Commands::Crtsh
34
37
  include Mihari::Commands::DNPedia
35
38
  include Mihari::Commands::DNSTwister
39
+ include Mihari::Commands::Feed
40
+ include Mihari::Commands::GreyNoise
36
41
  include Mihari::Commands::JSON
37
42
  include Mihari::Commands::Onyphe
38
43
  include Mihari::Commands::OTX
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Commands
5
+ module Feed
6
+ def self.included(thor)
7
+ thor.class_eval do
8
+ desc "feed [URL]", "ingest feed"
9
+ method_option :title, type: :string, desc: "title"
10
+ method_option :description, type: :string, desc: "description"
11
+ method_option :tags, type: :array, desc: "tags"
12
+ method_option :http_request_method, type: :string, desc: "HTTP request method"
13
+ method_option :http_request_headers, type: :hash, desc: "HTTP request headers"
14
+ method_option :http_request_payload_type, type: :string, desc: "HTTP request payload type"
15
+ method_option :http_request_payload, type: :hash, desc: "HTTP request payload"
16
+ method_option :selector, type: :string, desc: "jr selector", required: true
17
+ def feed(query)
18
+ with_error_handling do
19
+ run_analyzer Analyzers::Feed, query: query, options: options
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mihari
4
+ module Commands
5
+ module GreyNoise
6
+ def self.included(thor)
7
+ thor.class_eval do
8
+ desc "greynoise [QUERY]", "GreyNoise search"
9
+ method_option :title, type: :string, desc: "title"
10
+ method_option :description, type: :string, desc: "description"
11
+ method_option :tags, type: :array, desc: "tags"
12
+ def greynoise(query)
13
+ with_error_handling do
14
+ run_analyzer Analyzers::GreyNoise, query: query, options: options
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -9,8 +9,7 @@ module Mihari
9
9
  method_option :title, type: :string, desc: "title"
10
10
  method_option :description, type: :string, desc: "description"
11
11
  method_option :tags, type: :array, desc: "tags"
12
- method_option :target_type, type: :string, default: "url", desc: "target type to fetch from search results (target type should be 'url', 'domain' or 'ip')"
13
- method_option :use_similarity, type: :boolean, default: false, desc: "use similarity API or not"
12
+ method_option :allowed_data_types, type: :array, default: ["url", "ip", "domain"], desc: "types to fetch from search results ('url', 'domain' or 'ip')"
14
13
  def urlscan(query)
15
14
  with_error_handling do
16
15
  run_analyzer Analyzers::Urlscan, query: query, options: options
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "active_record"
4
4
 
5
- class InitialSchema < ActiveRecord::Migration[6.1]
5
+ class InitialSchema < ActiveRecord::Migration[7.0]
6
6
  def change
7
7
  create_table :tags, if_not_exists: true do |t|
8
8
  t.string :name, null: false
@@ -32,13 +32,13 @@ class InitialSchema < ActiveRecord::Migration[6.1]
32
32
  end
33
33
  end
34
34
 
35
- class AddeSourceToArtifactSchema < ActiveRecord::Migration[6.1]
35
+ class AddeSourceToArtifactSchema < ActiveRecord::Migration[7.0]
36
36
  def change
37
37
  add_column :artifacts, :source, :string, if_not_exists: true
38
38
  end
39
39
  end
40
40
 
41
- class EnrichmentsSchema < ActiveRecord::Migration[6.1]
41
+ class EnrichmentsSchema < ActiveRecord::Migration[7.0]
42
42
  def change
43
43
  create_table :autonomous_systems, if_not_exists: true do |t|
44
44
  t.integer :asn, null: false
@@ -74,7 +74,7 @@ class EnrichmentsSchema < ActiveRecord::Migration[6.1]
74
74
  end
75
75
  end
76
76
 
77
- class EnrichmentCreatedAtSchema < ActiveRecord::Migration[6.1]
77
+ class EnrichmentCreatedAtSchema < ActiveRecord::Migration[7.0]
78
78
  def change
79
79
  # Add created_at column because now it is able to enrich an atrifact after the creation
80
80
  add_column :autonomous_systems, :created_at, :datetime, if_not_exists: true
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "http"
2
4
  require "json"
3
5
  require "memist"
data/lib/mihari/errors.rb CHANGED
@@ -8,4 +8,8 @@ module Mihari
8
8
  class RetryableError < Error; end
9
9
 
10
10
  class FileNotFoundError < Error; end
11
+
12
+ class HttpError < Error; end
13
+
14
+ class FeedParseError < Error; end
11
15
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jr/cli/core_ext"
4
+
5
+ module Mihari
6
+ module Feed
7
+ class Parser
8
+ attr_reader :data
9
+
10
+ #
11
+ # @param [Array<Hash>, Array<Array<String>>] data
12
+ #
13
+ def initialize(data)
14
+ @data = data
15
+ end
16
+
17
+ #
18
+ # Parse data by selector
19
+ #
20
+ # @param [String] selector
21
+ #
22
+ # @return [Array<String>]
23
+ #
24
+ def parse(selector)
25
+ parsed = data.instance_eval(selector)
26
+
27
+ raise FeedParseError unless parsed.is_a?(Array) || parsed.is_a?(Enumerator)
28
+ raise FeedParseError unless parsed.all?(String)
29
+
30
+ parsed.to_a
31
+ end
32
+ end
33
+ end
34
+ end