mihari 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +56 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +110 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/examples/ipinfo_hosted_domains.rb +45 -0
- data/examples/vt_passive_dns.rb +46 -0
- data/exe/mihari +8 -0
- data/lib/mihari/analyzers/base.rb +50 -0
- data/lib/mihari/analyzers/basic.rb +19 -0
- data/lib/mihari/analyzers/censys.rb +53 -0
- data/lib/mihari/analyzers/shodan.rb +48 -0
- data/lib/mihari/artifact.rb +38 -0
- data/lib/mihari/cli.rb +64 -0
- data/lib/mihari/errors.rb +5 -0
- data/lib/mihari/notifiers/base.rb +20 -0
- data/lib/mihari/notifiers/slack.rb +89 -0
- data/lib/mihari/notifiers/the_hive.rb +26 -0
- data/lib/mihari/the_hive.rb +41 -0
- data/lib/mihari/type_checker.rb +86 -0
- data/lib/mihari/version.rb +5 -0
- data/lib/mihari.rb +34 -0
- data/mihari.gemspec +42 -0
- metadata +267 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a60ee7c7a40195a7b6b49484ce1df99926278721bd77603b6c9d0f3a952c4ac2
|
4
|
+
data.tar.gz: 5c51449f1c2c3a7e14f745490c2bee6f2d18c3f5e5eaafc25a2ba55b15e83e12
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dba9abb2e8c856bd2fc814daf4d7d4b5d63912d707c6e4e1225dc8ecb3d7f07067d6e8764509013a99621902fb24d7a74c8dd8a5dcbde00ec2469075799458db
|
7
|
+
data.tar.gz: 53f255209c397224a7250c5adc6323c1e99a833426f9304649c779751f0299a898c28c9b911289bf941ebed8b8ab0b61c9aef298153acf3fe269a9e9044b58e5
|
data/.gitignore
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/spec/examples.txt
|
9
|
+
/test/tmp/
|
10
|
+
/test/version_tmp/
|
11
|
+
/tmp/
|
12
|
+
|
13
|
+
# Used by dotenv library to load environment variables.
|
14
|
+
.env
|
15
|
+
|
16
|
+
## Specific to RubyMotion:
|
17
|
+
.dat*
|
18
|
+
.repl_history
|
19
|
+
build/
|
20
|
+
*.bridgesupport
|
21
|
+
build-iPhoneOS/
|
22
|
+
build-iPhoneSimulator/
|
23
|
+
|
24
|
+
## Specific to RubyMotion (use of CocoaPods):
|
25
|
+
#
|
26
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
27
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
28
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
29
|
+
#
|
30
|
+
# vendor/Pods/
|
31
|
+
|
32
|
+
## Documentation cache and generated files:
|
33
|
+
/.yardoc/
|
34
|
+
/_yardoc/
|
35
|
+
/doc/
|
36
|
+
/rdoc/
|
37
|
+
|
38
|
+
## Environment normalization:
|
39
|
+
/.bundle/
|
40
|
+
/vendor/bundle
|
41
|
+
/lib/bundler/man/
|
42
|
+
|
43
|
+
# for a library or gem, you might want to ignore these files since the code is
|
44
|
+
# intended to run in multiple environments; otherwise, check them in:
|
45
|
+
Gemfile.lock
|
46
|
+
.ruby-version
|
47
|
+
.ruby-gemset
|
48
|
+
|
49
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
50
|
+
.rvmrc
|
51
|
+
|
52
|
+
# rspec
|
53
|
+
.rspec_status
|
54
|
+
|
55
|
+
# solargraph
|
56
|
+
.solargraph.yml
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2019 Manabu Niseki
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
# mihari
|
2
|
+
|
3
|
+
[](https://travis-ci.org/ninoseki/mihari)
|
4
|
+
[](https://coveralls.io/github/ninoseki/mihari?branch=master)
|
5
|
+
|
6
|
+
mihari(`見張り`) is a framework for continuous malicious hosts (C2 / landing page / phishing, etc.) monitoring backended with [TheHive](https://github.com/TheHive-Project/TheHive).
|
7
|
+
|
8
|
+
## How it works
|
9
|
+
|
10
|
+
- mihari checks whether a TheHive instance contains given artifacts or not.
|
11
|
+
- If it doesn't contain the artifacts:
|
12
|
+
- mihari creates an alert with the artifacts on the TheHive instance.
|
13
|
+
- mihari sends a notification to Slack. (Optional)
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
```bash
|
18
|
+
gem install mihari
|
19
|
+
```
|
20
|
+
|
21
|
+
## Configuration
|
22
|
+
|
23
|
+
All configuration is done via ENV variables.
|
24
|
+
|
25
|
+
| Key | Desc. | Required or optional |
|
26
|
+
|----------------------|--------------------|--------------------------------|
|
27
|
+
| THEHIVE_API_ENDPOINT | TheHive URL | Required |
|
28
|
+
| THEHIVE_API_KEY | TheHive API key | Required |
|
29
|
+
| SLACK_WEBHOOK_URL | Slack Webhook URL | Optional |
|
30
|
+
| SLACK_CHANNEL | Slack channel name | Optional (default: `#general`) |
|
31
|
+
| CENSYS_ID | CENSYS API ID | Optional |
|
32
|
+
| CENSYS_SECRET | CENSYS secret | Optional |
|
33
|
+
| SHODAN_API_KEY | Shodan API key | Optional |
|
34
|
+
|
35
|
+
## Basic usage
|
36
|
+
|
37
|
+
### Censys
|
38
|
+
|
39
|
+
```bash
|
40
|
+
mihari censys "YOUR_QUERY"
|
41
|
+
```
|
42
|
+
|
43
|
+
### Shodan
|
44
|
+
|
45
|
+
```bash
|
46
|
+
mihari shodan "YOUR QUERY"
|
47
|
+
```
|
48
|
+
|
49
|
+
### Import from JSON
|
50
|
+
|
51
|
+
```bash
|
52
|
+
echo '{ "title": "test", "description": "test", "artifacts": ["1.1.1.1", "github.com", "2.2.2.2"] }' | mihari import_from_json
|
53
|
+
```
|
54
|
+
|
55
|
+
The input is a JSON data should have `title`, `description` and `artifacts` key.
|
56
|
+
|
57
|
+
```json
|
58
|
+
{
|
59
|
+
"title": "test",
|
60
|
+
"description": "test",
|
61
|
+
"artifacts": ["1.1.1.1", "github.com"]
|
62
|
+
}
|
63
|
+
```
|
64
|
+
|
65
|
+
| Key | Desc. |
|
66
|
+
|-------------|----------------------------------------------------------------------------|
|
67
|
+
| title | A title of an alert |
|
68
|
+
| description | A description of an alert |
|
69
|
+
| artifacts | An array of artifacts (supported data types: ip, domain, url, email, hash) |
|
70
|
+
|
71
|
+
## How to create a custom analyzer
|
72
|
+
|
73
|
+
Create a class which extends `Mihari::Analyzers::Base` and implements the following methods.
|
74
|
+
|
75
|
+
| Name | Desc. | @return |
|
76
|
+
|----------------|----------------------------------------------------------------------------|---------------|
|
77
|
+
| `#title` | A title of an alert | String |
|
78
|
+
| `#description` | A description of an alert | String |
|
79
|
+
| `#artifacts` | An array of artifacts (supported data types: ip, domain, url, email, hash) | Array<String> |
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
require "mihari"
|
83
|
+
|
84
|
+
module Mihari
|
85
|
+
module Analyzers
|
86
|
+
class Example < Base
|
87
|
+
def title
|
88
|
+
"example"
|
89
|
+
end
|
90
|
+
|
91
|
+
def description
|
92
|
+
"example"
|
93
|
+
end
|
94
|
+
|
95
|
+
def artifacts
|
96
|
+
["9.9.9.9", "example.com"]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
example = Mihari::Analyzers::Example.new
|
103
|
+
example.run
|
104
|
+
```
|
105
|
+
|
106
|
+
See `/examples` for more.
|
107
|
+
|
108
|
+
## License
|
109
|
+
|
110
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "mihari"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift("#{__dir__}/../lib")
|
4
|
+
|
5
|
+
require "json"
|
6
|
+
require "mihari"
|
7
|
+
require "open-uri"
|
8
|
+
|
9
|
+
module Mihari
|
10
|
+
module Analyzers
|
11
|
+
class HostedDomains < Base
|
12
|
+
attr_reader :ip
|
13
|
+
|
14
|
+
IPINFO_API_ENDPOINT = "https://ipinfo.io"
|
15
|
+
|
16
|
+
def initialize(ip, token: nil)
|
17
|
+
@ip = ip
|
18
|
+
@token = token
|
19
|
+
end
|
20
|
+
|
21
|
+
def title
|
22
|
+
"IPinfo hosted domains"
|
23
|
+
end
|
24
|
+
|
25
|
+
def description
|
26
|
+
"IP info hosted domains: #{ip}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def token
|
30
|
+
ENV["IPINFO_TOKEN"] || @token
|
31
|
+
end
|
32
|
+
|
33
|
+
def artifacts
|
34
|
+
uri = URI("#{IPINFO_API_ENDPOINT}/domains/#{ip}?token=#{token}")
|
35
|
+
res = uri.read
|
36
|
+
json = JSON.parse(res)
|
37
|
+
json.dig("domains") || []
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
ip = "TARGET_IP"
|
44
|
+
analyzer = Mihari::Analyzers::HostedDomains.new(ip)
|
45
|
+
analyzer.run
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift("#{__dir__}/../lib")
|
4
|
+
|
5
|
+
require "mihari"
|
6
|
+
|
7
|
+
require "virustotal_api"
|
8
|
+
|
9
|
+
module Mihari
|
10
|
+
module Analyzers
|
11
|
+
class VTPassiveDNS < Base
|
12
|
+
attr_reader :ip
|
13
|
+
|
14
|
+
def initialize(ip, api_key: nil)
|
15
|
+
@ip = ip
|
16
|
+
@api_key = api_key
|
17
|
+
end
|
18
|
+
|
19
|
+
def title
|
20
|
+
"VT passive DNS"
|
21
|
+
end
|
22
|
+
|
23
|
+
def description
|
24
|
+
"VT passive DNS: #{ip}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def api_key
|
28
|
+
ENV["VT_API_KEY"] || @api_key
|
29
|
+
end
|
30
|
+
|
31
|
+
def artifacts
|
32
|
+
ip_report = VirustotalAPI::IPReport.find(ip, api_key)
|
33
|
+
return [] unless ip_report.exists?
|
34
|
+
|
35
|
+
report = ip_report.report
|
36
|
+
report.dig("resolutions")&.map do |resolution|
|
37
|
+
resolution.dig("hostname")
|
38
|
+
end&.compact
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
ip = "TARGET_IP"
|
45
|
+
analyzer = Mihari::Analyzers::VTPassiveDNS.new(ip)
|
46
|
+
analyzer.run
|
data/exe/mihari
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Analyzers
|
5
|
+
class Base
|
6
|
+
attr_reader :the_hive
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@the_hive = TheHive.new
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [Array<String>, Array<Mihari::Artifact>]
|
13
|
+
def artifacts
|
14
|
+
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [String]
|
18
|
+
def title
|
19
|
+
self.class.to_s.split("::").last
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [String]
|
23
|
+
def description
|
24
|
+
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def run(reject_exists_ones: true)
|
28
|
+
unique_artifacts = normalized_artifacts.reject do |artifact|
|
29
|
+
reject_exists_ones & the_hive.valid? && the_hive.exists?(data: artifact.data, data_type: artifact.data_type)
|
30
|
+
end
|
31
|
+
|
32
|
+
Mihari.notifiers.each do |notifier_class|
|
33
|
+
notifier = notifier_class.new
|
34
|
+
next unless notifier.valid?
|
35
|
+
|
36
|
+
notifier.notify(title: title, description: description, artifacts: unique_artifacts)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# @return [Array<Mihari::Artifact>]
|
43
|
+
def normalized_artifacts
|
44
|
+
artifacts.map do |artifact|
|
45
|
+
artifact.is_a?(Artifact) ? artifact : Artifact.new(artifact)
|
46
|
+
end.select(&:valid?)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Analyzers
|
5
|
+
class Basic < Base
|
6
|
+
attr_reader :title
|
7
|
+
attr_reader :description
|
8
|
+
attr_reader :artifacts
|
9
|
+
|
10
|
+
def initialize(title:, description:, artifacts:)
|
11
|
+
super()
|
12
|
+
|
13
|
+
@title = title
|
14
|
+
@description = description
|
15
|
+
@artifacts = artifacts
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "censu"
|
4
|
+
|
5
|
+
module Mihari
|
6
|
+
module Analyzers
|
7
|
+
class Censys < Base
|
8
|
+
attr_reader :api
|
9
|
+
attr_reader :title
|
10
|
+
attr_reader :description
|
11
|
+
attr_reader :query
|
12
|
+
|
13
|
+
CENSYS_ID_KEY = "CENSYS_ID"
|
14
|
+
CENSYS_SECRET_KEY = "CENSYS_SECRET"
|
15
|
+
|
16
|
+
def initialize(query)
|
17
|
+
super()
|
18
|
+
|
19
|
+
raise ArgumentError, "#{CENSYS_ID_KEY} and #{CENSYS_SECRET_KEY} are required" unless valid?
|
20
|
+
|
21
|
+
@api = ::Censys::API.new
|
22
|
+
@query = query
|
23
|
+
@title = "Censys lookup"
|
24
|
+
@description = "Query: #{query}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def artifacts
|
28
|
+
ipv4s = []
|
29
|
+
res = api.ipv4.search(query: query)
|
30
|
+
res.each_page do |page|
|
31
|
+
page.each { |result| ipv4s << result.ip }
|
32
|
+
end
|
33
|
+
|
34
|
+
ipv4s
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [true, false]
|
38
|
+
def censys_id?
|
39
|
+
ENV.key? CENSYS_ID_KEY
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [true, false]
|
43
|
+
def censys_secret?
|
44
|
+
ENV.key? CENSYS_SECRET_KEY
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [true, false]
|
48
|
+
def valid?
|
49
|
+
censys_id? && censys_secret?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "open-uri"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
module Mihari
|
7
|
+
module Analyzers
|
8
|
+
class Shodan < Base
|
9
|
+
attr_reader :api_key
|
10
|
+
attr_reader :title
|
11
|
+
attr_reader :description
|
12
|
+
attr_reader :query
|
13
|
+
|
14
|
+
def initialize(query)
|
15
|
+
super()
|
16
|
+
|
17
|
+
api_key = ENV.fetch("SHODAN_API_KEY", nil)
|
18
|
+
raise ArgumentError, "SHODAN_API_KEY is required" unless api_key
|
19
|
+
|
20
|
+
@api_key = api_key
|
21
|
+
@query = query
|
22
|
+
@title = "Shodan lookup"
|
23
|
+
@description = "Query: #{query}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def artifacts
|
27
|
+
result = search
|
28
|
+
return [] unless result
|
29
|
+
|
30
|
+
matches = result.dig("matches") || []
|
31
|
+
matches.map do |match|
|
32
|
+
match.dig "ip_str"
|
33
|
+
end.compact
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def search
|
39
|
+
uri = URI("https://api.shodan.io/shodan/host/search?key=#{api_key}&query=#{query}")
|
40
|
+
begin
|
41
|
+
JSON.parse uri.read
|
42
|
+
rescue OpenURI::HTTPError
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "hachi"
|
4
|
+
|
5
|
+
module Mihari
|
6
|
+
class Artifact
|
7
|
+
attr_reader :data
|
8
|
+
|
9
|
+
#
|
10
|
+
# @param [String] data
|
11
|
+
# @param [String, nil] message
|
12
|
+
#
|
13
|
+
def initialize(data, message: nil)
|
14
|
+
@data = data
|
15
|
+
@message = message
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [String, nil]
|
19
|
+
def data_type
|
20
|
+
TypeChecker.type data
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [String]
|
24
|
+
def message
|
25
|
+
@mesasge || data
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [true, false]
|
29
|
+
def valid?
|
30
|
+
!data_type.nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [Hash]
|
34
|
+
def to_h
|
35
|
+
{ data: data, data_type: data_type, message: message }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/mihari/cli.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "thor"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
module Mihari
|
7
|
+
class CLI < Thor
|
8
|
+
desc "censys [QUERY]", "Censys lookup by a given query"
|
9
|
+
def censys(query)
|
10
|
+
with_error_handling do
|
11
|
+
censys = Analyzers::Censys.new(query)
|
12
|
+
censys.run
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "shodan [QUERY]", "Shodan lookup by a given query"
|
17
|
+
def shodan(query)
|
18
|
+
with_error_handling do
|
19
|
+
shodan = Analyzers::Shodan.new(query)
|
20
|
+
shodan.run
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "import_from_json", "Give a JSON input via STDIN"
|
25
|
+
def import_from_json(input = nil)
|
26
|
+
json = input || STDIN.gets.chomp
|
27
|
+
raise ArgumentError, "Input not found: please give an input in a JSON format" unless json
|
28
|
+
|
29
|
+
json = parse_as_json(json)
|
30
|
+
raise ArgumentError, "Invalid input format: an input JSON data should have title, description and artifacts key" unless valid_json?(json)
|
31
|
+
|
32
|
+
title = json.dig("title")
|
33
|
+
description = json.dig("description")
|
34
|
+
artifacts = json.dig("artifacts")
|
35
|
+
|
36
|
+
with_error_handling do
|
37
|
+
basic = Analyzers::Basic.new(title: title, description: description, artifacts: artifacts)
|
38
|
+
basic.run
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
no_commands do
|
43
|
+
def with_error_handling
|
44
|
+
yield
|
45
|
+
rescue ArgumentError, Hachi::Error, Censys::ResponseError => e
|
46
|
+
puts "Warning: #{e}"
|
47
|
+
rescue StandardError => e
|
48
|
+
puts "Warning: #{e}"
|
49
|
+
puts e.backtrace.join('\n')
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse_as_json(input)
|
53
|
+
JSON.parse input
|
54
|
+
rescue JSON::ParserError => _
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [true, false]
|
59
|
+
def valid_json?(json)
|
60
|
+
%w(title description artifacts).all? { |key| json.key? key }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Notifiers
|
5
|
+
class Base
|
6
|
+
def self.inherited(child)
|
7
|
+
Mihari.notifiers << child
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [true, false]
|
11
|
+
def valid?
|
12
|
+
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def notify(title:, description:, artifacts:)
|
16
|
+
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "slack/incoming/webhooks"
|
4
|
+
require "digest/sha2"
|
5
|
+
|
6
|
+
module Mihari
|
7
|
+
module Notifiers
|
8
|
+
class Attachment
|
9
|
+
attr_reader :data, :data_type
|
10
|
+
|
11
|
+
def initialize(data:, data_type:)
|
12
|
+
@data = data
|
13
|
+
@data_type = data_type
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [String]
|
17
|
+
def link
|
18
|
+
case data_type
|
19
|
+
when "hash"
|
20
|
+
"https://www.virustotal.com/#/file/#{data}"
|
21
|
+
when "ip"
|
22
|
+
"https://www.virustotal.com/#/ip-address/#{data}"
|
23
|
+
when "domain"
|
24
|
+
"https://www.virustotal.com/#/domain/#{data}"
|
25
|
+
when "url"
|
26
|
+
"https://www.virustotal.com/#/url/#{sha256}"
|
27
|
+
when "mail"
|
28
|
+
"https://www.virustotal.com/#/search/#{data}"
|
29
|
+
else
|
30
|
+
""
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [Hash]
|
35
|
+
def to_h
|
36
|
+
{
|
37
|
+
fallback: "VT link",
|
38
|
+
title: data,
|
39
|
+
title_link: link,
|
40
|
+
footer: "virustotal.com",
|
41
|
+
footer_icon: "http://www.google.com/s2/favicons?domain=virustotal.com"
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# @return [String]
|
48
|
+
def sha256
|
49
|
+
Digest::SHA256.hexdigest data
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Slack < Base
|
54
|
+
SLACK_WEBHOOK_URL_KEY = "SLACK_WEBHOOK_URL"
|
55
|
+
SLACK_CHANNEL_KEY = "SLACK_CHANNEL"
|
56
|
+
|
57
|
+
def slack_channel
|
58
|
+
ENV.fetch SLACK_CHANNEL_KEY, "#general"
|
59
|
+
end
|
60
|
+
|
61
|
+
def slack_webhook_url
|
62
|
+
ENV.fetch SLACK_WEBHOOK_URL_KEY
|
63
|
+
end
|
64
|
+
|
65
|
+
def slack_webhook_url?
|
66
|
+
ENV.key? SLACK_WEBHOOK_URL_KEY
|
67
|
+
end
|
68
|
+
|
69
|
+
def valid?
|
70
|
+
slack_webhook_url?
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_attachments(artifacts)
|
74
|
+
artifacts.map do |artifact|
|
75
|
+
Attachment.new(data: artifact.data, data_type: artifact.data_type).to_h
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def notify(title:, description:, artifacts:)
|
80
|
+
return if artifacts.empty?
|
81
|
+
|
82
|
+
attachments = to_attachments(artifacts)
|
83
|
+
|
84
|
+
slack = ::Slack::Incoming::Webhooks.new(slack_webhook_url, channel: slack_channel)
|
85
|
+
slack.post("#{title} (#{description})", attachments: attachments)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Notifiers
|
5
|
+
class TheHive < Base
|
6
|
+
attr_reader :api
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@api = Mihari::TheHive.new
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [true, false]
|
13
|
+
def valid?
|
14
|
+
api.valid?
|
15
|
+
end
|
16
|
+
|
17
|
+
def notify(title:, description:, artifacts:)
|
18
|
+
return if artifacts.empty?
|
19
|
+
|
20
|
+
res = api.create_alert(title: title, description: description, artifacts: artifacts.map(&:to_h))
|
21
|
+
id = res.dig("id")
|
22
|
+
puts "A new alret is created. (id: #{id})"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
class TheHive
|
5
|
+
# @return [true, false]
|
6
|
+
def api_endpont?
|
7
|
+
ENV.key? "THEHIVE_API_ENDPOINT"
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [true, false]
|
11
|
+
def api_key?
|
12
|
+
ENV.key? "THEHIVE_API_KEY"
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [true, false]
|
16
|
+
def valid?
|
17
|
+
api_endpont? && api_key?
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Hachi::API]
|
21
|
+
def api
|
22
|
+
@api ||= Hachi::API.new
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Hash]
|
26
|
+
def search(data:, data_type:, range: "all")
|
27
|
+
api.artifact.search(data: data, data_type: data_type, range: range)
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [true, false]
|
31
|
+
def exists?(data:, data_type:)
|
32
|
+
res = search(data: data, data_type: data_type, range: "0-1")
|
33
|
+
!res.empty?
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Hash]
|
37
|
+
def create_alert(title:, description:, artifacts:)
|
38
|
+
api.alert.create(title: title, description: description, artifacts: artifacts, type: "external", source: "mihari")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "addressable/uri"
|
4
|
+
require "email_address"
|
5
|
+
require "ipaddr"
|
6
|
+
require "public_suffix"
|
7
|
+
|
8
|
+
module Mihari
|
9
|
+
class TypeChecker
|
10
|
+
attr_reader :data
|
11
|
+
|
12
|
+
def initialize(data)
|
13
|
+
@data = data
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [true, false]
|
17
|
+
def hash?
|
18
|
+
md5? || sha1? || sha256? || sha512?
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [true, false]
|
22
|
+
def ip?
|
23
|
+
IPAddr.new data
|
24
|
+
true
|
25
|
+
rescue IPAddr::InvalidAddressError => _
|
26
|
+
false
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [true, false]
|
30
|
+
def domain?
|
31
|
+
uri = Addressable::URI.parse("http://#{data}")
|
32
|
+
uri.host == data && PublicSuffix.valid?(uri.host)
|
33
|
+
rescue Addressable::URI::InvalidURIError => _
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [true, false]
|
38
|
+
def url?
|
39
|
+
uri = Addressable::URI.parse(data)
|
40
|
+
uri.scheme && uri.host && uri.path && PublicSuffix.valid?(uri.host)
|
41
|
+
rescue Addressable::URI::InvalidURIError => _
|
42
|
+
false
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [true, false]
|
46
|
+
def mail?
|
47
|
+
EmailAddress.valid? data
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return [String, nil]
|
51
|
+
def type
|
52
|
+
return "hash" if hash?
|
53
|
+
return "ip" if ip?
|
54
|
+
return "domain" if domain?
|
55
|
+
return "url" if url?
|
56
|
+
return "mail" if mail?
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [String, nil]
|
60
|
+
def self.type(data)
|
61
|
+
new(data).type
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# @return [true, false]
|
67
|
+
def md5?
|
68
|
+
data.match? /^[A-Fa-f0-9]{32}$/
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [true, false]
|
72
|
+
def sha1?
|
73
|
+
data.match? /^[A-Fa-f0-9]{40}$/
|
74
|
+
end
|
75
|
+
|
76
|
+
# @return [true, false]
|
77
|
+
def sha256?
|
78
|
+
data.match? /^[A-Fa-f0-9]{64}$/
|
79
|
+
end
|
80
|
+
|
81
|
+
# @return [true, false]
|
82
|
+
def sha512?
|
83
|
+
data.match? /^[A-Fa-f0-9]{128}$/
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/mihari.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "mem"
|
4
|
+
|
5
|
+
module Mihari
|
6
|
+
class << self
|
7
|
+
include Mem
|
8
|
+
|
9
|
+
def notifiers
|
10
|
+
[]
|
11
|
+
end
|
12
|
+
memoize :notifiers
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
require "mihari/version"
|
17
|
+
|
18
|
+
require "mihari/errors"
|
19
|
+
|
20
|
+
require "mihari/type_checker"
|
21
|
+
require "mihari/artifact"
|
22
|
+
|
23
|
+
require "mihari/the_hive"
|
24
|
+
|
25
|
+
require "mihari/analyzers/base"
|
26
|
+
require "mihari/analyzers/basic"
|
27
|
+
require "mihari/analyzers/censys"
|
28
|
+
require "mihari/analyzers/shodan"
|
29
|
+
|
30
|
+
require "mihari/notifiers/base"
|
31
|
+
require "mihari/notifiers/slack"
|
32
|
+
require "mihari/notifiers/the_hive"
|
33
|
+
|
34
|
+
require "mihari/cli"
|
data/mihari.gemspec
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require "mihari/version"
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "mihari"
|
9
|
+
spec.version = Mihari::VERSION
|
10
|
+
spec.authors = ["Manabu Niseki"]
|
11
|
+
spec.email = ["manabu.niseki@gmail.com"]
|
12
|
+
|
13
|
+
spec.summary = "A framework for continuous malicious hosts monitoring."
|
14
|
+
spec.description = "A framework for continuous malicious hosts monitoring."
|
15
|
+
spec.homepage = "https://github.com/ninoseki/mihari"
|
16
|
+
spec.license = "MIT"
|
17
|
+
|
18
|
+
# Specify which files should be added to the gem when it is released.
|
19
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
20
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
21
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
22
|
+
end
|
23
|
+
spec.bindir = "exe"
|
24
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
25
|
+
spec.require_paths = ["lib"]
|
26
|
+
|
27
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
28
|
+
spec.add_development_dependency "coveralls", "~> 0.8"
|
29
|
+
spec.add_development_dependency "rake", "~> 12.3"
|
30
|
+
spec.add_development_dependency "rspec", "~> 3.8"
|
31
|
+
spec.add_development_dependency "vcr", "~> 4.0"
|
32
|
+
spec.add_development_dependency "webmock", "~> 3.5"
|
33
|
+
|
34
|
+
spec.add_dependency "addressable", "~> 2.6"
|
35
|
+
spec.add_dependency "censu", "~> 0.2"
|
36
|
+
spec.add_dependency "email_address", "~> 0.1"
|
37
|
+
spec.add_dependency "hachi", "~> 0.1"
|
38
|
+
spec.add_dependency "mem", "~> 0.1"
|
39
|
+
spec.add_dependency "public_suffix", "~> 3.0"
|
40
|
+
spec.add_dependency "slack-incoming-webhooks", "~> 0.2"
|
41
|
+
spec.add_dependency "thor", "~> 0.19"
|
42
|
+
end
|
metadata
ADDED
@@ -0,0 +1,267 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mihari
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Manabu Niseki
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-04-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: coveralls
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.8'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.8'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '12.3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '12.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.8'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.8'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: vcr
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '4.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '4.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: webmock
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.5'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.5'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: addressable
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.6'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '2.6'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: censu
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.2'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.2'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: email_address
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0.1'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0.1'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: hachi
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0.1'
|
146
|
+
type: :runtime
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0.1'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: mem
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0.1'
|
160
|
+
type: :runtime
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0.1'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: public_suffix
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '3.0'
|
174
|
+
type: :runtime
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '3.0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: slack-incoming-webhooks
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - "~>"
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0.2'
|
188
|
+
type: :runtime
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - "~>"
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0.2'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: thor
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - "~>"
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0.19'
|
202
|
+
type: :runtime
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - "~>"
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0.19'
|
209
|
+
description: A framework for continuous malicious hosts monitoring.
|
210
|
+
email:
|
211
|
+
- manabu.niseki@gmail.com
|
212
|
+
executables:
|
213
|
+
- mihari
|
214
|
+
extensions: []
|
215
|
+
extra_rdoc_files: []
|
216
|
+
files:
|
217
|
+
- ".gitignore"
|
218
|
+
- ".rspec"
|
219
|
+
- ".travis.yml"
|
220
|
+
- Gemfile
|
221
|
+
- LICENSE
|
222
|
+
- README.md
|
223
|
+
- Rakefile
|
224
|
+
- bin/console
|
225
|
+
- bin/setup
|
226
|
+
- examples/ipinfo_hosted_domains.rb
|
227
|
+
- examples/vt_passive_dns.rb
|
228
|
+
- exe/mihari
|
229
|
+
- lib/mihari.rb
|
230
|
+
- lib/mihari/analyzers/base.rb
|
231
|
+
- lib/mihari/analyzers/basic.rb
|
232
|
+
- lib/mihari/analyzers/censys.rb
|
233
|
+
- lib/mihari/analyzers/shodan.rb
|
234
|
+
- lib/mihari/artifact.rb
|
235
|
+
- lib/mihari/cli.rb
|
236
|
+
- lib/mihari/errors.rb
|
237
|
+
- lib/mihari/notifiers/base.rb
|
238
|
+
- lib/mihari/notifiers/slack.rb
|
239
|
+
- lib/mihari/notifiers/the_hive.rb
|
240
|
+
- lib/mihari/the_hive.rb
|
241
|
+
- lib/mihari/type_checker.rb
|
242
|
+
- lib/mihari/version.rb
|
243
|
+
- mihari.gemspec
|
244
|
+
homepage: https://github.com/ninoseki/mihari
|
245
|
+
licenses:
|
246
|
+
- MIT
|
247
|
+
metadata: {}
|
248
|
+
post_install_message:
|
249
|
+
rdoc_options: []
|
250
|
+
require_paths:
|
251
|
+
- lib
|
252
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
253
|
+
requirements:
|
254
|
+
- - ">="
|
255
|
+
- !ruby/object:Gem::Version
|
256
|
+
version: '0'
|
257
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
258
|
+
requirements:
|
259
|
+
- - ">="
|
260
|
+
- !ruby/object:Gem::Version
|
261
|
+
version: '0'
|
262
|
+
requirements: []
|
263
|
+
rubygems_version: 3.0.2
|
264
|
+
signing_key:
|
265
|
+
specification_version: 4
|
266
|
+
summary: A framework for continuous malicious hosts monitoring.
|
267
|
+
test_files: []
|