mihari 1.3.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +44 -0
  3. data/README.md +7 -7
  4. data/Rakefile +1 -0
  5. data/docker/Dockerfile +1 -1
  6. data/lib/mihari/alert_viewer.rb +3 -3
  7. data/lib/mihari/analyzers/base.rb +1 -1
  8. data/lib/mihari/analyzers/basic.rb +3 -4
  9. data/lib/mihari/analyzers/binaryedge.rb +8 -7
  10. data/lib/mihari/analyzers/censys.rb +3 -7
  11. data/lib/mihari/analyzers/circl.rb +3 -5
  12. data/lib/mihari/analyzers/crtsh.rb +2 -6
  13. data/lib/mihari/analyzers/dnpedia.rb +3 -6
  14. data/lib/mihari/analyzers/dnstwister.rb +4 -9
  15. data/lib/mihari/analyzers/free_text.rb +2 -6
  16. data/lib/mihari/analyzers/http_hash.rb +3 -11
  17. data/lib/mihari/analyzers/onyphe.rb +3 -6
  18. data/lib/mihari/analyzers/otx.rb +4 -9
  19. data/lib/mihari/analyzers/passive_dns.rb +4 -9
  20. data/lib/mihari/analyzers/passive_ssl.rb +4 -9
  21. data/lib/mihari/analyzers/passivetotal.rb +9 -14
  22. data/lib/mihari/analyzers/pulsedive.rb +7 -12
  23. data/lib/mihari/analyzers/reverse_whois.rb +4 -9
  24. data/lib/mihari/analyzers/securitytrails.rb +12 -17
  25. data/lib/mihari/analyzers/securitytrails_domain_feed.rb +3 -7
  26. data/lib/mihari/analyzers/shodan.rb +9 -8
  27. data/lib/mihari/analyzers/spyse.rb +6 -11
  28. data/lib/mihari/analyzers/ssh_fingerprint.rb +2 -6
  29. data/lib/mihari/analyzers/urlscan.rb +25 -9
  30. data/lib/mihari/analyzers/virustotal.rb +6 -11
  31. data/lib/mihari/analyzers/zoomeye.rb +7 -11
  32. data/lib/mihari/cli.rb +14 -7
  33. data/lib/mihari/config.rb +2 -24
  34. data/lib/mihari/database.rb +1 -1
  35. data/lib/mihari/emitters/misp.rb +4 -2
  36. data/lib/mihari/emitters/slack.rb +18 -7
  37. data/lib/mihari/emitters/the_hive.rb +2 -2
  38. data/lib/mihari/errors.rb +3 -0
  39. data/lib/mihari/models/artifact.rb +1 -1
  40. data/lib/mihari/notifiers/exception_notifier.rb +5 -5
  41. data/lib/mihari/retriable.rb +1 -1
  42. data/lib/mihari/status.rb +1 -1
  43. data/lib/mihari/type_checker.rb +4 -4
  44. data/lib/mihari/version.rb +1 -1
  45. data/mihari.gemspec +22 -23
  46. metadata +37 -51
  47. data/.travis.yml +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6d6b5d42dc4fbe937cc101f7dbdfa1491104f2b6f70fdf690b2ad51db02304c5
4
- data.tar.gz: 32b5df5f6970d6aaf6f9d5c57ca9bc86d8e43f0298f22c6c9e811cf60a6d1089
3
+ metadata.gz: 70afeb6b1ddaa263689beb836de85264ad1e871a1887a0574adfc22f00e006d8
4
+ data.tar.gz: ad614363a9a3320c2dfa34ec19bd4712d08a9a6e662cdf11b4b70c775f55e592
5
5
  SHA512:
6
- metadata.gz: 16cdfd458c0d464eb618fc5aae8386c1cb37fd554fd6aba275323cb539a08916f3cd855e4e5683085e03f206debe879af6f2e3c7a42b1cd0b2a6395d86e3f55a
7
- data.tar.gz: 93f9e9e2165e79ef6602d1628b5689e0993ae4d88605db4f6aa4ff79dc6a21d25437997a1402f76ce3a5e2e9155ad68a441a8cc8351b54c5f4d1c3a72eb82df4
6
+ metadata.gz: 8b7b9b86a6ec5341ce03b5652e29d45ccfafcd6ee09ca469ccf88d9872a965aded13fae1925c647cbbdedb389532469060698e35a06905197ffb143b223d0a93
7
+ data.tar.gz: 5d839d16358cf855658bc85d8e30868ce9fcae63cf9064461e67343cd979cee90daad2822260d96da9f00acadb9f981e29de89eb07b35047f9cd3ae039a37c3e
@@ -0,0 +1,44 @@
1
+ name: Ruby CI
2
+
3
+ on: [pull_request]
4
+
5
+ jobs:
6
+ build:
7
+
8
+ runs-on: ubuntu-latest
9
+
10
+ services:
11
+ db:
12
+ image: postgres:12
13
+ env:
14
+ POSTGRES_USER: postgres
15
+ POSTGRES_PASSWORD: postgres
16
+ POSTGRES_DB: test
17
+ options: >-
18
+ --health-cmd pg_isready
19
+ --health-interval 10s
20
+ --health-timeout 5s
21
+ --health-retries 5
22
+ ports:
23
+ - 5432:5432
24
+
25
+ strategy:
26
+ fail-fast: false
27
+ matrix:
28
+ ruby: [2.7, '3.0']
29
+
30
+ steps:
31
+ - uses: actions/checkout@v2
32
+ - name: Set up Ruby 2.7
33
+ uses: actions/setup-ruby@v1
34
+ with:
35
+ ruby-version: ${{ matrix.ruby }}
36
+ bundler-cache: true
37
+ - name: Build and test with Rake
38
+ env:
39
+ DATABASE: postgresql://postgres:postgres@localhost:5432/test
40
+ run: |
41
+ sudo apt-get -yqq install libpq-dev
42
+ gem install bundler
43
+ bundle install
44
+ bundle exec rake
data/README.md CHANGED
@@ -10,12 +10,12 @@ Mihari is a helper to run queries & manage results continuously. Mihari can be u
10
10
 
11
11
  ## How it works
12
12
 
13
- - Mihari makes a query against Shodan, Censys, VirusTotal, SecurityTrails, etc. and extracts artifacts (IP addresses, domains, URLs and hashes) from the results.
13
+ - Mihari makes a query against Shodan, Censys, VirusTotal, SecurityTrails, etc. and extracts artifacts (IP addresses, domains, URLs and hashes).
14
14
  - Mihari checks whether a DB (SQLite3 or PostgreSQL) contains the artifacts or not.
15
15
  - If it doesn't contain the artifacts:
16
- - Mihari creates an alert on TheHive. (Optional)
17
- - Mihari sends a notification to Slack. (Optional)
18
- - Mihari creates an event on MISP. (Optional)
16
+ - Mihari creates an alert on TheHive.
17
+ - Mihari sends a notification to Slack.
18
+ - Mihari creates an event on MISP.
19
19
 
20
20
  ![img](https://github.com/ninoseki/mihari/raw/master/screenshots/eyecatch.png)
21
21
 
@@ -35,9 +35,8 @@ Mihari is a helper to run queries & manage results continuously. Mihari can be u
35
35
 
36
36
  ## Requirements
37
37
 
38
- - Ruby 2.6+
39
- - SQLite3
40
- - libpq
38
+ - Ruby (2.7 or 3.0)
39
+ - SQLite3 or PostgreSQL
41
40
 
42
41
  ```bash
43
42
  # For Debian / Ubuntu
@@ -226,6 +225,7 @@ Configuration can be done via environment variables or a YAML file.
226
225
  | SPYSE_API_KEY | Spyse API key | |
227
226
  | THEHIVE_API_ENDPOINT | TheHive URL | |
228
227
  | THEHIVE_API_KEY | TheHive API key | |
228
+ | URLSCAN_API_KEY | urlscan.io API key | |
229
229
  | VIRUSTOTAL_API_KEY | VirusTotal API key | |
230
230
  | ZOOMEYE_PASSWORD | ZoomEye password | |
231
231
  | ZOOMEYE_USERNAMME | ZoomEye username | |
data/Rakefile CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  require "rspec/core/rake_task"
5
+ require "standard/rake"
5
6
 
6
7
  RSpec::Core::RakeTask.new(:spec)
7
8
 
data/docker/Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- FROM ruby:2.7-alpine3.10
1
+ FROM ruby:3.0.0-alpine3.13
2
2
  RUN apk --no-cache add git build-base ruby-dev sqlite-dev postgresql-dev \
3
3
  && cd /tmp/ \
4
4
  && git clone https://github.com/ninoseki/mihari.git \
@@ -9,13 +9,13 @@ module Mihari
9
9
  relation = Alert.includes(:tags, :artifacts)
10
10
  relation = relation.where(title: title) if title
11
11
  relation = relation.where(source: source) if source
12
- relation = relation.where(tags: { name: tag } ) if tag
12
+ relation = relation.where(tags: {name: tag}) if tag
13
13
 
14
14
  alerts = relation.limit(limit).order(id: :desc)
15
15
  alerts.map do |alert|
16
16
  json = AlertSerializer.new(alert).as_json
17
- json[:artifacts] = (json.dig(:artifacts) || []).map { |artifact_| artifact_.dig(:data) }
18
- json[:tags] = (json.dig(:tags) || []).map { |tag_| tag_.dig(:name) }
17
+ json[:artifacts] = (json[:artifacts] || []).map { |artifact_| artifact_[:data] }
18
+ json[:tags] = (json[:tags] || []).map { |tag_| tag_[:name] }
19
19
  json
20
20
  end
21
21
  end
@@ -42,7 +42,7 @@ module Mihari
42
42
 
43
43
  def run_emitter(emitter)
44
44
  emitter.run(title: title, description: description, artifacts: unique_artifacts, source: source, tags: tags)
45
- rescue StandardError => e
45
+ rescue => e
46
46
  puts "Emission by #{emitter.class} is failed: #{e}"
47
47
  end
48
48
 
@@ -4,12 +4,11 @@ module Mihari
4
4
  module Analyzers
5
5
  class Basic < Base
6
6
  attr_accessor :title
7
- attr_reader :description
8
- attr_reader :artifacts
9
- attr_reader :source
10
- attr_reader :tags
7
+ attr_reader :description, :artifacts, :source, :tags
11
8
 
12
9
  def initialize(title:, description:, artifacts:, source:, tags: [])
10
+ super()
11
+
13
12
  @title = title
14
13
  @description = description
15
14
  @artifacts = artifacts
@@ -5,10 +5,7 @@ require "binaryedge"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class BinaryEdge < Base
8
- attr_reader :title
9
- attr_reader :description
10
- attr_reader :query
11
- attr_reader :tags
8
+ attr_reader :title, :description, :query, :tags
12
9
 
13
10
  def initialize(query, title: nil, description: nil, tags: [])
14
11
  super()
@@ -24,7 +21,7 @@ module Mihari
24
21
  return [] unless results || results.empty?
25
22
 
26
23
  results.map do |result|
27
- events = result.dig("events") || []
24
+ events = result["events"] || []
28
25
  events.map do |event|
29
26
  event.dig "target", "ip"
30
27
  end.compact
@@ -37,13 +34,17 @@ module Mihari
37
34
 
38
35
  def search_with_page(query, page: 1)
39
36
  api.host.search(query, page: page)
37
+ rescue ::BinaryEdge::Error => e
38
+ raise RetryableError, e if e.message.include?("Request time limit exceeded")
39
+
40
+ raise e
40
41
  end
41
42
 
42
43
  def search
43
44
  responses = []
44
45
  (1..Float::INFINITY).each do |page|
45
46
  res = search_with_page(query, page: page)
46
- total = res.dig("total").to_i
47
+ total = res["total"].to_i
47
48
 
48
49
  responses << res
49
50
  break if total <= page * PAGE_SIZE
@@ -52,7 +53,7 @@ module Mihari
52
53
  end
53
54
 
54
55
  def config_keys
55
- %w(binaryedge_api_key)
56
+ %w[binaryedge_api_key]
56
57
  end
57
58
 
58
59
  def api
@@ -5,11 +5,7 @@ require "censu"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class Censys < Base
8
- attr_reader :title
9
- attr_reader :description
10
- attr_reader :query
11
- attr_reader :tags
12
- attr_reader :type
8
+ attr_reader :title, :description, :query, :tags, :type
13
9
 
14
10
  def initialize(query, title: nil, description: nil, tags: [], type: "ipv4")
15
11
  super()
@@ -37,7 +33,7 @@ module Mihari
37
33
  private
38
34
 
39
35
  def valid_type?
40
- %w(ipv4 websites certificates).include? type
36
+ %w[ipv4 websites certificates].include? type
41
37
  end
42
38
 
43
39
  def normalize(domain)
@@ -86,7 +82,7 @@ module Mihari
86
82
  end
87
83
 
88
84
  def config_keys
89
- %w(censys_id censys_secret)
85
+ %w[censys_id censys_secret]
90
86
  end
91
87
 
92
88
  def api
@@ -5,9 +5,7 @@ require "passive_circl"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class CIRCL < Base
8
- attr_reader :title
9
- attr_reader :description
10
- attr_reader :tags
8
+ attr_reader :title, :description, :tags
11
9
 
12
10
  def initialize(query, title: nil, description: nil, tags: [])
13
11
  super()
@@ -27,7 +25,7 @@ module Mihari
27
25
  private
28
26
 
29
27
  def config_keys
30
- %w(circl_passive_password circl_passive_username)
28
+ %w[circl_passive_password circl_passive_username]
31
29
  end
32
30
 
33
31
  def api
@@ -41,7 +39,7 @@ module Mihari
41
39
  when "hash"
42
40
  passive_ssl_lookup
43
41
  else
44
- raise InvalidInputError, "#{@query}(type: #{@type || 'unknown'}) is not supported."
42
+ raise InvalidInputError, "#{@query}(type: #{@type || "unknown"}) is not supported."
45
43
  end
46
44
  end
47
45
 
@@ -5,11 +5,7 @@ require "crtsh"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class Crtsh < Base
8
- attr_reader :title
9
- attr_reader :description
10
- attr_reader :query
11
- attr_reader :tags
12
- attr_reader :exclude_expired
8
+ attr_reader :title, :description, :query, :tags, :exclude_expired
13
9
 
14
10
  def initialize(query, title: nil, description: nil, tags: [], exclude_expired: nil)
15
11
  super()
@@ -24,7 +20,7 @@ module Mihari
24
20
 
25
21
  def artifacts
26
22
  results = search
27
- name_values = results.map { |result| result.dig("name_value") }.compact
23
+ name_values = results.map { |result| result["name_value"] }.compact
28
24
  name_values.map(&:lines).flatten.uniq.map(&:chomp)
29
25
  end
30
26
 
@@ -5,10 +5,7 @@ require "dnpedia"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class DNPedia < Base
8
- attr_reader :query
9
- attr_reader :title
10
- attr_reader :description
11
- attr_reader :tags
8
+ attr_reader :query, :title, :description, :tags
12
9
 
13
10
  def initialize(query, title: nil, description: nil, tags: [])
14
11
  super()
@@ -31,9 +28,9 @@ module Mihari
31
28
 
32
29
  def lookup
33
30
  res = api.search(query)
34
- rows = res.dig("rows") || []
31
+ rows = res["rows"] || []
35
32
  rows.map do |row|
36
- [row.dig("name"), row.dig("zoneid")].join(".")
33
+ [row["name"], row["zoneid"]].join(".")
37
34
  end
38
35
  end
39
36
  end
@@ -7,12 +7,7 @@ require "parallel"
7
7
  module Mihari
8
8
  module Analyzers
9
9
  class DNSTwister < Base
10
- attr_reader :query
11
- attr_reader :type
12
-
13
- attr_reader :title
14
- attr_reader :description
15
- attr_reader :tags
10
+ attr_reader :query, :type, :title, :description, :tags
16
11
 
17
12
  def initialize(query, title: nil, description: nil, tags: [])
18
13
  super()
@@ -47,11 +42,11 @@ module Mihari
47
42
  end
48
43
 
49
44
  def lookup
50
- raise InvalidInputError, "#{query}(type: #{type || 'unknown'}) is not supported." unless valid_type?
45
+ raise InvalidInputError, "#{query}(type: #{type || "unknown"}) is not supported." unless valid_type?
51
46
 
52
47
  res = api.fuzz(query)
53
- fuzzy_domains = res.dig("fuzzy_domains") || []
54
- domains = fuzzy_domains.map { |domain| domain.dig("domain") }
48
+ fuzzy_domains = res["fuzzy_domains"] || []
49
+ domains = fuzzy_domains.map { |domain| domain["domain"] }
55
50
  Parallel.map(domains) do |domain|
56
51
  resolvable?(domain) ? domain : nil
57
52
  end.compact
@@ -5,15 +5,11 @@ require "parallel"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class FreeText < Base
8
- attr_reader :query
9
-
10
- attr_reader :title
11
- attr_reader :description
12
- attr_reader :tags
8
+ attr_reader :query, :title, :description, :tags
13
9
 
14
10
  ANALYZERS = [
15
11
  Mihari::Analyzers::BinaryEdge,
16
- Mihari::Analyzers::Censys,
12
+ Mihari::Analyzers::Censys
17
13
  ].freeze
18
14
 
19
15
  def initialize(query, title: nil, description: nil, tags: [])
@@ -5,15 +5,7 @@ require "parallel"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class HTTPHash < Base
8
- attr_reader :md5
9
- attr_reader :sha256
10
- attr_reader :mmh3
11
-
12
- attr_reader :html
13
-
14
- attr_reader :title
15
- attr_reader :description
16
- attr_reader :tags
8
+ attr_reader :md5, :sha256, :mmh3, :html, :title, :description, :tags
17
9
 
18
10
  def initialize(_query, md5: nil, sha256: nil, mmh3: nil, html: nil, title: nil, description: nil, tags: [])
19
11
  super()
@@ -57,7 +49,7 @@ module Mihari
57
49
  [
58
50
  md5 ? "md5:#{md5}" : nil,
59
51
  sha256 ? "sha256:#{sha256}" : nil,
60
- mmh3 ? "mmh3:#{mmh3}" : nil,
52
+ mmh3 ? "mmh3:#{mmh3}" : nil
61
53
  ].compact.join(",")
62
54
  end
63
55
 
@@ -92,7 +84,7 @@ module Mihari
92
84
  binary_edge,
93
85
  censys,
94
86
  onyphe,
95
- shodan,
87
+ shodan
96
88
  ].compact
97
89
  end
98
90
 
@@ -5,10 +5,7 @@ require "onyphe"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class Onyphe < Base
8
- attr_reader :title
9
- attr_reader :description
10
- attr_reader :query
11
- attr_reader :tags
8
+ attr_reader :title, :description, :query, :tags
12
9
 
13
10
  def initialize(query, title: nil, description: nil, tags: [])
14
11
  super()
@@ -35,7 +32,7 @@ module Mihari
35
32
  PAGE_SIZE = 10
36
33
 
37
34
  def config_keys
38
- %w(onyphe_api_key)
35
+ %w[onyphe_api_key]
39
36
  end
40
37
 
41
38
  def api
@@ -51,7 +48,7 @@ module Mihari
51
48
  (1..Float::INFINITY).each do |page|
52
49
  res = search_with_page(query, page: page)
53
50
  responses << res
54
- total = res.dig("total").to_i
51
+ total = res["total"].to_i
55
52
  break if total <= page * PAGE_SIZE
56
53
  end
57
54
  responses
@@ -5,12 +5,7 @@ require "otx_ruby"
5
5
  module Mihari
6
6
  module Analyzers
7
7
  class OTX < Base
8
- attr_reader :query
9
- attr_reader :type
10
-
11
- attr_reader :title
12
- attr_reader :description
13
- attr_reader :tags
8
+ attr_reader :query, :type, :title, :description, :tags
14
9
 
15
10
  def initialize(query, title: nil, description: nil, tags: [])
16
11
  super()
@@ -30,7 +25,7 @@ module Mihari
30
25
  private
31
26
 
32
27
  def config_keys
33
- %w(otx_api_key)
28
+ %w[otx_api_key]
34
29
  end
35
30
 
36
31
  def domain_client
@@ -42,7 +37,7 @@ module Mihari
42
37
  end
43
38
 
44
39
  def valid_type?
45
- %w(ip domain).include? type
40
+ %w[ip domain].include? type
46
41
  end
47
42
 
48
43
  def lookup
@@ -52,7 +47,7 @@ module Mihari
52
47
  when "ip"
53
48
  ip_lookup
54
49
  else
55
- raise InvalidInputError, "#{query}(type: #{type || 'unknown'}) is not supported." unless valid_type?
50
+ raise InvalidInputError, "#{query}(type: #{type || "unknown"}) is not supported." unless valid_type?
56
51
  end
57
52
  end
58
53