mihari 1.3.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +44 -0
- data/README.md +7 -7
- data/Rakefile +1 -0
- data/docker/Dockerfile +1 -1
- data/lib/mihari/alert_viewer.rb +3 -3
- data/lib/mihari/analyzers/base.rb +1 -1
- data/lib/mihari/analyzers/basic.rb +3 -4
- data/lib/mihari/analyzers/binaryedge.rb +8 -7
- data/lib/mihari/analyzers/censys.rb +3 -7
- data/lib/mihari/analyzers/circl.rb +3 -5
- data/lib/mihari/analyzers/crtsh.rb +2 -6
- data/lib/mihari/analyzers/dnpedia.rb +3 -6
- data/lib/mihari/analyzers/dnstwister.rb +4 -9
- data/lib/mihari/analyzers/free_text.rb +2 -6
- data/lib/mihari/analyzers/http_hash.rb +3 -11
- data/lib/mihari/analyzers/onyphe.rb +3 -6
- data/lib/mihari/analyzers/otx.rb +4 -9
- data/lib/mihari/analyzers/passive_dns.rb +4 -9
- data/lib/mihari/analyzers/passive_ssl.rb +4 -9
- data/lib/mihari/analyzers/passivetotal.rb +9 -14
- data/lib/mihari/analyzers/pulsedive.rb +7 -12
- data/lib/mihari/analyzers/reverse_whois.rb +4 -9
- data/lib/mihari/analyzers/securitytrails.rb +12 -17
- data/lib/mihari/analyzers/securitytrails_domain_feed.rb +3 -7
- data/lib/mihari/analyzers/shodan.rb +9 -8
- data/lib/mihari/analyzers/spyse.rb +6 -11
- data/lib/mihari/analyzers/ssh_fingerprint.rb +2 -6
- data/lib/mihari/analyzers/urlscan.rb +25 -9
- data/lib/mihari/analyzers/virustotal.rb +6 -11
- data/lib/mihari/analyzers/zoomeye.rb +7 -11
- data/lib/mihari/cli.rb +14 -7
- data/lib/mihari/config.rb +2 -24
- data/lib/mihari/database.rb +1 -1
- data/lib/mihari/emitters/misp.rb +4 -2
- data/lib/mihari/emitters/slack.rb +18 -7
- data/lib/mihari/emitters/the_hive.rb +2 -2
- data/lib/mihari/errors.rb +3 -0
- data/lib/mihari/models/artifact.rb +1 -1
- data/lib/mihari/notifiers/exception_notifier.rb +5 -5
- data/lib/mihari/retriable.rb +1 -1
- data/lib/mihari/status.rb +1 -1
- data/lib/mihari/type_checker.rb +4 -4
- data/lib/mihari/version.rb +1 -1
- data/mihari.gemspec +22 -23
- metadata +37 -51
- data/.travis.yml +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 70afeb6b1ddaa263689beb836de85264ad1e871a1887a0574adfc22f00e006d8
|
4
|
+
data.tar.gz: ad614363a9a3320c2dfa34ec19bd4712d08a9a6e662cdf11b4b70c775f55e592
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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)
|
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.
|
17
|
-
- Mihari sends a notification to Slack.
|
18
|
-
- Mihari creates an event on MISP.
|
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.
|
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
data/docker/Dockerfile
CHANGED
data/lib/mihari/alert_viewer.rb
CHANGED
@@ -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: {
|
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
|
18
|
-
json[:tags] = (json
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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 ||
|
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
|
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
|
31
|
+
rows = res["rows"] || []
|
35
32
|
rows.map do |row|
|
36
|
-
[row
|
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 ||
|
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
|
54
|
-
domains = fuzzy_domains.map { |domain| 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
|
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
|
51
|
+
total = res["total"].to_i
|
55
52
|
break if total <= page * PAGE_SIZE
|
56
53
|
end
|
57
54
|
responses
|
data/lib/mihari/analyzers/otx.rb
CHANGED
@@ -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
|
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
|
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 ||
|
50
|
+
raise InvalidInputError, "#{query}(type: #{type || "unknown"}) is not supported." unless valid_type?
|
56
51
|
end
|
57
52
|
end
|
58
53
|
|