mihari 0.17.5 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.rubocop.yml +155 -0
- data/.travis.yml +1 -0
- data/Gemfile +2 -0
- data/README.md +30 -72
- data/config/pre_commit.yml +3 -0
- data/lib/mihari.rb +12 -8
- data/lib/mihari/alert_viewer.rb +6 -28
- data/lib/mihari/analyzers/base.rb +7 -19
- data/lib/mihari/analyzers/basic.rb +3 -1
- data/lib/mihari/analyzers/binaryedge.rb +2 -2
- data/lib/mihari/analyzers/censys.rb +2 -2
- data/lib/mihari/analyzers/circl.rb +2 -2
- data/lib/mihari/analyzers/onyphe.rb +3 -3
- data/lib/mihari/analyzers/passivetotal.rb +2 -2
- data/lib/mihari/analyzers/pulsedive.rb +2 -2
- data/lib/mihari/analyzers/securitytrails.rb +2 -2
- data/lib/mihari/analyzers/securitytrails_domain_feed.rb +2 -2
- data/lib/mihari/analyzers/shodan.rb +2 -2
- data/lib/mihari/analyzers/virustotal.rb +2 -2
- data/lib/mihari/analyzers/zoomeye.rb +2 -2
- data/lib/mihari/cli.rb +2 -2
- data/lib/mihari/config.rb +68 -2
- data/lib/mihari/configurable.rb +1 -1
- data/lib/mihari/database.rb +45 -0
- data/lib/mihari/emitters/base.rb +1 -1
- data/lib/mihari/emitters/misp.rb +8 -1
- data/lib/mihari/emitters/slack.rb +2 -2
- data/lib/mihari/emitters/sqlite.rb +29 -0
- data/lib/mihari/emitters/stdout.rb +2 -1
- data/lib/mihari/emitters/the_hive.rb +28 -14
- data/lib/mihari/models/alert.rb +11 -0
- data/lib/mihari/models/artifact.rb +27 -0
- data/lib/mihari/models/tag.rb +10 -0
- data/lib/mihari/models/tagging.rb +10 -0
- data/lib/mihari/notifiers/slack.rb +4 -4
- data/lib/mihari/serializers/alert.rb +12 -0
- data/lib/mihari/serializers/artifact.rb +9 -0
- data/lib/mihari/serializers/tag.rb +9 -0
- data/lib/mihari/status.rb +1 -1
- data/lib/mihari/type_checker.rb +1 -1
- data/lib/mihari/version.rb +1 -1
- data/mihari.gemspec +11 -5
- metadata +120 -31
- data/lib/mihari/artifact.rb +0 -36
- data/lib/mihari/cache.rb +0 -35
- data/lib/mihari/the_hive.rb +0 -42
- data/lib/mihari/the_hive/alert.rb +0 -25
- data/lib/mihari/the_hive/artifact.rb +0 -33
- data/lib/mihari/the_hive/base.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '099c3dece3c31a745979b619c361d3bcff09e67abb3214c32747a935d57be963'
|
4
|
+
data.tar.gz: 1f777f2ca61721716e32e85846817a5183d11063573813adec757919bb27f457
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b8e0babf78935a90d1d4d39493ed7db77d498697a1ea8d2bbdf3277745544a0f7a59de6dc444e93065f66c339b9c5208df408748194cb490ffe3901681a9aa0b
|
7
|
+
data.tar.gz: 0b430d270f34f1fae64f384b672a4270500407e725f0f8f99bba278b134d74196fda894b4ed425ed21d363583da93e40648dce77b9ff39a9b0da07c7f5a7a5d2
|
data/.gitignore
CHANGED
data/.rubocop.yml
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
# Relaxed.Ruby.Style
|
2
|
+
## Version 2.5
|
3
|
+
|
4
|
+
require:
|
5
|
+
- rubocop-performance
|
6
|
+
|
7
|
+
Style/Alias:
|
8
|
+
Enabled: false
|
9
|
+
StyleGuide: https://relaxed.ruby.style/#stylealias
|
10
|
+
|
11
|
+
Style/AsciiComments:
|
12
|
+
Enabled: false
|
13
|
+
StyleGuide: https://relaxed.ruby.style/#styleasciicomments
|
14
|
+
|
15
|
+
Style/BeginBlock:
|
16
|
+
Enabled: false
|
17
|
+
StyleGuide: https://relaxed.ruby.style/#stylebeginblock
|
18
|
+
|
19
|
+
Style/BlockDelimiters:
|
20
|
+
Enabled: false
|
21
|
+
StyleGuide: https://relaxed.ruby.style/#styleblockdelimiters
|
22
|
+
|
23
|
+
Style/CommentAnnotation:
|
24
|
+
Enabled: false
|
25
|
+
StyleGuide: https://relaxed.ruby.style/#stylecommentannotation
|
26
|
+
|
27
|
+
Style/Documentation:
|
28
|
+
Enabled: false
|
29
|
+
StyleGuide: https://relaxed.ruby.style/#styledocumentation
|
30
|
+
|
31
|
+
Layout/DotPosition:
|
32
|
+
Enabled: false
|
33
|
+
StyleGuide: https://relaxed.ruby.style/#layoutdotposition
|
34
|
+
|
35
|
+
Style/DoubleNegation:
|
36
|
+
Enabled: false
|
37
|
+
StyleGuide: https://relaxed.ruby.style/#styledoublenegation
|
38
|
+
|
39
|
+
Style/EndBlock:
|
40
|
+
Enabled: false
|
41
|
+
StyleGuide: https://relaxed.ruby.style/#styleendblock
|
42
|
+
|
43
|
+
Style/FormatString:
|
44
|
+
Enabled: false
|
45
|
+
StyleGuide: https://relaxed.ruby.style/#styleformatstring
|
46
|
+
|
47
|
+
Style/IfUnlessModifier:
|
48
|
+
Enabled: false
|
49
|
+
StyleGuide: https://relaxed.ruby.style/#styleifunlessmodifier
|
50
|
+
|
51
|
+
Style/Lambda:
|
52
|
+
Enabled: false
|
53
|
+
StyleGuide: https://relaxed.ruby.style/#stylelambda
|
54
|
+
|
55
|
+
Style/ModuleFunction:
|
56
|
+
Enabled: false
|
57
|
+
StyleGuide: https://relaxed.ruby.style/#stylemodulefunction
|
58
|
+
|
59
|
+
Style/MultilineBlockChain:
|
60
|
+
Enabled: false
|
61
|
+
StyleGuide: https://relaxed.ruby.style/#stylemultilineblockchain
|
62
|
+
|
63
|
+
Style/NegatedIf:
|
64
|
+
Enabled: false
|
65
|
+
StyleGuide: https://relaxed.ruby.style/#stylenegatedif
|
66
|
+
|
67
|
+
Style/NegatedWhile:
|
68
|
+
Enabled: false
|
69
|
+
StyleGuide: https://relaxed.ruby.style/#stylenegatedwhile
|
70
|
+
|
71
|
+
Style/NumericPredicate:
|
72
|
+
Enabled: false
|
73
|
+
StyleGuide: https://relaxed.ruby.style/#stylenumericpredicate
|
74
|
+
|
75
|
+
Style/ParallelAssignment:
|
76
|
+
Enabled: false
|
77
|
+
StyleGuide: https://relaxed.ruby.style/#styleparallelassignment
|
78
|
+
|
79
|
+
Style/PercentLiteralDelimiters:
|
80
|
+
Enabled: false
|
81
|
+
StyleGuide: https://relaxed.ruby.style/#stylepercentliteraldelimiters
|
82
|
+
|
83
|
+
Style/PerlBackrefs:
|
84
|
+
Enabled: false
|
85
|
+
StyleGuide: https://relaxed.ruby.style/#styleperlbackrefs
|
86
|
+
|
87
|
+
Style/Semicolon:
|
88
|
+
Enabled: false
|
89
|
+
StyleGuide: https://relaxed.ruby.style/#stylesemicolon
|
90
|
+
|
91
|
+
Style/SignalException:
|
92
|
+
Enabled: false
|
93
|
+
StyleGuide: https://relaxed.ruby.style/#stylesignalexception
|
94
|
+
|
95
|
+
Style/SingleLineBlockParams:
|
96
|
+
Enabled: false
|
97
|
+
StyleGuide: https://relaxed.ruby.style/#stylesinglelineblockparams
|
98
|
+
|
99
|
+
Style/SingleLineMethods:
|
100
|
+
Enabled: false
|
101
|
+
StyleGuide: https://relaxed.ruby.style/#stylesinglelinemethods
|
102
|
+
|
103
|
+
Layout/SpaceBeforeBlockBraces:
|
104
|
+
Enabled: false
|
105
|
+
StyleGuide: https://relaxed.ruby.style/#layoutspacebeforeblockbraces
|
106
|
+
|
107
|
+
Layout/SpaceInsideParens:
|
108
|
+
Enabled: false
|
109
|
+
StyleGuide: https://relaxed.ruby.style/#layoutspaceinsideparens
|
110
|
+
|
111
|
+
Style/SpecialGlobalVars:
|
112
|
+
Enabled: false
|
113
|
+
StyleGuide: https://relaxed.ruby.style/#stylespecialglobalvars
|
114
|
+
|
115
|
+
Style/StringLiterals:
|
116
|
+
Enabled: false
|
117
|
+
StyleGuide: https://relaxed.ruby.style/#stylestringliterals
|
118
|
+
|
119
|
+
Style/TrailingCommaInArguments:
|
120
|
+
Enabled: false
|
121
|
+
StyleGuide: https://relaxed.ruby.style/#styletrailingcommainarguments
|
122
|
+
|
123
|
+
Style/TrailingCommaInArrayLiteral:
|
124
|
+
Enabled: false
|
125
|
+
StyleGuide: https://relaxed.ruby.style/#styletrailingcommainarrayliteral
|
126
|
+
|
127
|
+
Style/TrailingCommaInHashLiteral:
|
128
|
+
Enabled: false
|
129
|
+
StyleGuide: https://relaxed.ruby.style/#styletrailingcommainhashliteral
|
130
|
+
|
131
|
+
Style/SymbolArray:
|
132
|
+
Enabled: false
|
133
|
+
StyleGuide: http://relaxed.ruby.style/#stylesymbolarray
|
134
|
+
|
135
|
+
Style/WhileUntilModifier:
|
136
|
+
Enabled: false
|
137
|
+
StyleGuide: https://relaxed.ruby.style/#stylewhileuntilmodifier
|
138
|
+
|
139
|
+
Style/WordArray:
|
140
|
+
Enabled: false
|
141
|
+
StyleGuide: https://relaxed.ruby.style/#stylewordarray
|
142
|
+
|
143
|
+
Lint/AmbiguousRegexpLiteral:
|
144
|
+
Enabled: false
|
145
|
+
StyleGuide: https://relaxed.ruby.style/#lintambiguousregexpliteral
|
146
|
+
|
147
|
+
Lint/AssignmentInCondition:
|
148
|
+
Enabled: false
|
149
|
+
StyleGuide: https://relaxed.ruby.style/#lintassignmentincondition
|
150
|
+
|
151
|
+
Layout/LineLength:
|
152
|
+
Enabled: false
|
153
|
+
|
154
|
+
Metrics:
|
155
|
+
Enabled: false
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -10,19 +10,15 @@ 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 from the results.
|
14
|
-
- Mihari checks whether
|
13
|
+
- Mihari makes a query against Shodan, Censys, VirusTotal, SecurityTrails, etc. and extracts artifacts (IP addresses, domains, URLs and hashes) from the results.
|
14
|
+
- Mihari checks whether a DB (SQLite3) contains the artifacts or not.
|
15
15
|
- If it doesn't contain the artifacts:
|
16
|
-
- Mihari creates an alert on TheHive.
|
16
|
+
- Mihari creates an alert on TheHive. (Optional)
|
17
17
|
- Mihari sends a notification to Slack. (Optional)
|
18
18
|
- Mihari creates an event on MISP. (Optional)
|
19
19
|
|
20
20
|
![img](https://github.com/ninoseki/mihari/raw/master/screenshots/eyecatch.png)
|
21
21
|
|
22
|
-
Check this blog post for more details: [Continuous C2 hunting with Censys, Shodan, Onyphe and TheHive](https://hackmd.io/s/SkUaSrqoE).
|
23
|
-
|
24
|
-
You can use mihari without TheHive but note that mihari depends on TheHive to manage artifacts. It means mihari might make duplications when without TheHive.
|
25
|
-
|
26
22
|
### Screenshots
|
27
23
|
|
28
24
|
- TheHive alert example
|
@@ -156,49 +152,13 @@ mihari http_hash --html /tmp/index.html
|
|
156
152
|
|
157
153
|
```bash
|
158
154
|
# Censys lookup for PANDA C2
|
159
|
-
|
160
|
-
{
|
161
|
-
"title": "PANDA C2",
|
162
|
-
"description": "query = (\"PANDA\" AND \"SMAdmin\" AND \"layui\")",
|
163
|
-
"artifacts": [
|
164
|
-
"154.223.165.223",
|
165
|
-
"154.194.2.31",
|
166
|
-
"45.114.127.119",
|
167
|
-
"..."
|
168
|
-
],
|
169
|
-
"tags": []
|
170
|
-
}
|
155
|
+
mihari censys '("PANDA" AND "SMAdmin" AND "layui")' --title "PANDA C2"
|
171
156
|
|
172
157
|
# VirusTotal passive DNS lookup of a FAKESPY host
|
173
|
-
|
174
|
-
{
|
175
|
-
"title": "FAKESPY host passive DNS results",
|
176
|
-
"description": "indicator = jppost-hi.top",
|
177
|
-
"artifacts": [
|
178
|
-
"185.22.152.28",
|
179
|
-
"192.236.200.44",
|
180
|
-
"193.148.69.12",
|
181
|
-
"..."
|
182
|
-
],
|
183
|
-
"tags": []
|
184
|
-
}
|
158
|
+
mihari virustotal "jppost-hi.top" --title "FAKESPY passive DNS"
|
185
159
|
|
186
160
|
# You can pass a "defanged" indicator as an input
|
187
|
-
|
188
|
-
|
189
|
-
# SecurityTrails domain feed lookup for finding (possibly) Apple phishing websites
|
190
|
-
$ mihari securitytrails_domain_feed "apple-" --type new
|
191
|
-
{
|
192
|
-
"title": "SecurityTrails domain feed lookup",
|
193
|
-
"description": "Regexp = /apple-/",
|
194
|
-
"artifacts": [
|
195
|
-
"apple-sign.online",
|
196
|
-
"apple-log-in.com",
|
197
|
-
"apple-locator-id.info",
|
198
|
-
"..."
|
199
|
-
],
|
200
|
-
"tags": []
|
201
|
-
}
|
161
|
+
mihari virustotal "jppost-hi[.]top" --title "FAKESPY passive DNS"
|
202
162
|
```
|
203
163
|
|
204
164
|
### Import from JSON
|
@@ -229,28 +189,29 @@ The input is a JSON data should have `title`, `description` and `artifacts` key.
|
|
229
189
|
|
230
190
|
Configuration can be done via environment variables or a YAML file.
|
231
191
|
|
232
|
-
| Key |
|
233
|
-
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
| ZOOMEYE_PASSWORD | ZoomEye password |
|
192
|
+
| Key | Description | Default |
|
193
|
+
|------------------------|--------------------------------|-------------|
|
194
|
+
| DATABASE | A path to the SQLite database | `mihari.db` |
|
195
|
+
| BINARYEDGE_API_KEY | BinaryEdge API key | |
|
196
|
+
| CENSYS_ID | Censys API ID | |
|
197
|
+
| CENSYS_SECRET | Censys secret | |
|
198
|
+
| CIRCL_PASSIVE_PASSWORD | CIRCL passive DNS/SSL password | |
|
199
|
+
| CIRCL_PASSIVE_USERNAME | CIRCL passive DNS/SSL username | |
|
200
|
+
| MISP_API_ENDPOINT | MISP URL | |
|
201
|
+
| MISP_API_KEY | MISP API key | |
|
202
|
+
| ONYPHE_API_KEY | Onyphe API key | |
|
203
|
+
| PASSIVETOTAL_API_KEY | PassiveTotal API key | |
|
204
|
+
| PASSIVETOTAL_USERNAME | PassiveTotal username | |
|
205
|
+
| PULSEDIVE_API_KEY | Pulsedive API key | |
|
206
|
+
| SECURITYTRAILS_API_KEY | SecurityTrails API key | |
|
207
|
+
| SHODAN_API_KEY | Shodan API key | |
|
208
|
+
| SLACK_CHANNEL | Slack channel name | `#general` |
|
209
|
+
| SLACK_WEBHOOK_URL | Slack Webhook URL | |
|
210
|
+
| THEHIVE_API_ENDPOINT | TheHive URL | |
|
211
|
+
| THEHIVE_API_KEY | TheHive API key | |
|
212
|
+
| VIRUSTOTAL_API_KEY | VirusTotal API key | |
|
213
|
+
| ZOOMEYE_PASSWORD | ZoomEye password | |
|
214
|
+
| ZOOMEYE_USERNAMME | ZoomEye username | |
|
254
215
|
|
255
216
|
Instead of using environment variables, you can use a YAML file for configuration.
|
256
217
|
|
@@ -261,6 +222,7 @@ mihari virustotal 1.1.1.1 --config /path/to/yaml.yml
|
|
261
222
|
The YAML file should be a YAML hash like below:
|
262
223
|
|
263
224
|
```yaml
|
225
|
+
database: /tmp/mihari.db
|
264
226
|
thehive_api_endpoint: https://localhost
|
265
227
|
thehive_api_key: foo
|
266
228
|
virustotal_api_key: foo
|
@@ -314,10 +276,6 @@ example.run
|
|
314
276
|
|
315
277
|
See `/examples` for more.
|
316
278
|
|
317
|
-
## Caching
|
318
|
-
|
319
|
-
Mihari caches execution results in `/tmp/mihari` and the default cache duration is 7 days. If you want to clear the cache, please clear `/tmp/mihari`.
|
320
|
-
|
321
279
|
## Using it with Docker
|
322
280
|
|
323
281
|
```bash
|
data/lib/mihari.rb
CHANGED
@@ -19,24 +19,27 @@ module Mihari
|
|
19
19
|
end
|
20
20
|
|
21
21
|
require "mihari/version"
|
22
|
-
|
23
22
|
require "mihari/errors"
|
24
23
|
|
25
|
-
require "mihari/artifact"
|
26
|
-
require "mihari/cache"
|
27
24
|
require "mihari/config"
|
25
|
+
|
26
|
+
require "mihari/database"
|
28
27
|
require "mihari/type_checker"
|
29
28
|
|
29
|
+
require "mihari/models/alert"
|
30
|
+
require "mihari/models/artifact"
|
31
|
+
require "mihari/models/tag"
|
32
|
+
require "mihari/models/tagging"
|
33
|
+
|
34
|
+
require "mihari/serializers/alert"
|
35
|
+
require "mihari/serializers/artifact"
|
36
|
+
require "mihari/serializers/tag"
|
37
|
+
|
30
38
|
require "mihari/html"
|
31
39
|
|
32
40
|
require "mihari/configurable"
|
33
41
|
require "mihari/retriable"
|
34
42
|
|
35
|
-
require "mihari/the_hive/base"
|
36
|
-
require "mihari/the_hive/alert"
|
37
|
-
require "mihari/the_hive/artifact"
|
38
|
-
require "mihari/the_hive"
|
39
|
-
|
40
43
|
require "mihari/analyzers/base"
|
41
44
|
require "mihari/analyzers/basic"
|
42
45
|
|
@@ -70,6 +73,7 @@ require "mihari/notifiers/exception_notifier"
|
|
70
73
|
require "mihari/emitters/base"
|
71
74
|
require "mihari/emitters/misp"
|
72
75
|
require "mihari/emitters/slack"
|
76
|
+
require "mihari/emitters/sqlite"
|
73
77
|
require "mihari/emitters/stdout"
|
74
78
|
require "mihari/emitters/the_hive"
|
75
79
|
|
data/lib/mihari/alert_viewer.rb
CHANGED
@@ -3,39 +3,17 @@
|
|
3
3
|
module Mihari
|
4
4
|
class AlertViewer
|
5
5
|
attr_reader :limit
|
6
|
-
attr_reader :the_hive
|
7
|
-
|
8
|
-
ALERT_KEYS = %w(title description artifacts tags createdAt status).freeze
|
9
6
|
|
10
7
|
def initialize(limit: 5)
|
11
|
-
@limit = limit
|
12
|
-
|
13
|
-
|
14
|
-
@the_hive = TheHive.new
|
15
|
-
raise Error, "Cannot connect to the TheHive instance" unless the_hive.valid?
|
8
|
+
@limit = limit.to_i
|
9
|
+
raise ArgumentError, "limit should be bigger than zero" unless @limit.positive?
|
16
10
|
end
|
17
11
|
|
18
12
|
def list
|
19
|
-
|
20
|
-
alerts
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
def validate_limit
|
27
|
-
return true if limit == "all"
|
28
|
-
|
29
|
-
raise ArgumentError, "limit should be bigger than zero" unless limit.to_i.positive?
|
30
|
-
end
|
31
|
-
|
32
|
-
def convert(alert)
|
33
|
-
attributes = alert.select { |k, _v| ALERT_KEYS.include? k }
|
34
|
-
attributes["createdAt"] = Time.at(attributes["createdAt"] / 1000).to_s
|
35
|
-
attributes["artifacts"] = (attributes.dig("artifacts") || []).map do |artifact|
|
36
|
-
artifact.dig("data")
|
37
|
-
end.sort
|
38
|
-
attributes
|
13
|
+
alerts = Alert.order(id: :desc).limit(limit).includes(:tags, :artifacts)
|
14
|
+
alerts.map do |alert|
|
15
|
+
AlertSerializer.new(alert).as_json
|
16
|
+
end
|
39
17
|
end
|
40
18
|
end
|
41
19
|
end
|
@@ -23,6 +23,10 @@ module Mihari
|
|
23
23
|
raise NotImplementedError, "You must implement #{self.class}##{__method__}"
|
24
24
|
end
|
25
25
|
|
26
|
+
def source
|
27
|
+
self.class.to_s.split("::").last
|
28
|
+
end
|
29
|
+
|
26
30
|
# @return [Array<String>]
|
27
31
|
def tags
|
28
32
|
[]
|
@@ -37,7 +41,7 @@ module Mihari
|
|
37
41
|
end
|
38
42
|
|
39
43
|
def run_emitter(emitter)
|
40
|
-
emitter.run(title: title, description: description, artifacts: unique_artifacts, tags: tags)
|
44
|
+
emitter.run(title: title, description: description, artifacts: unique_artifacts, source: source, tags: tags)
|
41
45
|
rescue StandardError => e
|
42
46
|
puts "Emission by #{emitter.class} is failed: #{e}"
|
43
47
|
end
|
@@ -48,32 +52,16 @@ module Mihari
|
|
48
52
|
|
49
53
|
private
|
50
54
|
|
51
|
-
def the_hive
|
52
|
-
@the_hive ||= TheHive.new
|
53
|
-
end
|
54
|
-
|
55
|
-
def cache
|
56
|
-
@cache ||= Cache.new
|
57
|
-
end
|
58
|
-
|
59
55
|
# @return [Array<Mihari::Artifact>]
|
60
56
|
def normalized_artifacts
|
61
57
|
@normalized_artifacts ||= artifacts.compact.uniq.sort.map do |artifact|
|
62
|
-
artifact.is_a?(Artifact) ? artifact : Artifact.new(artifact)
|
58
|
+
artifact.is_a?(Artifact) ? artifact : Artifact.new(data: artifact)
|
63
59
|
end.select(&:valid?)
|
64
60
|
end
|
65
61
|
|
66
|
-
def uncached_artifacts
|
67
|
-
@uncached_artifacts ||= normalized_artifacts.reject do |artifact|
|
68
|
-
cache.cached? artifact.data
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
62
|
# @return [Array<Mihari::Artifact>]
|
73
63
|
def unique_artifacts
|
74
|
-
|
75
|
-
|
76
|
-
@unique_artifacts ||= the_hive.artifact.find_non_existing_artifacts(uncached_artifacts)
|
64
|
+
@unique_artifacts ||= normalized_artifacts.select(&:unique?)
|
77
65
|
end
|
78
66
|
|
79
67
|
def set_unique_artifacts
|