mihari 1.4.1 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +43 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +15 -0
  4. data/.github/workflows/test.yml +68 -0
  5. data/.rubocop.yml +6 -0
  6. data/.standard.yml +4 -0
  7. data/README.md +24 -270
  8. data/Rakefile +1 -0
  9. data/bin/console +1 -0
  10. data/build_frontend.sh +14 -0
  11. data/docker/Dockerfile +5 -3
  12. data/examples/ipinfo_hosted_domains.rb +1 -1
  13. data/{screenshots → images}/alert.png +0 -0
  14. data/images/logo.png +0 -0
  15. data/{screenshots → images}/misp.png +0 -0
  16. data/{screenshots/eyecatch.png → images/overview.png} +0 -0
  17. data/{screenshots → images}/slack.png +0 -0
  18. data/images/web_alerts.png +0 -0
  19. data/images/web_config.png +0 -0
  20. data/lib/mihari.rb +2 -2
  21. data/lib/mihari/analyzers/base.rb +10 -1
  22. data/lib/mihari/analyzers/basic.rb +3 -4
  23. data/lib/mihari/analyzers/binaryedge.rb +4 -7
  24. data/lib/mihari/analyzers/censys.rb +3 -7
  25. data/lib/mihari/analyzers/circl.rb +6 -8
  26. data/lib/mihari/analyzers/crtsh.rb +2 -6
  27. data/lib/mihari/analyzers/dnpedia.rb +3 -6
  28. data/lib/mihari/analyzers/dnstwister.rb +4 -9
  29. data/lib/mihari/analyzers/free_text.rb +2 -6
  30. data/lib/mihari/analyzers/http_hash.rb +3 -11
  31. data/lib/mihari/analyzers/onyphe.rb +5 -8
  32. data/lib/mihari/analyzers/otx.rb +4 -9
  33. data/lib/mihari/analyzers/passive_dns.rb +4 -9
  34. data/lib/mihari/analyzers/passive_ssl.rb +4 -9
  35. data/lib/mihari/analyzers/passivetotal.rb +9 -14
  36. data/lib/mihari/analyzers/pulsedive.rb +7 -12
  37. data/lib/mihari/analyzers/reverse_whois.rb +4 -9
  38. data/lib/mihari/analyzers/securitytrails.rb +12 -17
  39. data/lib/mihari/analyzers/securitytrails_domain_feed.rb +3 -7
  40. data/lib/mihari/analyzers/shodan.rb +9 -8
  41. data/lib/mihari/analyzers/spyse.rb +6 -11
  42. data/lib/mihari/analyzers/ssh_fingerprint.rb +2 -6
  43. data/lib/mihari/analyzers/urlscan.rb +4 -12
  44. data/lib/mihari/analyzers/virustotal.rb +6 -11
  45. data/lib/mihari/analyzers/zoomeye.rb +7 -11
  46. data/lib/mihari/cli.rb +70 -300
  47. data/lib/mihari/commands/binaryedge.rb +21 -0
  48. data/lib/mihari/commands/censys.rb +22 -0
  49. data/lib/mihari/commands/circl.rb +21 -0
  50. data/lib/mihari/commands/config.rb +27 -0
  51. data/lib/mihari/commands/crtsh.rb +22 -0
  52. data/lib/mihari/commands/dnpedia.rb +21 -0
  53. data/lib/mihari/commands/dnstwister.rb +21 -0
  54. data/lib/mihari/commands/free_text.rb +21 -0
  55. data/lib/mihari/commands/http_hash.rb +25 -0
  56. data/lib/mihari/commands/json.rb +42 -0
  57. data/lib/mihari/commands/onyphe.rb +21 -0
  58. data/lib/mihari/commands/otx.rb +21 -0
  59. data/lib/mihari/commands/passive_dns.rb +21 -0
  60. data/lib/mihari/commands/passive_ssl.rb +21 -0
  61. data/lib/mihari/commands/passivetotal.rb +21 -0
  62. data/lib/mihari/commands/pulsedive.rb +21 -0
  63. data/lib/mihari/commands/reverse_whois.rb +21 -0
  64. data/lib/mihari/commands/securitytrails.rb +22 -0
  65. data/lib/mihari/commands/securitytrails_domain_feed.rb +23 -0
  66. data/lib/mihari/commands/shodan.rb +21 -0
  67. data/lib/mihari/commands/spyse.rb +22 -0
  68. data/lib/mihari/commands/ssh_fingerprint.rb +21 -0
  69. data/lib/mihari/commands/urlscan.rb +25 -0
  70. data/lib/mihari/commands/virustotal.rb +21 -0
  71. data/lib/mihari/commands/web.rb +22 -0
  72. data/lib/mihari/commands/zoomeye.rb +22 -0
  73. data/lib/mihari/config.rb +13 -25
  74. data/lib/mihari/configurable.rb +4 -5
  75. data/lib/mihari/database.rb +7 -1
  76. data/lib/mihari/emitters/misp.rb +4 -2
  77. data/lib/mihari/emitters/slack.rb +18 -7
  78. data/lib/mihari/emitters/the_hive.rb +1 -1
  79. data/lib/mihari/errors.rb +2 -0
  80. data/lib/mihari/models/alert.rb +51 -0
  81. data/lib/mihari/models/artifact.rb +14 -3
  82. data/lib/mihari/notifiers/exception_notifier.rb +1 -1
  83. data/lib/mihari/serializers/alert.rb +1 -1
  84. data/lib/mihari/serializers/artifact.rb +1 -1
  85. data/lib/mihari/serializers/tag.rb +1 -1
  86. data/lib/mihari/status.rb +6 -14
  87. data/lib/mihari/type_checker.rb +4 -4
  88. data/lib/mihari/version.rb +1 -1
  89. data/lib/mihari/web/app.rb +49 -0
  90. data/lib/mihari/web/controllers/alerts_controller.rb +66 -0
  91. data/lib/mihari/web/controllers/artifacts_controller.rb +26 -0
  92. data/lib/mihari/web/controllers/command_controller.rb +27 -0
  93. data/lib/mihari/web/controllers/config_controller.rb +15 -0
  94. data/lib/mihari/web/controllers/sources_controller.rb +14 -0
  95. data/lib/mihari/web/controllers/tags_controller.rb +30 -0
  96. data/lib/mihari/web/helpers/json.rb +51 -0
  97. data/lib/mihari/web/public/index.html +21 -0
  98. data/lib/mihari/web/public/redoc-static.html +519 -0
  99. data/lib/mihari/web/public/static/favicon.ico +0 -0
  100. data/lib/mihari/web/public/static/fonts/fa-brands-400.099a9556.woff +0 -0
  101. data/lib/mihari/web/public/static/fonts/fa-brands-400.30cc681d.eot +0 -0
  102. data/lib/mihari/web/public/static/fonts/fa-brands-400.3b89dd10.ttf +0 -0
  103. data/lib/mihari/web/public/static/fonts/fa-brands-400.f7307680.woff2 +0 -0
  104. data/lib/mihari/web/public/static/fonts/fa-regular-400.1f77739c.ttf +0 -0
  105. data/lib/mihari/web/public/static/fonts/fa-regular-400.7124eb50.woff +0 -0
  106. data/lib/mihari/web/public/static/fonts/fa-regular-400.7630483d.eot +0 -0
  107. data/lib/mihari/web/public/static/fonts/fa-regular-400.f0f82301.woff2 +0 -0
  108. data/lib/mihari/web/public/static/fonts/fa-solid-900.1042e8ca.eot +0 -0
  109. data/lib/mihari/web/public/static/fonts/fa-solid-900.605ed792.ttf +0 -0
  110. data/lib/mihari/web/public/static/fonts/fa-solid-900.9fe5a17c.woff +0 -0
  111. data/lib/mihari/web/public/static/fonts/fa-solid-900.e8a427e1.woff2 +0 -0
  112. data/lib/mihari/web/public/static/img/fa-brands-400.ba7ed552.svg +3717 -0
  113. data/lib/mihari/web/public/static/img/fa-regular-400.0bb42845.svg +801 -0
  114. data/lib/mihari/web/public/static/img/fa-solid-900.376c1f97.svg +5034 -0
  115. data/lib/mihari/web/public/static/js/app.bcc595df.js +12 -0
  116. data/lib/mihari/web/public/static/js/app.bcc595df.js.map +1 -0
  117. data/mihari.gemspec +28 -21
  118. metadata +217 -45
  119. data/.travis.yml +0 -13
  120. data/lib/mihari/alert_viewer.rb +0 -23
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/bin/console CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require "bundler/setup"
4
5
  require "mihari"
data/build_frontend.sh ADDED
@@ -0,0 +1,14 @@
1
+ #!/bin/sh
2
+
3
+ CURRENT_DIR=${PWD}
4
+
5
+ mkdir -p tmp
6
+ cd tmp
7
+ git clone https://github.com/ninoseki/mihari-frontend.git
8
+ cd mihari-frontend
9
+ npm install
10
+ npm run build
11
+
12
+ cp -r dist/* ${CURRENT_DIR}/lib/mihari/web/public
13
+
14
+ rm -rf ${CURRENT_DIR}/tmp/mihari-frontend
data/docker/Dockerfile CHANGED
@@ -1,5 +1,7 @@
1
- FROM ruby:2.7-alpine3.10
2
- RUN apk --no-cache add git build-base ruby-dev sqlite-dev postgresql-dev \
1
+ FROM ruby:3.0.1-alpine3.13
2
+
3
+ RUN apk --no-cache add git build-base ruby-dev sqlite-dev postgresql-dev mysql-client mysql-dev \
4
+ && gem install pg mysql2 \
3
5
  && cd /tmp/ \
4
6
  && git clone https://github.com/ninoseki/mihari.git \
5
7
  && cd mihari \
@@ -10,4 +12,4 @@ RUN apk --no-cache add git build-base ruby-dev sqlite-dev postgresql-dev \
10
12
 
11
13
  ENTRYPOINT ["mihari"]
12
14
 
13
- CMD ["--help"]
15
+ CMD ["--help"]
@@ -34,7 +34,7 @@ module Mihari
34
34
  uri = URI("#{IPINFO_API_ENDPOINT}/domains/#{ip}?token=#{token}")
35
35
  res = uri.read
36
36
  json = JSON.parse(res)
37
- json.dig("domains") || []
37
+ json["domains"] || []
38
38
  end
39
39
  end
40
40
  end
File without changes
data/images/logo.png ADDED
Binary file
File without changes
File without changes
File without changes
Binary file
Binary file
data/lib/mihari.rb CHANGED
@@ -79,8 +79,8 @@ require "mihari/emitters/slack"
79
79
  require "mihari/emitters/stdout"
80
80
  require "mihari/emitters/the_hive"
81
81
 
82
- require "mihari/alert_viewer"
83
-
84
82
  require "mihari/status"
85
83
 
84
+ require "mihari/web/app"
85
+
86
86
  require "mihari/cli"
@@ -8,6 +8,13 @@ module Mihari
8
8
  include Configurable
9
9
  include Retriable
10
10
 
11
+ attr_accessor :ignore_old_artifacts, :ignore_threshold
12
+
13
+ def initialize
14
+ @ignore_old_artifacts = false
15
+ @ignore_threshold = 0
16
+ end
17
+
11
18
  # @return [Array<String>, Array<Mihari::Artifact>]
12
19
  def artifacts
13
20
  raise NotImplementedError, "You must implement #{self.class}##{__method__}"
@@ -61,7 +68,9 @@ module Mihari
61
68
 
62
69
  # @return [Array<Mihari::Artifact>]
63
70
  def unique_artifacts
64
- @unique_artifacts ||= normalized_artifacts.select(&:unique?)
71
+ @unique_artifacts ||= normalized_artifacts.select do |artifact|
72
+ artifact.unique?(ignore_old_artifacts: ignore_old_artifacts, ignore_threshold: ignore_threshold)
73
+ end
65
74
  end
66
75
 
67
76
  def set_unique_artifacts
@@ -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,21 +39,21 @@ 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
 
48
46
  def passive_dns_lookup
49
47
  results = api.dns.query(@query)
50
48
  results.map do |result|
51
- type = result.dig("rrtype")
52
- type == "A" ? result.dig("rdata") : nil
49
+ type = result["rrtype"]
50
+ type == "A" ? result["rdata"] : nil
53
51
  end.compact.uniq
54
52
  end
55
53
 
56
54
  def passive_ssl_lookup
57
55
  result = api.ssl.cquery(@query)
58
- seen = result.dig("seen") || []
56
+ seen = result["seen"] || []
59
57
  seen.uniq
60
58
  end
61
59
  end
@@ -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()
@@ -24,10 +21,10 @@ module Mihari
24
21
  return [] unless results
25
22
 
26
23
  flat_results = results.map do |result|
27
- result.dig("results")
24
+ result["results"]
28
25
  end.flatten.compact
29
26
 
30
- flat_results.map { |result| result.dig("ip") }.compact.uniq
27
+ flat_results.map { |result| result["ip"] }.compact.uniq
31
28
  end
32
29
 
33
30
  private
@@ -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