parse_ioc 0.4.0__tar.gz → 0.6.0__tar.gz
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.
- parse_ioc-0.6.0/PKG-INFO +244 -0
- parse_ioc-0.6.0/README.md +234 -0
- parse_ioc-0.6.0/parse_ioc.egg-info/PKG-INFO +244 -0
- {parse_ioc-0.4.0 → parse_ioc-0.6.0}/parse_ioc.py +37 -16
- {parse_ioc-0.4.0 → parse_ioc-0.6.0}/pyproject.toml +1 -1
- parse_ioc-0.4.0/PKG-INFO +0 -480
- parse_ioc-0.4.0/README.md +0 -470
- parse_ioc-0.4.0/parse_ioc.egg-info/PKG-INFO +0 -480
- {parse_ioc-0.4.0 → parse_ioc-0.6.0}/LICENSE +0 -0
- {parse_ioc-0.4.0 → parse_ioc-0.6.0}/parse_ioc.egg-info/SOURCES.txt +0 -0
- {parse_ioc-0.4.0 → parse_ioc-0.6.0}/parse_ioc.egg-info/dependency_links.txt +0 -0
- {parse_ioc-0.4.0 → parse_ioc-0.6.0}/parse_ioc.egg-info/top_level.txt +0 -0
- {parse_ioc-0.4.0 → parse_ioc-0.6.0}/setup.cfg +0 -0
parse_ioc-0.6.0/PKG-INFO
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: parse_ioc
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: Parse a list of indicators into a dictionary or JSON structure for programmatic use.
|
|
5
|
+
Project-URL: Homepage, https://github.com/bonifield/parse_ioc
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Dynamic: license-file
|
|
10
|
+
|
|
11
|
+
# ParseIOC
|
|
12
|
+
|
|
13
|
+
Parse a list of indicators into a dictionary, JSON structure, or Sqlite database for programmatic use.
|
|
14
|
+
|
|
15
|
+
Handles:
|
|
16
|
+
- emails and their domains
|
|
17
|
+
- URL domains, and username+password and ports if present
|
|
18
|
+
- ipv4 and ipv6 addresses and networks
|
|
19
|
+
- md5, sha1, sha256, and sha512 hashes
|
|
20
|
+
|
|
21
|
+
...with the goal of **mapping this dictionary to fields in a SIEM** or similar database.
|
|
22
|
+
|
|
23
|
+
This enables programatic use of an IOC file to quickly query specific values in a SIEM, such as `source.ip` or `file.hash.sha256`.
|
|
24
|
+
|
|
25
|
+
This tool will support more formats (imphash, ssdeep, ja3+) "eventually".
|
|
26
|
+
|
|
27
|
+
**Otherwise-unidentifiable strings are categorized as "file".**
|
|
28
|
+
|
|
29
|
+
IOCs are categorized as one of these types:
|
|
30
|
+
- `domain`
|
|
31
|
+
- `email`
|
|
32
|
+
- `file_path_linux`
|
|
33
|
+
- `file_path_windows`
|
|
34
|
+
- `ipv4`
|
|
35
|
+
- `ipv4_network`
|
|
36
|
+
- `ipv6`
|
|
37
|
+
- `ipv6_network`
|
|
38
|
+
- `md5`
|
|
39
|
+
- `sha1`
|
|
40
|
+
- `sha256`
|
|
41
|
+
- `sha512`
|
|
42
|
+
- extra: `credentials`
|
|
43
|
+
- extra: `port`
|
|
44
|
+
|
|
45
|
+
## Setup
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
uv add parse_ioc
|
|
49
|
+
# or
|
|
50
|
+
python3 -m pip install parse_ioc
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Usage
|
|
54
|
+
|
|
55
|
+
### Imports
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
from parse_ioc import ParseIOC, parse_multi, map_fields, to_sqlite
|
|
59
|
+
```
|
|
60
|
+
- `ParseIOC`: class that parses each indicator
|
|
61
|
+
- `parse_multi`: function that wraps `ParseIOC` to create one large dictionary, or a list of dictionaries, containing the parsed indicators
|
|
62
|
+
- `map_fields`: function that maps parsed indicators by type to fields used in a SIEM, specified a TOML config
|
|
63
|
+
- `to_sqlite`: writes parsed indicators to a simple Sqlite3 database
|
|
64
|
+
|
|
65
|
+
### Categorize a single indicator using `ParseIOC()`
|
|
66
|
+
```
|
|
67
|
+
>>> i = ParseIOC("https://192.168.20.20/bad")
|
|
68
|
+
>>> i.to_dict
|
|
69
|
+
{'ioc': '192.168.20.20', 'ioc_type': 'ipv4'}
|
|
70
|
+
>>> i.to_json
|
|
71
|
+
'{"ioc": "192.168.20.20", "ioc_type": "ipv4"}'
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
"extra" details are added when appropriate, such as credentials in a URL, ports, or domains in email addresses
|
|
75
|
+
```
|
|
76
|
+
>>> e = ParseIOC("alice@email.local")
|
|
77
|
+
>>> print(json.dumps(e.to_dict, indent=4))
|
|
78
|
+
{
|
|
79
|
+
"ioc": "alice@email.local",
|
|
80
|
+
"ioc_type": "email",
|
|
81
|
+
"extra": [
|
|
82
|
+
{
|
|
83
|
+
"ioc": "email.local",
|
|
84
|
+
"ioc_type": "domain"
|
|
85
|
+
...
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Categorize a **list** or **file** of indicators using `parse_multi()`
|
|
89
|
+
|
|
90
|
+
using `mode="combined"` produces one massive dictionary of categorized indicators
|
|
91
|
+
```
|
|
92
|
+
>>> p = parse_multi(ioc_list, mode="combined")
|
|
93
|
+
# or
|
|
94
|
+
>>> p = parse_multi("ioc_examples.txt", mode="combined")
|
|
95
|
+
>>> import json
|
|
96
|
+
>>> print(json.dumps(p, indent=4))
|
|
97
|
+
{
|
|
98
|
+
"email": [
|
|
99
|
+
"xn--bob--o4d3g5c@xn--c1a3a5a.xn--p1ai",
|
|
100
|
+
"alice@email.local",
|
|
101
|
+
...
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
using `mode="single"` produces a **list of dictionaries**, similar to calling `ParseIOC(ioc)` by itself in a loop and putting the results in a list
|
|
105
|
+
```
|
|
106
|
+
>>> p = parse_multi("tests/ioc_examples.txt", mode="single")
|
|
107
|
+
>>> print(json.dumps(p, indent=4))
|
|
108
|
+
[
|
|
109
|
+
{
|
|
110
|
+
"ioc": "website.local",
|
|
111
|
+
"ioc_type": "domain"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"ioc": "anotherwebsite.local",
|
|
115
|
+
"ioc_type": "domain",
|
|
116
|
+
"extra": [
|
|
117
|
+
{
|
|
118
|
+
"ioc": 9443,
|
|
119
|
+
"ioc_type": "port"
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"ioc": "bob@email.local",
|
|
125
|
+
"ioc_type": "email",
|
|
126
|
+
"extra": [
|
|
127
|
+
{
|
|
128
|
+
"ioc": "email.local",
|
|
129
|
+
"ioc_type": "domain"
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Map a **list** or **file** of indicators to SIEM fields using `map_fields()`
|
|
134
|
+
|
|
135
|
+
- use the `map_ecs.toml` file as a reference or template
|
|
136
|
+
|
|
137
|
+
example shows parsing a file into [Elastic Common Schema (ECS)](https://www.elastic.co/docs/reference/ecs/ecs-field-reference) fields
|
|
138
|
+
```
|
|
139
|
+
>>> m = map_fields("tests/ioc_examples.txt", "map_ecs.toml")
|
|
140
|
+
>>> print(json.dumps(m, indent=4))
|
|
141
|
+
{
|
|
142
|
+
"email.from.address": [
|
|
143
|
+
"xn--bob--o4d3g5c@xn--c1a3a5a.xn--p1ai",
|
|
144
|
+
"alice@email.local",
|
|
145
|
+
"bob@emailsubdomain.bad.local.xn--p1ai",
|
|
146
|
+
"bob@email.local",
|
|
147
|
+
...
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
specify `keep_types=True` in `map_fields()` to nest the SIEM values under the original type categorizations
|
|
151
|
+
```
|
|
152
|
+
>>> m = map_fields("tests/ioc_examples.txt", "map_ecs.toml", keep_types=True)
|
|
153
|
+
>>> print(json.dumps(m, indent=4))
|
|
154
|
+
{
|
|
155
|
+
"email": {
|
|
156
|
+
"email.from.address": [
|
|
157
|
+
"xn--bob--o4d3g5c@xn--c1a3a5a.xn--p1ai",
|
|
158
|
+
"alice@email.local",
|
|
159
|
+
...
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Create Sqlite database of parsed indicators using `to_sqlite()`
|
|
163
|
+
- table name: `indicators`
|
|
164
|
+
- columns: `ioc` (`TEXT`, primary key) and `type` (`TEXT`)
|
|
165
|
+
- uses `parse_multi(input_object, mode="combined")`
|
|
166
|
+
- `extra` data has its own row, not linked to its parent data
|
|
167
|
+
- ex. a port won't have any reference to the original URL it came from
|
|
168
|
+
```
|
|
169
|
+
>>> to_sqlite("tests/ioc_examples.txt", "iocs.db")
|
|
170
|
+
exported 41 indicators to iocs.db
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
example output using `sqlite3`
|
|
174
|
+
```
|
|
175
|
+
$ sqlite3 iocs.db "SELECT ioc,type FROM indicators;"
|
|
176
|
+
xn--bob--o4d3g5c@xn--c1a3a5a.xn--p1ai|email
|
|
177
|
+
alice@email.local|email
|
|
178
|
+
bob@emailsubdomain.bad.local.xn--p1ai|email
|
|
179
|
+
bob@email.local|email
|
|
180
|
+
192.168.1.1|ipv4
|
|
181
|
+
...
|
|
182
|
+
9443|port
|
|
183
|
+
email.local|domain
|
|
184
|
+
...
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Gotchas
|
|
188
|
+
- Domain names must have a period (`.`) otherwise the string will be considered a file
|
|
189
|
+
- This regex in the domain checker, determines if the parsed domain name drops further down to the `file` checker or not:
|
|
190
|
+
```
|
|
191
|
+
file_extensions = re.compile(r"\.(?:exe|dll|msi|bat|cmd|elf|scr|cpl|ps1|vbs|pdf|docx|xlsx|pptx|doc|xls|ppt|rtf|csv|txt|log|xml|zip|rar|7z|tar|gz|iso|img|dmg|cab|png|jpg|jpeg|gif|ico|bmp|svg|mp3|mp4|wav|avi|mov|js|php|css|html|htm|sql|conf|ini|yaml|yml|json)$", re.IGNORECASE)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Expected output from running `tests/parse_ioc.py` using example data
|
|
195
|
+
```
|
|
196
|
+
$ uv run tests/parseonly.py -f tests/ioc_examples.txt -h
|
|
197
|
+
usage: parseonly.py [-h] -f FILENAME [-m] [-j] [-k]
|
|
198
|
+
|
|
199
|
+
parse indicators
|
|
200
|
+
|
|
201
|
+
options:
|
|
202
|
+
-h, --help show this help message and exit
|
|
203
|
+
-f, --file FILENAME input filename
|
|
204
|
+
-m, --map show mapped format
|
|
205
|
+
-j, --json display indented JSON
|
|
206
|
+
-k, --keep-types keep field types in output dict
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
$ uv run tests/parseonly.py -f tests/ioc_examples.txt -mjk
|
|
211
|
+
{
|
|
212
|
+
"email": {
|
|
213
|
+
"email.from.address": [
|
|
214
|
+
"xn--bob--o4d3g5c@xn--c1a3a5a.xn--p1ai",
|
|
215
|
+
"bob@emailsubdomain.bad.local",
|
|
216
|
+
"alice@email.local",
|
|
217
|
+
"bob@emailsubdomain.bad.local.xn--p1ai",
|
|
218
|
+
"bob@email.local"
|
|
219
|
+
],
|
|
220
|
+
"email.sender.address": [
|
|
221
|
+
"xn--bob--o4d3g5c@xn--c1a3a5a.xn--p1ai",
|
|
222
|
+
"bob@emailsubdomain.bad.local",
|
|
223
|
+
...
|
|
224
|
+
"ipv4": {
|
|
225
|
+
"source.ip": [
|
|
226
|
+
"192.168.20.20",
|
|
227
|
+
"8.8.8.8",
|
|
228
|
+
"1.2.3.4",
|
|
229
|
+
"192.168.1.1"
|
|
230
|
+
],
|
|
231
|
+
...
|
|
232
|
+
"ipv4_network": {
|
|
233
|
+
"source.ip": [
|
|
234
|
+
"192.168.1.0/24",
|
|
235
|
+
"8.8.8.0/24"
|
|
236
|
+
...
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## TODO
|
|
240
|
+
|
|
241
|
+
- add optional type conversions to match TheHive5 default observable types
|
|
242
|
+
- change the list of categorizations in README to a markdown table, showing the converted types
|
|
243
|
+
- need to streamline and optimize code
|
|
244
|
+
- remove the filepath regex statements created by AI
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# ParseIOC
|
|
2
|
+
|
|
3
|
+
Parse a list of indicators into a dictionary, JSON structure, or Sqlite database for programmatic use.
|
|
4
|
+
|
|
5
|
+
Handles:
|
|
6
|
+
- emails and their domains
|
|
7
|
+
- URL domains, and username+password and ports if present
|
|
8
|
+
- ipv4 and ipv6 addresses and networks
|
|
9
|
+
- md5, sha1, sha256, and sha512 hashes
|
|
10
|
+
|
|
11
|
+
...with the goal of **mapping this dictionary to fields in a SIEM** or similar database.
|
|
12
|
+
|
|
13
|
+
This enables programatic use of an IOC file to quickly query specific values in a SIEM, such as `source.ip` or `file.hash.sha256`.
|
|
14
|
+
|
|
15
|
+
This tool will support more formats (imphash, ssdeep, ja3+) "eventually".
|
|
16
|
+
|
|
17
|
+
**Otherwise-unidentifiable strings are categorized as "file".**
|
|
18
|
+
|
|
19
|
+
IOCs are categorized as one of these types:
|
|
20
|
+
- `domain`
|
|
21
|
+
- `email`
|
|
22
|
+
- `file_path_linux`
|
|
23
|
+
- `file_path_windows`
|
|
24
|
+
- `ipv4`
|
|
25
|
+
- `ipv4_network`
|
|
26
|
+
- `ipv6`
|
|
27
|
+
- `ipv6_network`
|
|
28
|
+
- `md5`
|
|
29
|
+
- `sha1`
|
|
30
|
+
- `sha256`
|
|
31
|
+
- `sha512`
|
|
32
|
+
- extra: `credentials`
|
|
33
|
+
- extra: `port`
|
|
34
|
+
|
|
35
|
+
## Setup
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
uv add parse_ioc
|
|
39
|
+
# or
|
|
40
|
+
python3 -m pip install parse_ioc
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
### Imports
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
from parse_ioc import ParseIOC, parse_multi, map_fields, to_sqlite
|
|
49
|
+
```
|
|
50
|
+
- `ParseIOC`: class that parses each indicator
|
|
51
|
+
- `parse_multi`: function that wraps `ParseIOC` to create one large dictionary, or a list of dictionaries, containing the parsed indicators
|
|
52
|
+
- `map_fields`: function that maps parsed indicators by type to fields used in a SIEM, specified a TOML config
|
|
53
|
+
- `to_sqlite`: writes parsed indicators to a simple Sqlite3 database
|
|
54
|
+
|
|
55
|
+
### Categorize a single indicator using `ParseIOC()`
|
|
56
|
+
```
|
|
57
|
+
>>> i = ParseIOC("https://192.168.20.20/bad")
|
|
58
|
+
>>> i.to_dict
|
|
59
|
+
{'ioc': '192.168.20.20', 'ioc_type': 'ipv4'}
|
|
60
|
+
>>> i.to_json
|
|
61
|
+
'{"ioc": "192.168.20.20", "ioc_type": "ipv4"}'
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
"extra" details are added when appropriate, such as credentials in a URL, ports, or domains in email addresses
|
|
65
|
+
```
|
|
66
|
+
>>> e = ParseIOC("alice@email.local")
|
|
67
|
+
>>> print(json.dumps(e.to_dict, indent=4))
|
|
68
|
+
{
|
|
69
|
+
"ioc": "alice@email.local",
|
|
70
|
+
"ioc_type": "email",
|
|
71
|
+
"extra": [
|
|
72
|
+
{
|
|
73
|
+
"ioc": "email.local",
|
|
74
|
+
"ioc_type": "domain"
|
|
75
|
+
...
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Categorize a **list** or **file** of indicators using `parse_multi()`
|
|
79
|
+
|
|
80
|
+
using `mode="combined"` produces one massive dictionary of categorized indicators
|
|
81
|
+
```
|
|
82
|
+
>>> p = parse_multi(ioc_list, mode="combined")
|
|
83
|
+
# or
|
|
84
|
+
>>> p = parse_multi("ioc_examples.txt", mode="combined")
|
|
85
|
+
>>> import json
|
|
86
|
+
>>> print(json.dumps(p, indent=4))
|
|
87
|
+
{
|
|
88
|
+
"email": [
|
|
89
|
+
"xn--bob--o4d3g5c@xn--c1a3a5a.xn--p1ai",
|
|
90
|
+
"alice@email.local",
|
|
91
|
+
...
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
using `mode="single"` produces a **list of dictionaries**, similar to calling `ParseIOC(ioc)` by itself in a loop and putting the results in a list
|
|
95
|
+
```
|
|
96
|
+
>>> p = parse_multi("tests/ioc_examples.txt", mode="single")
|
|
97
|
+
>>> print(json.dumps(p, indent=4))
|
|
98
|
+
[
|
|
99
|
+
{
|
|
100
|
+
"ioc": "website.local",
|
|
101
|
+
"ioc_type": "domain"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"ioc": "anotherwebsite.local",
|
|
105
|
+
"ioc_type": "domain",
|
|
106
|
+
"extra": [
|
|
107
|
+
{
|
|
108
|
+
"ioc": 9443,
|
|
109
|
+
"ioc_type": "port"
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"ioc": "bob@email.local",
|
|
115
|
+
"ioc_type": "email",
|
|
116
|
+
"extra": [
|
|
117
|
+
{
|
|
118
|
+
"ioc": "email.local",
|
|
119
|
+
"ioc_type": "domain"
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Map a **list** or **file** of indicators to SIEM fields using `map_fields()`
|
|
124
|
+
|
|
125
|
+
- use the `map_ecs.toml` file as a reference or template
|
|
126
|
+
|
|
127
|
+
example shows parsing a file into [Elastic Common Schema (ECS)](https://www.elastic.co/docs/reference/ecs/ecs-field-reference) fields
|
|
128
|
+
```
|
|
129
|
+
>>> m = map_fields("tests/ioc_examples.txt", "map_ecs.toml")
|
|
130
|
+
>>> print(json.dumps(m, indent=4))
|
|
131
|
+
{
|
|
132
|
+
"email.from.address": [
|
|
133
|
+
"xn--bob--o4d3g5c@xn--c1a3a5a.xn--p1ai",
|
|
134
|
+
"alice@email.local",
|
|
135
|
+
"bob@emailsubdomain.bad.local.xn--p1ai",
|
|
136
|
+
"bob@email.local",
|
|
137
|
+
...
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
specify `keep_types=True` in `map_fields()` to nest the SIEM values under the original type categorizations
|
|
141
|
+
```
|
|
142
|
+
>>> m = map_fields("tests/ioc_examples.txt", "map_ecs.toml", keep_types=True)
|
|
143
|
+
>>> print(json.dumps(m, indent=4))
|
|
144
|
+
{
|
|
145
|
+
"email": {
|
|
146
|
+
"email.from.address": [
|
|
147
|
+
"xn--bob--o4d3g5c@xn--c1a3a5a.xn--p1ai",
|
|
148
|
+
"alice@email.local",
|
|
149
|
+
...
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Create Sqlite database of parsed indicators using `to_sqlite()`
|
|
153
|
+
- table name: `indicators`
|
|
154
|
+
- columns: `ioc` (`TEXT`, primary key) and `type` (`TEXT`)
|
|
155
|
+
- uses `parse_multi(input_object, mode="combined")`
|
|
156
|
+
- `extra` data has its own row, not linked to its parent data
|
|
157
|
+
- ex. a port won't have any reference to the original URL it came from
|
|
158
|
+
```
|
|
159
|
+
>>> to_sqlite("tests/ioc_examples.txt", "iocs.db")
|
|
160
|
+
exported 41 indicators to iocs.db
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
example output using `sqlite3`
|
|
164
|
+
```
|
|
165
|
+
$ sqlite3 iocs.db "SELECT ioc,type FROM indicators;"
|
|
166
|
+
xn--bob--o4d3g5c@xn--c1a3a5a.xn--p1ai|email
|
|
167
|
+
alice@email.local|email
|
|
168
|
+
bob@emailsubdomain.bad.local.xn--p1ai|email
|
|
169
|
+
bob@email.local|email
|
|
170
|
+
192.168.1.1|ipv4
|
|
171
|
+
...
|
|
172
|
+
9443|port
|
|
173
|
+
email.local|domain
|
|
174
|
+
...
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Gotchas
|
|
178
|
+
- Domain names must have a period (`.`) otherwise the string will be considered a file
|
|
179
|
+
- This regex in the domain checker, determines if the parsed domain name drops further down to the `file` checker or not:
|
|
180
|
+
```
|
|
181
|
+
file_extensions = re.compile(r"\.(?:exe|dll|msi|bat|cmd|elf|scr|cpl|ps1|vbs|pdf|docx|xlsx|pptx|doc|xls|ppt|rtf|csv|txt|log|xml|zip|rar|7z|tar|gz|iso|img|dmg|cab|png|jpg|jpeg|gif|ico|bmp|svg|mp3|mp4|wav|avi|mov|js|php|css|html|htm|sql|conf|ini|yaml|yml|json)$", re.IGNORECASE)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Expected output from running `tests/parse_ioc.py` using example data
|
|
185
|
+
```
|
|
186
|
+
$ uv run tests/parseonly.py -f tests/ioc_examples.txt -h
|
|
187
|
+
usage: parseonly.py [-h] -f FILENAME [-m] [-j] [-k]
|
|
188
|
+
|
|
189
|
+
parse indicators
|
|
190
|
+
|
|
191
|
+
options:
|
|
192
|
+
-h, --help show this help message and exit
|
|
193
|
+
-f, --file FILENAME input filename
|
|
194
|
+
-m, --map show mapped format
|
|
195
|
+
-j, --json display indented JSON
|
|
196
|
+
-k, --keep-types keep field types in output dict
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
$ uv run tests/parseonly.py -f tests/ioc_examples.txt -mjk
|
|
201
|
+
{
|
|
202
|
+
"email": {
|
|
203
|
+
"email.from.address": [
|
|
204
|
+
"xn--bob--o4d3g5c@xn--c1a3a5a.xn--p1ai",
|
|
205
|
+
"bob@emailsubdomain.bad.local",
|
|
206
|
+
"alice@email.local",
|
|
207
|
+
"bob@emailsubdomain.bad.local.xn--p1ai",
|
|
208
|
+
"bob@email.local"
|
|
209
|
+
],
|
|
210
|
+
"email.sender.address": [
|
|
211
|
+
"xn--bob--o4d3g5c@xn--c1a3a5a.xn--p1ai",
|
|
212
|
+
"bob@emailsubdomain.bad.local",
|
|
213
|
+
...
|
|
214
|
+
"ipv4": {
|
|
215
|
+
"source.ip": [
|
|
216
|
+
"192.168.20.20",
|
|
217
|
+
"8.8.8.8",
|
|
218
|
+
"1.2.3.4",
|
|
219
|
+
"192.168.1.1"
|
|
220
|
+
],
|
|
221
|
+
...
|
|
222
|
+
"ipv4_network": {
|
|
223
|
+
"source.ip": [
|
|
224
|
+
"192.168.1.0/24",
|
|
225
|
+
"8.8.8.0/24"
|
|
226
|
+
...
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## TODO
|
|
230
|
+
|
|
231
|
+
- add optional type conversions to match TheHive5 default observable types
|
|
232
|
+
- change the list of categorizations in README to a markdown table, showing the converted types
|
|
233
|
+
- need to streamline and optimize code
|
|
234
|
+
- remove the filepath regex statements created by AI
|