mihari 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +44 -0
  3. data/README.md +6 -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 +4 -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 +5 -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 +4 -12
  30. data/lib/mihari/analyzers/virustotal.rb +6 -11
  31. data/lib/mihari/analyzers/zoomeye.rb +7 -11
  32. data/lib/mihari/cli.rb +7 -7
  33. data/lib/mihari/config.rb +1 -25
  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 +2 -0
  39. data/lib/mihari/models/artifact.rb +1 -1
  40. data/lib/mihari/notifiers/exception_notifier.rb +5 -5
  41. data/lib/mihari/status.rb +1 -1
  42. data/lib/mihari/type_checker.rb +4 -4
  43. data/lib/mihari/version.rb +1 -1
  44. data/mihari.gemspec +17 -19
  45. metadata +15 -43
  46. data/.travis.yml +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3739df12ff7fe35c98ad1dbd181fd8b300486443e36b7ed50bbbd8507a00eccf
4
- data.tar.gz: 43e048cf9eb60f8c0e32cbd43b729e25fa86da2e09784dc24d52163260653988
3
+ metadata.gz: 70afeb6b1ddaa263689beb836de85264ad1e871a1887a0574adfc22f00e006d8
4
+ data.tar.gz: ad614363a9a3320c2dfa34ec19bd4712d08a9a6e662cdf11b4b70c775f55e592
5
5
  SHA512:
6
- metadata.gz: f62b84ba24b7405b0977414c6c91c9d97219a77f5d166c46c83682613a99f19a17dfedd8768f560eed7c0e6d28d7ea343a332400512748551b518b8e09e038fd
7
- data.tar.gz: 794a4b44504e01cbb8b772fb7d7f9e389b67895352e4552549cf02bb70827695d44424ba583059020394ea0eb620a58ef05c4e34bed78b9dabc842abad996516
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
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
@@ -47,7 +44,7 @@ module Mihari
47
44
  responses = []
48
45
  (1..Float::INFINITY).each do |page|
49
46
  res = search_with_page(query, page: page)
50
- total = res.dig("total").to_i
47
+ total = res["total"].to_i
51
48
 
52
49
  responses << res
53
50
  break if total <= page * PAGE_SIZE
@@ -56,7 +53,7 @@ module Mihari
56
53
  end
57
54
 
58
55
  def config_keys
59
- %w(binaryedge_api_key)
56
+ %w[binaryedge_api_key]
60
57
  end
61
58
 
62
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