mihari 5.3.1 → 5.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -0
- data/frontend/package-lock.json +672 -652
- data/frontend/package.json +22 -22
- data/frontend/src/ace-config.ts +6 -0
- data/frontend/src/components/alert/Form.vue +2 -2
- data/frontend/src/components/rule/EditRule.vue +3 -2
- data/frontend/src/components/rule/Form.vue +2 -2
- data/frontend/src/components/rule/InputForm.vue +18 -59
- data/frontend/src/components/rule/YAML.vue +21 -28
- data/frontend/src/views/Artifact.vue +3 -8
- data/frontend/src/views/EditRule.vue +2 -7
- data/frontend/src/views/Rule.vue +3 -8
- data/lib/mihari/analyzers/base.rb +16 -3
- data/lib/mihari/analyzers/binaryedge.rb +2 -2
- data/lib/mihari/analyzers/censys.rb +2 -2
- data/lib/mihari/analyzers/hunterhow.rb +68 -0
- data/lib/mihari/analyzers/onyphe.rb +2 -2
- data/lib/mihari/analyzers/rule.rb +5 -7
- data/lib/mihari/analyzers/shodan.rb +2 -2
- data/lib/mihari/analyzers/urlscan.rb +2 -2
- data/lib/mihari/analyzers/virustotal_intelligence.rb +2 -2
- data/lib/mihari/analyzers/zoomeye.rb +4 -4
- data/lib/mihari/clients/hunterhow.rb +47 -0
- data/lib/mihari/commands/rule.rb +3 -3
- data/lib/mihari/commands/search.rb +3 -3
- data/lib/mihari/config.rb +46 -27
- data/lib/mihari/constants.rb +3 -3
- data/lib/mihari/emitters/base.rb +2 -2
- data/lib/mihari/emitters/misp.rb +3 -3
- data/lib/mihari/emitters/slack.rb +1 -1
- data/lib/mihari/emitters/the_hive.rb +1 -1
- data/lib/mihari/emitters/webhook.rb +1 -1
- data/lib/mihari/mixins/configurable.rb +5 -0
- data/lib/mihari/mixins/falsepositive.rb +1 -1
- data/lib/mihari/mixins/retriable.rb +0 -2
- data/lib/mihari/schemas/analyzer.rb +12 -2
- data/lib/mihari/schemas/rule.rb +1 -1
- data/lib/mihari/{structs → services}/rule.rb +16 -16
- data/lib/mihari/structs/hunterhow.rb +104 -0
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/endpoints/rules.rb +9 -8
- data/lib/mihari/web/public/assets/index-33165282.css +1 -0
- data/lib/mihari/web/public/assets/index-61dc587c.js +1738 -0
- data/lib/mihari/web/public/assets/mode-yaml-a21faa53.js +8 -0
- data/lib/mihari/web/public/index.html +2 -2
- data/lib/mihari.rb +6 -2
- data/mihari.gemspec +6 -5
- metadata +67 -20
- data/lib/mihari/web/public/assets/index-b17c40c6.css +0 -1
- data/lib/mihari/web/public/assets/index-f740e4f9.js +0 -799
data/frontend/package.json
CHANGED
@@ -14,11 +14,12 @@
|
|
14
14
|
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false"
|
15
15
|
},
|
16
16
|
"dependencies": {
|
17
|
-
"@fortawesome/fontawesome-svg-core": "^6.4.
|
18
|
-
"@fortawesome/free-solid-svg-icons": "^6.4.
|
17
|
+
"@fortawesome/fontawesome-svg-core": "^6.4.2",
|
18
|
+
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
19
19
|
"@fortawesome/vue-fontawesome": "^3.0.3",
|
20
|
-
"@vueuse/core": "^10.
|
21
|
-
"@vueuse/router": "^10.
|
20
|
+
"@vueuse/core": "^10.3.0",
|
21
|
+
"@vueuse/router": "^10.3.0",
|
22
|
+
"ace-builds": "^1.24.0",
|
22
23
|
"axios": "^1.4.0",
|
23
24
|
"bulma": "^0.9.4",
|
24
25
|
"bulma-helpers": "^0.4.3",
|
@@ -32,36 +33,35 @@
|
|
32
33
|
"vue": "^3.3.4",
|
33
34
|
"vue-concurrency": "4.0.1",
|
34
35
|
"vue-json-pretty": "^2.2.4",
|
35
|
-
"vue-
|
36
|
-
"
|
36
|
+
"vue-router": "^4.2.4",
|
37
|
+
"vue3-ace-editor": "^2.2.3"
|
37
38
|
},
|
38
39
|
"devDependencies": {
|
39
|
-
"@redocly/cli": "
|
40
|
-
"@rushstack/eslint-patch": "^1.3.
|
41
|
-
"@tsconfig/node20": "^1.
|
40
|
+
"@redocly/cli": "1.0.2",
|
41
|
+
"@rushstack/eslint-patch": "^1.3.3",
|
42
|
+
"@tsconfig/node20": "^20.1.1",
|
42
43
|
"@types/jsdom": "^21.1.1",
|
43
|
-
"@types/node": "^20.4.
|
44
|
-
"@types/prismjs": "^1.26.0",
|
44
|
+
"@types/node": "^20.4.9",
|
45
45
|
"@types/url-parse": "^1.4.8",
|
46
|
-
"@typescript-eslint/eslint-plugin": "^6.
|
47
|
-
"@typescript-eslint/parser": "^6.
|
46
|
+
"@typescript-eslint/eslint-plugin": "^6.3.0",
|
47
|
+
"@typescript-eslint/parser": "^6.3.0",
|
48
48
|
"@vitejs/plugin-vue": "^4.2.3",
|
49
|
-
"@vue/eslint-config-prettier": "^
|
49
|
+
"@vue/eslint-config-prettier": "^8.0.0",
|
50
50
|
"@vue/eslint-config-typescript": "^11.0.3",
|
51
|
-
"@vue/test-utils": "2.4.
|
51
|
+
"@vue/test-utils": "2.4.1",
|
52
52
|
"@vue/tsconfig": "^0.4.0",
|
53
|
-
"eslint": "^8.
|
54
|
-
"eslint-config-prettier": "^
|
53
|
+
"eslint": "^8.46.0",
|
54
|
+
"eslint-config-prettier": "^9.0.0",
|
55
55
|
"eslint-plugin-prettier": "^5.0.0",
|
56
56
|
"eslint-plugin-simple-import-sort": "^10.0.0",
|
57
|
-
"eslint-plugin-vue": "^9.
|
57
|
+
"eslint-plugin-vue": "^9.17.0",
|
58
58
|
"husky": "^8.0.3",
|
59
59
|
"jsdom": "^22.1.0",
|
60
60
|
"npm-run-all": "^4.1.5",
|
61
|
-
"prettier": "^3.0.
|
61
|
+
"prettier": "^3.0.1",
|
62
62
|
"typescript": "~5.1.6",
|
63
|
-
"vite": "^4.4.
|
64
|
-
"vitest": "^0.
|
65
|
-
"vue-tsc": "^1.8.
|
63
|
+
"vite": "^4.4.9",
|
64
|
+
"vitest": "^0.34.1",
|
65
|
+
"vue-tsc": "^1.8.8"
|
66
66
|
}
|
67
67
|
}
|
@@ -0,0 +1,6 @@
|
|
1
|
+
import ace from "ace-builds"
|
2
|
+
import modeYamlUrl from "ace-builds/src-min-noconflict/mode-yaml?url"
|
3
|
+
import themeMonokaiUrl from "ace-builds/src-min-noconflict/theme-monokai?url"
|
4
|
+
|
5
|
+
ace.config.setModuleUrl("ace/mode/yaml", modeYamlUrl)
|
6
|
+
ace.config.setModuleUrl("ace/theme/monokai", themeMonokaiUrl)
|
@@ -95,7 +95,7 @@
|
|
95
95
|
</template>
|
96
96
|
|
97
97
|
<script lang="ts">
|
98
|
-
import { defineComponent, type PropType, ref, watch } from "vue"
|
98
|
+
import { defineComponent, type PropType, ref, toRef,watch } from "vue"
|
99
99
|
import { useRoute } from "vue-router"
|
100
100
|
|
101
101
|
import type { AlertSearchParams } from "@/types"
|
@@ -126,7 +126,7 @@ export default defineComponent({
|
|
126
126
|
|
127
127
|
const artifact = ref<string | undefined>(undefined)
|
128
128
|
const fromAt = ref<string | undefined>(undefined)
|
129
|
-
const tagInput =
|
129
|
+
const tagInput = toRef(props, "tag")
|
130
130
|
const ruleId = ref<string | undefined>(undefined)
|
131
131
|
const toAt = ref<string | undefined>(undefined)
|
132
132
|
const asn = ref<number | undefined>(undefined)
|
@@ -23,7 +23,7 @@
|
|
23
23
|
</template>
|
24
24
|
|
25
25
|
<script lang="ts">
|
26
|
-
import { defineComponent, type PropType,
|
26
|
+
import { defineComponent, type PropType, toRef } from "vue"
|
27
27
|
import { useRouter } from "vue-router"
|
28
28
|
|
29
29
|
import { generateUpdateRuleTask } from "@/api-helper"
|
@@ -46,7 +46,8 @@ export default defineComponent({
|
|
46
46
|
setup(props) {
|
47
47
|
const router = useRouter()
|
48
48
|
|
49
|
-
const
|
49
|
+
const rule = toRef(props, "rule")
|
50
|
+
const yaml = toRef(rule.value, "yaml")
|
50
51
|
|
51
52
|
const updateRuleTask = generateUpdateRuleTask()
|
52
53
|
|
@@ -88,7 +88,7 @@
|
|
88
88
|
</template>
|
89
89
|
|
90
90
|
<script lang="ts">
|
91
|
-
import { defineComponent, type PropType, ref, watch } from "vue"
|
91
|
+
import { defineComponent, type PropType, ref, toRef,watch } from "vue"
|
92
92
|
import { useRoute } from "vue-router"
|
93
93
|
|
94
94
|
import type { RuleSearchParams } from "@/types"
|
@@ -115,7 +115,7 @@ export default defineComponent({
|
|
115
115
|
|
116
116
|
const description = ref<string | undefined>(undefined)
|
117
117
|
const fromAt = ref<string | undefined>(undefined)
|
118
|
-
const tagInput =
|
118
|
+
const tagInput = toRef(props, "tag")
|
119
119
|
const title = ref<string | undefined>(undefined)
|
120
120
|
const toAt = ref<string | undefined>(undefined)
|
121
121
|
|
@@ -1,31 +1,29 @@
|
|
1
1
|
<template>
|
2
|
-
<div class="block
|
3
|
-
<
|
4
|
-
class="
|
5
|
-
v-model="yamlInput"
|
6
|
-
|
7
|
-
|
8
|
-
|
2
|
+
<div class="block">
|
3
|
+
<VAceEditor
|
4
|
+
class="vue-ace-editor"
|
5
|
+
v-model:value="yamlInput"
|
6
|
+
lang="yaml"
|
7
|
+
theme="monokai"
|
8
|
+
:options="{
|
9
|
+
fontSize: 16,
|
10
|
+
minLines: 6,
|
11
|
+
maxLines: 10000
|
12
|
+
}"
|
13
|
+
></VAceEditor>
|
9
14
|
</div>
|
10
15
|
</template>
|
11
16
|
|
12
17
|
<script lang="ts">
|
13
|
-
|
14
|
-
import "vue-prism-editor/dist/prismeditor.min.css"
|
18
|
+
import "@/ace-config"
|
15
19
|
|
16
|
-
import { defineComponent,
|
17
|
-
import {
|
18
|
-
|
19
|
-
import Prism from "prismjs"
|
20
|
-
|
21
|
-
import "prismjs/components/prism-yaml"
|
22
|
-
import "prismjs/plugins/custom-class/prism-custom-class"
|
23
|
-
import "prismjs/themes/prism-twilight.css"
|
20
|
+
import { defineComponent, toRef, watchEffect } from "vue"
|
21
|
+
import { VAceEditor } from "vue3-ace-editor"
|
24
22
|
|
25
23
|
export default defineComponent({
|
26
24
|
name: "RuleInputForm",
|
27
25
|
components: {
|
28
|
-
|
26
|
+
VAceEditor
|
29
27
|
},
|
30
28
|
props: {
|
31
29
|
yaml: {
|
@@ -35,52 +33,13 @@ export default defineComponent({
|
|
35
33
|
},
|
36
34
|
emits: ["update-yaml"],
|
37
35
|
setup(props, context) {
|
38
|
-
const yamlInput =
|
39
|
-
const wrapper = ref<HTMLElement | undefined>(undefined)
|
40
|
-
|
41
|
-
Prism.plugins.customClass.map({
|
42
|
-
number: "prism-number",
|
43
|
-
tag: "prism-tag"
|
44
|
-
})
|
45
|
-
|
46
|
-
const highlighter = (code: string) => {
|
47
|
-
return Prism.highlight(code, Prism.languages.yaml, "yaml")
|
48
|
-
}
|
36
|
+
const yamlInput = toRef(props, "yaml")
|
49
37
|
|
50
38
|
watchEffect(() => {
|
51
39
|
context.emit("update-yaml", yamlInput.value)
|
52
|
-
|
53
|
-
// TODO: a dirty hack to change the default text color
|
54
|
-
if (wrapper.value) {
|
55
|
-
const strings = wrapper.value.querySelectorAll(":not(span.token)")
|
56
|
-
strings.forEach((string) => {
|
57
|
-
;(string as HTMLElement).style.color = "white"
|
58
|
-
})
|
59
|
-
}
|
60
40
|
})
|
61
41
|
|
62
|
-
return { yamlInput
|
42
|
+
return { yamlInput }
|
63
43
|
}
|
64
44
|
})
|
65
45
|
</script>
|
66
|
-
|
67
|
-
<style scoped>
|
68
|
-
.my-editor {
|
69
|
-
background: hsl(0, 0%, 8%);
|
70
|
-
font-family:
|
71
|
-
Fira code,
|
72
|
-
Fira Mono,
|
73
|
-
Consolas,
|
74
|
-
Menlo,
|
75
|
-
Courier,
|
76
|
-
monospace;
|
77
|
-
font-size: 1em;
|
78
|
-
line-height: 1.5;
|
79
|
-
padding: 5px;
|
80
|
-
}
|
81
|
-
|
82
|
-
.my-editor-wrapper {
|
83
|
-
background: hsl(0, 0%, 8%);
|
84
|
-
padding: 10px;
|
85
|
-
}
|
86
|
-
</style>
|
@@ -1,44 +1,37 @@
|
|
1
1
|
<template>
|
2
|
-
<
|
2
|
+
<div class="block">
|
3
|
+
<VAceEditor
|
4
|
+
class="vue-ace-editor"
|
5
|
+
:value="yaml"
|
6
|
+
lang="yaml"
|
7
|
+
theme="monokai"
|
8
|
+
:options="{
|
9
|
+
readOnly: true,
|
10
|
+
fontSize: 16,
|
11
|
+
maxLines: 10000,
|
12
|
+
minLines: 6
|
13
|
+
}"
|
14
|
+
></VAceEditor>
|
15
|
+
</div>
|
3
16
|
</template>
|
4
17
|
|
5
18
|
<script lang="ts">
|
6
|
-
|
7
|
-
import { defineComponent, onMounted, ref } from "vue"
|
19
|
+
import "@/ace-config"
|
8
20
|
|
9
|
-
import
|
10
|
-
|
11
|
-
import "prismjs/components/prism-yaml"
|
12
|
-
import "prismjs/plugins/custom-class/prism-custom-class"
|
13
|
-
import "prismjs/plugins/line-numbers/prism-line-numbers.css"
|
14
|
-
import "prismjs/plugins/line-numbers/prism-line-numbers"
|
15
|
-
import "prismjs/themes/prism-twilight.css"
|
21
|
+
import { defineComponent } from "vue"
|
22
|
+
import { VAceEditor } from "vue3-ace-editor"
|
16
23
|
|
17
24
|
export default defineComponent({
|
18
25
|
name: "YAML",
|
26
|
+
components: {
|
27
|
+
VAceEditor
|
28
|
+
},
|
19
29
|
props: {
|
20
30
|
yaml: {
|
21
31
|
type: String,
|
22
32
|
required: true
|
23
33
|
}
|
24
34
|
},
|
25
|
-
setup() {
|
26
|
-
const pre = ref<HTMLElement | undefined>(undefined)
|
27
|
-
|
28
|
-
Prism.plugins.customClass.map({
|
29
|
-
number: "prism-number",
|
30
|
-
tag: "prism-tag"
|
31
|
-
})
|
32
|
-
|
33
|
-
onMounted(() => {
|
34
|
-
if (pre.value) {
|
35
|
-
pre.value.querySelectorAll("code").forEach((elem) => {
|
36
|
-
Prism.highlightElement(elem)
|
37
|
-
})
|
38
|
-
}
|
39
|
-
})
|
40
|
-
|
41
|
-
return { pre }
|
42
|
-
}
|
35
|
+
setup() {}
|
43
36
|
})
|
44
37
|
</script>
|
@@ -1,10 +1,10 @@
|
|
1
1
|
<template>
|
2
|
-
<Artifact :id="
|
2
|
+
<Artifact :id="id"></Artifact>
|
3
3
|
</template>
|
4
4
|
|
5
5
|
<script lang="ts">
|
6
6
|
import { useTitle } from "@vueuse/core"
|
7
|
-
import { defineComponent, onMounted,
|
7
|
+
import { defineComponent, onMounted, watch } from "vue"
|
8
8
|
|
9
9
|
import Artifact from "@/components/artifact/ArtifactWrapper.vue"
|
10
10
|
|
@@ -20,10 +20,8 @@ export default defineComponent({
|
|
20
20
|
}
|
21
21
|
},
|
22
22
|
setup(props) {
|
23
|
-
const artifactId = ref<string>(props.id)
|
24
|
-
|
25
23
|
const updateTitle = () => {
|
26
|
-
useTitle(`Artifact:${
|
24
|
+
useTitle(`Artifact:${props.id} - Mihari`)
|
27
25
|
}
|
28
26
|
|
29
27
|
onMounted(() => {
|
@@ -33,12 +31,9 @@ export default defineComponent({
|
|
33
31
|
watch(
|
34
32
|
() => props.id,
|
35
33
|
() => {
|
36
|
-
artifactId.value = props.id
|
37
34
|
updateTitle()
|
38
35
|
}
|
39
36
|
)
|
40
|
-
|
41
|
-
return { artifactId }
|
42
37
|
}
|
43
38
|
})
|
44
39
|
</script>
|
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
<script lang="ts">
|
6
6
|
import { useTitle } from "@vueuse/core"
|
7
|
-
import { defineComponent, onMounted,
|
7
|
+
import { defineComponent, onMounted, watch } from "vue"
|
8
8
|
|
9
9
|
import EditRule from "@/components/rule/EditRuleWrapper.vue"
|
10
10
|
|
@@ -20,10 +20,8 @@ export default defineComponent({
|
|
20
20
|
}
|
21
21
|
},
|
22
22
|
setup(props) {
|
23
|
-
const ruleId = ref<string>(props.id)
|
24
|
-
|
25
23
|
const updateTitle = () => {
|
26
|
-
useTitle(`Edit rule:${
|
24
|
+
useTitle(`Edit rule:${props.id} - Mihari`)
|
27
25
|
}
|
28
26
|
|
29
27
|
onMounted(() => {
|
@@ -33,12 +31,9 @@ export default defineComponent({
|
|
33
31
|
watch(
|
34
32
|
() => props.id,
|
35
33
|
() => {
|
36
|
-
ruleId.value = props.id
|
37
34
|
updateTitle()
|
38
35
|
}
|
39
36
|
)
|
40
|
-
|
41
|
-
return { ruleId }
|
42
37
|
}
|
43
38
|
})
|
44
39
|
</script>
|
data/frontend/src/views/Rule.vue
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
<template>
|
2
|
-
<Rule :id="
|
2
|
+
<Rule :id="id"></Rule>
|
3
3
|
</template>
|
4
4
|
|
5
5
|
<script lang="ts">
|
6
6
|
import { useTitle } from "@vueuse/core"
|
7
|
-
import { defineComponent, onMounted,
|
7
|
+
import { defineComponent, onMounted, watch } from "vue"
|
8
8
|
|
9
9
|
import Rule from "@/components/rule/RuleWrapper.vue"
|
10
10
|
|
@@ -20,10 +20,8 @@ export default defineComponent({
|
|
20
20
|
}
|
21
21
|
},
|
22
22
|
setup(props) {
|
23
|
-
const ruleId = ref<string>(props.id)
|
24
|
-
|
25
23
|
const updateTitle = () => {
|
26
|
-
useTitle(`Rule:${
|
24
|
+
useTitle(`Rule:${props.id} - Mihari`)
|
27
25
|
}
|
28
26
|
|
29
27
|
onMounted(() => {
|
@@ -33,12 +31,9 @@ export default defineComponent({
|
|
33
31
|
watch(
|
34
32
|
() => props.id,
|
35
33
|
() => {
|
36
|
-
ruleId.value = props.id
|
37
34
|
updateTitle()
|
38
35
|
}
|
39
36
|
)
|
40
|
-
|
41
|
-
return { ruleId }
|
42
37
|
}
|
43
38
|
})
|
44
39
|
</script>
|
@@ -25,21 +25,28 @@ module Mihari
|
|
25
25
|
# @return [Integer, nil]
|
26
26
|
#
|
27
27
|
def interval
|
28
|
-
@interval
|
28
|
+
@interval ||= options[:interval]
|
29
29
|
end
|
30
30
|
|
31
31
|
#
|
32
32
|
# @return [Integer]
|
33
33
|
#
|
34
34
|
def retry_interval
|
35
|
-
@retry_interval ||= options[:retry_interval] ||
|
35
|
+
@retry_interval ||= options[:retry_interval] || Mihari.config.retry_interval
|
36
36
|
end
|
37
37
|
|
38
38
|
#
|
39
39
|
# @return [Integer]
|
40
40
|
#
|
41
41
|
def retry_times
|
42
|
-
@retry_times ||= options[:retry_times] ||
|
42
|
+
@retry_times ||= options[:retry_times] || Mihari.config.retry_times
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# @return [Integer]
|
47
|
+
#
|
48
|
+
def pagination_limit
|
49
|
+
@pagination_limit ||= options[:pagination_limit] || Mihari.config.pagination_limit
|
43
50
|
end
|
44
51
|
|
45
52
|
# @return [Array<String>, Array<Mihari::Artifact>]
|
@@ -73,6 +80,12 @@ module Mihari
|
|
73
80
|
|
74
81
|
alias_method :source, :class_name
|
75
82
|
|
83
|
+
private
|
84
|
+
|
85
|
+
def sleep_interval
|
86
|
+
sleep(interval) if interval
|
87
|
+
end
|
88
|
+
|
76
89
|
class << self
|
77
90
|
#
|
78
91
|
# Initialize an analyzer by query params
|
@@ -56,7 +56,7 @@ module Mihari
|
|
56
56
|
#
|
57
57
|
def search
|
58
58
|
responses = []
|
59
|
-
(1..
|
59
|
+
(1..pagination_limit).each do |page|
|
60
60
|
res = search_with_page(page: page)
|
61
61
|
total = res["total"].to_i
|
62
62
|
|
@@ -64,7 +64,7 @@ module Mihari
|
|
64
64
|
break if total <= page * PAGE_SIZE
|
65
65
|
|
66
66
|
# sleep #{interval} seconds to avoid the rate limitation (if it is set)
|
67
|
-
|
67
|
+
sleep_interval
|
68
68
|
end
|
69
69
|
responses
|
70
70
|
end
|
@@ -30,7 +30,7 @@ module Mihari
|
|
30
30
|
artifacts = []
|
31
31
|
|
32
32
|
cursor = nil
|
33
|
-
|
33
|
+
pagination_limit.times do
|
34
34
|
response = client.search(query, cursor: cursor)
|
35
35
|
artifacts << response.result.to_artifacts
|
36
36
|
cursor = response.result.links.next
|
@@ -42,7 +42,7 @@ module Mihari
|
|
42
42
|
break if cursor.nil? || cursor.empty?
|
43
43
|
|
44
44
|
# sleep #{interval} seconds to avoid the rate limitation (if it is set)
|
45
|
-
|
45
|
+
sleep_interval
|
46
46
|
end
|
47
47
|
|
48
48
|
artifacts.flatten.uniq(&:data)
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mihari
|
4
|
+
module Analyzers
|
5
|
+
class HunterHow < Base
|
6
|
+
# @return [Integer]
|
7
|
+
PAGE_SIZE = 100
|
8
|
+
|
9
|
+
# @return [String, nil]
|
10
|
+
attr_reader :api_key
|
11
|
+
|
12
|
+
# @return [Date]
|
13
|
+
attr_reader :start_time
|
14
|
+
|
15
|
+
# @return [Date]
|
16
|
+
attr_reader :end_time
|
17
|
+
|
18
|
+
#
|
19
|
+
# @param [String] query
|
20
|
+
# @param [Hash, nil] options
|
21
|
+
# @param [String, nil] api_key
|
22
|
+
#
|
23
|
+
def initialize(query, start_time:, end_time:, options: nil, api_key: nil)
|
24
|
+
super(query, options: options)
|
25
|
+
|
26
|
+
@api_key = api_key || Mihari.config.hunterhow_api_key
|
27
|
+
|
28
|
+
@start_time = start_time
|
29
|
+
@end_time = end_time
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# @return [Array<Mihari::Artifact>]
|
34
|
+
#
|
35
|
+
def artifacts
|
36
|
+
artifacts = []
|
37
|
+
|
38
|
+
(1..pagination_limit).each do |page|
|
39
|
+
res = client.search(
|
40
|
+
query,
|
41
|
+
page: page,
|
42
|
+
page_size: PAGE_SIZE,
|
43
|
+
start_time: start_time.strftime("%Y-%m-%d"),
|
44
|
+
end_time: end_time.strftime("%Y-%m-%d")
|
45
|
+
)
|
46
|
+
|
47
|
+
artifacts << res.data.artifacts
|
48
|
+
|
49
|
+
break if res.data.list.length < PAGE_SIZE
|
50
|
+
|
51
|
+
sleep_interval
|
52
|
+
end
|
53
|
+
|
54
|
+
artifacts.flatten
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def configuration_keys
|
60
|
+
%w[hunterhow_api_key]
|
61
|
+
end
|
62
|
+
|
63
|
+
def client
|
64
|
+
@client ||= Clients::HunterHow.new(api_key: api_key)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -57,7 +57,7 @@ module Mihari
|
|
57
57
|
#
|
58
58
|
def search
|
59
59
|
responses = []
|
60
|
-
(1..
|
60
|
+
(1..pagination_limit).each do |page|
|
61
61
|
res = search_with_page(query, page: page)
|
62
62
|
responses << res
|
63
63
|
|
@@ -65,7 +65,7 @@ module Mihari
|
|
65
65
|
break if total <= page * PAGE_SIZE
|
66
66
|
|
67
67
|
# sleep #{interval} seconds to avoid the rate limitation (if it is set)
|
68
|
-
|
68
|
+
sleep_interval
|
69
69
|
end
|
70
70
|
responses
|
71
71
|
end
|
@@ -10,6 +10,7 @@ module Mihari
|
|
10
10
|
"dnstwister" => DNSTwister,
|
11
11
|
"feed" => Feed,
|
12
12
|
"greynoise" => GreyNoise,
|
13
|
+
"hunterhow" => HunterHow,
|
13
14
|
"onyphe" => Onyphe,
|
14
15
|
"otx" => OTX,
|
15
16
|
"passivetotal" => PassiveTotal,
|
@@ -37,14 +38,14 @@ module Mihari
|
|
37
38
|
class Rule
|
38
39
|
include Mixins::FalsePositive
|
39
40
|
|
40
|
-
# @return [Mihari::
|
41
|
+
# @return [Mihari::Services::Rule]
|
41
42
|
attr_reader :rule
|
42
43
|
|
43
44
|
# @return [Time]
|
44
45
|
attr_reader :base_time
|
45
46
|
|
46
47
|
#
|
47
|
-
# @param [Mihari::
|
48
|
+
# @param [Mihari::Services::Rule] rule
|
48
49
|
#
|
49
50
|
def initialize(rule)
|
50
51
|
@rule = rule
|
@@ -146,11 +147,8 @@ module Mihari
|
|
146
147
|
def falsepositive?(value)
|
147
148
|
return true if rule.falsepositives.include?(value)
|
148
149
|
|
149
|
-
rule.falsepositives.select
|
150
|
-
|
151
|
-
end.any? do |falseposistive|
|
152
|
-
falseposistive.match?(value)
|
153
|
-
end
|
150
|
+
regexps = rule.falsepositives.select { |fp| fp.is_a?(Regexp) }
|
151
|
+
regexps.any? { |fp| fp.match?(value) }
|
154
152
|
end
|
155
153
|
|
156
154
|
#
|
@@ -54,13 +54,13 @@ module Mihari
|
|
54
54
|
#
|
55
55
|
def search
|
56
56
|
responses = []
|
57
|
-
(1..
|
57
|
+
(1..pagination_limit).each do |page|
|
58
58
|
res = search_with_page(page: page)
|
59
59
|
responses << res
|
60
60
|
break if res.total <= page * PAGE_SIZE
|
61
61
|
|
62
62
|
# sleep #{interval} seconds to avoid the rate limitation (if it is set)
|
63
|
-
|
63
|
+
sleep_interval
|
64
64
|
rescue JSON::ParserError
|
65
65
|
# ignore JSON::ParserError
|
66
66
|
# ref. https://github.com/ninoseki/mihari/issues/197
|
@@ -68,7 +68,7 @@ module Mihari
|
|
68
68
|
responses = []
|
69
69
|
|
70
70
|
search_after = nil
|
71
|
-
|
71
|
+
pagination_limit.times do
|
72
72
|
res = search_with_search_after(search_after: search_after)
|
73
73
|
responses << res
|
74
74
|
|
@@ -77,7 +77,7 @@ module Mihari
|
|
77
77
|
search_after = res.results.last.sort.join(",")
|
78
78
|
|
79
79
|
# sleep #{interval} seconds to avoid the rate limitation (if it is set)
|
80
|
-
|
80
|
+
sleep_interval
|
81
81
|
end
|
82
82
|
|
83
83
|
responses
|
@@ -45,7 +45,7 @@ module Mihari
|
|
45
45
|
cursor = nil
|
46
46
|
responses = []
|
47
47
|
|
48
|
-
|
48
|
+
pagination_limit.times do
|
49
49
|
response = Structs::VirusTotalIntelligence::Response.from_dynamic!(client.intel_search(query,
|
50
50
|
cursor: cursor))
|
51
51
|
responses << response
|
@@ -53,7 +53,7 @@ module Mihari
|
|
53
53
|
|
54
54
|
cursor = response.meta.cursor
|
55
55
|
# sleep #{interval} seconds to avoid the rate limitation (if it is set)
|
56
|
-
|
56
|
+
sleep_interval
|
57
57
|
end
|
58
58
|
|
59
59
|
responses
|