parse_ioc 0.6.0__tar.gz → 0.7.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.7.0/PKG-INFO +187 -0
- parse_ioc-0.7.0/README.md +177 -0
- parse_ioc-0.7.0/parse_ioc.egg-info/PKG-INFO +187 -0
- {parse_ioc-0.6.0 → parse_ioc-0.7.0}/parse_ioc.egg-info/SOURCES.txt +2 -1
- {parse_ioc-0.6.0 → parse_ioc-0.7.0}/parse_ioc.py +201 -211
- {parse_ioc-0.6.0 → parse_ioc-0.7.0}/pyproject.toml +6 -1
- parse_ioc-0.7.0/tests/test_parse_ioc.py +157 -0
- parse_ioc-0.6.0/PKG-INFO +0 -244
- parse_ioc-0.6.0/README.md +0 -234
- parse_ioc-0.6.0/parse_ioc.egg-info/PKG-INFO +0 -244
- {parse_ioc-0.6.0 → parse_ioc-0.7.0}/LICENSE +0 -0
- {parse_ioc-0.6.0 → parse_ioc-0.7.0}/parse_ioc.egg-info/dependency_links.txt +0 -0
- {parse_ioc-0.6.0 → parse_ioc-0.7.0}/parse_ioc.egg-info/top_level.txt +0 -0
- {parse_ioc-0.6.0 → parse_ioc-0.7.0}/setup.cfg +0 -0
parse_ioc-0.7.0/PKG-INFO
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: parse_ioc
|
|
3
|
+
Version: 0.7.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
|
+
|
|
46
|
+
## Setup
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
uv add parse_ioc
|
|
50
|
+
# or
|
|
51
|
+
python3 -m pip install parse_ioc
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
### Import
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
from parse_ioc import ParseIOC
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Categorize a single indicator using `ParseIOC.parse_one()`
|
|
64
|
+
|
|
65
|
+
available attributes:
|
|
66
|
+
- `ioc_type`: the categorization (string)
|
|
67
|
+
- `to_dict`: the categorized IOC as a dictionary (contains `ioc` and `ioc_type`)
|
|
68
|
+
- `to_json`: the categorized IOC as a JSON string (contains `ioc` and `ioc_type`)
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
>>> from parse_ioc import ParseIOC
|
|
72
|
+
>>> p = ParseIOC.parse_one("https://192.168.20.20/")
|
|
73
|
+
>>> p.ioc_type
|
|
74
|
+
'ipv4'
|
|
75
|
+
>>> p.to_dict
|
|
76
|
+
{'ioc': '192.168.20.20', 'ioc_type': 'ipv4'}
|
|
77
|
+
>>> p.to_json
|
|
78
|
+
'{"ioc": "192.168.20.20", "ioc_type": "ipv4"}'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
"extra" details are added when appropriate, such as credentials in a URL, ports, or domains in email addresses
|
|
82
|
+
```
|
|
83
|
+
>>> e = ParseIOC.parse_one("alice@email.local").to_dict
|
|
84
|
+
>>> print(json.dumps(e, indent=4))
|
|
85
|
+
{
|
|
86
|
+
"ioc": "alice@email.local",
|
|
87
|
+
"ioc_type": "email",
|
|
88
|
+
"extra": [
|
|
89
|
+
{
|
|
90
|
+
"ioc": "email.local",
|
|
91
|
+
"ioc_type": "domain"
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
### Categorize a **list** or **file** of indicators using `ParseIOC()` methods
|
|
99
|
+
|
|
100
|
+
Instantiate the class with your file path once using `ParseIOC(<path>)`, then use class methods.
|
|
101
|
+
|
|
102
|
+
Using `mode="combined"` produces one dictionary of categorized indicators
|
|
103
|
+
```
|
|
104
|
+
>>> parser = ParseIOC("tests/ioc_examples.txt")
|
|
105
|
+
>>> p = parser.parse_many(mode="combined")
|
|
106
|
+
>>> print(json.dumps(p, indent=4))
|
|
107
|
+
{
|
|
108
|
+
"email": [
|
|
109
|
+
"bob@email.local",
|
|
110
|
+
"bob@emailsubdomain.bad.local.xn--p1ai",
|
|
111
|
+
"xn--bob--o4d3g5c@xn--c1a3a5a.xn--p1ai",
|
|
112
|
+
...
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Using `mode="single"` produces a list of dictionaries in the `parse_one()` style
|
|
116
|
+
- **this is the required input type for `map_fields()` below**
|
|
117
|
+
```
|
|
118
|
+
>>> parser = ParseIOC("tests/ioc_examples.txt")
|
|
119
|
+
>>> p = parser.parse_many(mode="single")
|
|
120
|
+
>>> print(json.dumps(p, indent=4))
|
|
121
|
+
[
|
|
122
|
+
{
|
|
123
|
+
"ioc": "bob@email.local",
|
|
124
|
+
"ioc_type": "email"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"ioc": "email.local",
|
|
128
|
+
"ioc_type": "domain"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"ioc": 9443,
|
|
132
|
+
"ioc_type": "port"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"ioc": "username:password",
|
|
136
|
+
"ioc_type": "credentials"
|
|
137
|
+
},
|
|
138
|
+
...
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
### Map a **list** or **file** of indicators to SIEM fields using `map_fields()`
|
|
143
|
+
|
|
144
|
+
These examples use `map_ecs.toml` as a reference for parsing a file into [Elastic Common Schema (ECS)](https://www.elastic.co/docs/reference/ecs/ecs-field-reference) fields
|
|
145
|
+
```
|
|
146
|
+
>>> parser = ParseIOC("tests/ioc_examples.txt")
|
|
147
|
+
>>> list_of_dicts = parser.parse_many(mode="single")
|
|
148
|
+
>>> mapped_fields = parser.map_fields(list_of_dicts, map_file="map_ecs.toml")
|
|
149
|
+
>>> print(json.dumps(mapped_fields, indent=4))
|
|
150
|
+
{
|
|
151
|
+
"destination.domain": [
|
|
152
|
+
"email.local",
|
|
153
|
+
"emailsubdomain.bad.local",
|
|
154
|
+
"emailsubdomain.bad.local.xn--p1ai",
|
|
155
|
+
...
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Specify `keep_types=True` in `map_fields()` to nest the SIEM values under the original type categorizations
|
|
159
|
+
```
|
|
160
|
+
>>> parser = ParseIOC("tests/ioc_examples.txt")
|
|
161
|
+
>>> list_of_dicts = parser.parse_many(mode="single")
|
|
162
|
+
>>> mapped_fields = parser.map_fields(list_of_dicts, map_file="map_ecs.toml", keep_types=True)
|
|
163
|
+
>>> print(json.dumps(mapped_fields, indent=4))
|
|
164
|
+
{
|
|
165
|
+
"domain": {
|
|
166
|
+
"destination.domain": [
|
|
167
|
+
"email.local",
|
|
168
|
+
"emailsubdomain.bad.local",
|
|
169
|
+
"emailsubdomain.bad.local.xn--p1ai",
|
|
170
|
+
...
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
## Gotchas
|
|
175
|
+
- Domain names must have a period (`.`) otherwise the string will be considered a file.
|
|
176
|
+
- This regex in the domain checker, determines if the parsed domain name drops further down to the `file` checker or not:
|
|
177
|
+
```
|
|
178
|
+
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)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
## TODO
|
|
183
|
+
|
|
184
|
+
- add optional type conversions to match TheHive5 default observable types
|
|
185
|
+
- change the list of categorizations in README to a markdown table, showing the converted types
|
|
186
|
+
- need to streamline and optimize code
|
|
187
|
+
- remove the filepath regex statements created by AI
|
|
@@ -0,0 +1,177 @@
|
|
|
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
|
+
|
|
36
|
+
## Setup
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
uv add parse_ioc
|
|
40
|
+
# or
|
|
41
|
+
python3 -m pip install parse_ioc
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
### Import
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
from parse_ioc import ParseIOC
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Categorize a single indicator using `ParseIOC.parse_one()`
|
|
54
|
+
|
|
55
|
+
available attributes:
|
|
56
|
+
- `ioc_type`: the categorization (string)
|
|
57
|
+
- `to_dict`: the categorized IOC as a dictionary (contains `ioc` and `ioc_type`)
|
|
58
|
+
- `to_json`: the categorized IOC as a JSON string (contains `ioc` and `ioc_type`)
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
>>> from parse_ioc import ParseIOC
|
|
62
|
+
>>> p = ParseIOC.parse_one("https://192.168.20.20/")
|
|
63
|
+
>>> p.ioc_type
|
|
64
|
+
'ipv4'
|
|
65
|
+
>>> p.to_dict
|
|
66
|
+
{'ioc': '192.168.20.20', 'ioc_type': 'ipv4'}
|
|
67
|
+
>>> p.to_json
|
|
68
|
+
'{"ioc": "192.168.20.20", "ioc_type": "ipv4"}'
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
"extra" details are added when appropriate, such as credentials in a URL, ports, or domains in email addresses
|
|
72
|
+
```
|
|
73
|
+
>>> e = ParseIOC.parse_one("alice@email.local").to_dict
|
|
74
|
+
>>> print(json.dumps(e, indent=4))
|
|
75
|
+
{
|
|
76
|
+
"ioc": "alice@email.local",
|
|
77
|
+
"ioc_type": "email",
|
|
78
|
+
"extra": [
|
|
79
|
+
{
|
|
80
|
+
"ioc": "email.local",
|
|
81
|
+
"ioc_type": "domain"
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
### Categorize a **list** or **file** of indicators using `ParseIOC()` methods
|
|
89
|
+
|
|
90
|
+
Instantiate the class with your file path once using `ParseIOC(<path>)`, then use class methods.
|
|
91
|
+
|
|
92
|
+
Using `mode="combined"` produces one dictionary of categorized indicators
|
|
93
|
+
```
|
|
94
|
+
>>> parser = ParseIOC("tests/ioc_examples.txt")
|
|
95
|
+
>>> p = parser.parse_many(mode="combined")
|
|
96
|
+
>>> print(json.dumps(p, indent=4))
|
|
97
|
+
{
|
|
98
|
+
"email": [
|
|
99
|
+
"bob@email.local",
|
|
100
|
+
"bob@emailsubdomain.bad.local.xn--p1ai",
|
|
101
|
+
"xn--bob--o4d3g5c@xn--c1a3a5a.xn--p1ai",
|
|
102
|
+
...
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Using `mode="single"` produces a list of dictionaries in the `parse_one()` style
|
|
106
|
+
- **this is the required input type for `map_fields()` below**
|
|
107
|
+
```
|
|
108
|
+
>>> parser = ParseIOC("tests/ioc_examples.txt")
|
|
109
|
+
>>> p = parser.parse_many(mode="single")
|
|
110
|
+
>>> print(json.dumps(p, indent=4))
|
|
111
|
+
[
|
|
112
|
+
{
|
|
113
|
+
"ioc": "bob@email.local",
|
|
114
|
+
"ioc_type": "email"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"ioc": "email.local",
|
|
118
|
+
"ioc_type": "domain"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"ioc": 9443,
|
|
122
|
+
"ioc_type": "port"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"ioc": "username:password",
|
|
126
|
+
"ioc_type": "credentials"
|
|
127
|
+
},
|
|
128
|
+
...
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
### Map a **list** or **file** of indicators to SIEM fields using `map_fields()`
|
|
133
|
+
|
|
134
|
+
These examples use `map_ecs.toml` as a reference for parsing a file into [Elastic Common Schema (ECS)](https://www.elastic.co/docs/reference/ecs/ecs-field-reference) fields
|
|
135
|
+
```
|
|
136
|
+
>>> parser = ParseIOC("tests/ioc_examples.txt")
|
|
137
|
+
>>> list_of_dicts = parser.parse_many(mode="single")
|
|
138
|
+
>>> mapped_fields = parser.map_fields(list_of_dicts, map_file="map_ecs.toml")
|
|
139
|
+
>>> print(json.dumps(mapped_fields, indent=4))
|
|
140
|
+
{
|
|
141
|
+
"destination.domain": [
|
|
142
|
+
"email.local",
|
|
143
|
+
"emailsubdomain.bad.local",
|
|
144
|
+
"emailsubdomain.bad.local.xn--p1ai",
|
|
145
|
+
...
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Specify `keep_types=True` in `map_fields()` to nest the SIEM values under the original type categorizations
|
|
149
|
+
```
|
|
150
|
+
>>> parser = ParseIOC("tests/ioc_examples.txt")
|
|
151
|
+
>>> list_of_dicts = parser.parse_many(mode="single")
|
|
152
|
+
>>> mapped_fields = parser.map_fields(list_of_dicts, map_file="map_ecs.toml", keep_types=True)
|
|
153
|
+
>>> print(json.dumps(mapped_fields, indent=4))
|
|
154
|
+
{
|
|
155
|
+
"domain": {
|
|
156
|
+
"destination.domain": [
|
|
157
|
+
"email.local",
|
|
158
|
+
"emailsubdomain.bad.local",
|
|
159
|
+
"emailsubdomain.bad.local.xn--p1ai",
|
|
160
|
+
...
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
## Gotchas
|
|
165
|
+
- Domain names must have a period (`.`) otherwise the string will be considered a file.
|
|
166
|
+
- This regex in the domain checker, determines if the parsed domain name drops further down to the `file` checker or not:
|
|
167
|
+
```
|
|
168
|
+
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)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
## TODO
|
|
173
|
+
|
|
174
|
+
- add optional type conversions to match TheHive5 default observable types
|
|
175
|
+
- change the list of categorizations in README to a markdown table, showing the converted types
|
|
176
|
+
- need to streamline and optimize code
|
|
177
|
+
- remove the filepath regex statements created by AI
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: parse_ioc
|
|
3
|
+
Version: 0.7.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
|
+
|
|
46
|
+
## Setup
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
uv add parse_ioc
|
|
50
|
+
# or
|
|
51
|
+
python3 -m pip install parse_ioc
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
### Import
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
from parse_ioc import ParseIOC
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Categorize a single indicator using `ParseIOC.parse_one()`
|
|
64
|
+
|
|
65
|
+
available attributes:
|
|
66
|
+
- `ioc_type`: the categorization (string)
|
|
67
|
+
- `to_dict`: the categorized IOC as a dictionary (contains `ioc` and `ioc_type`)
|
|
68
|
+
- `to_json`: the categorized IOC as a JSON string (contains `ioc` and `ioc_type`)
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
>>> from parse_ioc import ParseIOC
|
|
72
|
+
>>> p = ParseIOC.parse_one("https://192.168.20.20/")
|
|
73
|
+
>>> p.ioc_type
|
|
74
|
+
'ipv4'
|
|
75
|
+
>>> p.to_dict
|
|
76
|
+
{'ioc': '192.168.20.20', 'ioc_type': 'ipv4'}
|
|
77
|
+
>>> p.to_json
|
|
78
|
+
'{"ioc": "192.168.20.20", "ioc_type": "ipv4"}'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
"extra" details are added when appropriate, such as credentials in a URL, ports, or domains in email addresses
|
|
82
|
+
```
|
|
83
|
+
>>> e = ParseIOC.parse_one("alice@email.local").to_dict
|
|
84
|
+
>>> print(json.dumps(e, indent=4))
|
|
85
|
+
{
|
|
86
|
+
"ioc": "alice@email.local",
|
|
87
|
+
"ioc_type": "email",
|
|
88
|
+
"extra": [
|
|
89
|
+
{
|
|
90
|
+
"ioc": "email.local",
|
|
91
|
+
"ioc_type": "domain"
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
### Categorize a **list** or **file** of indicators using `ParseIOC()` methods
|
|
99
|
+
|
|
100
|
+
Instantiate the class with your file path once using `ParseIOC(<path>)`, then use class methods.
|
|
101
|
+
|
|
102
|
+
Using `mode="combined"` produces one dictionary of categorized indicators
|
|
103
|
+
```
|
|
104
|
+
>>> parser = ParseIOC("tests/ioc_examples.txt")
|
|
105
|
+
>>> p = parser.parse_many(mode="combined")
|
|
106
|
+
>>> print(json.dumps(p, indent=4))
|
|
107
|
+
{
|
|
108
|
+
"email": [
|
|
109
|
+
"bob@email.local",
|
|
110
|
+
"bob@emailsubdomain.bad.local.xn--p1ai",
|
|
111
|
+
"xn--bob--o4d3g5c@xn--c1a3a5a.xn--p1ai",
|
|
112
|
+
...
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Using `mode="single"` produces a list of dictionaries in the `parse_one()` style
|
|
116
|
+
- **this is the required input type for `map_fields()` below**
|
|
117
|
+
```
|
|
118
|
+
>>> parser = ParseIOC("tests/ioc_examples.txt")
|
|
119
|
+
>>> p = parser.parse_many(mode="single")
|
|
120
|
+
>>> print(json.dumps(p, indent=4))
|
|
121
|
+
[
|
|
122
|
+
{
|
|
123
|
+
"ioc": "bob@email.local",
|
|
124
|
+
"ioc_type": "email"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"ioc": "email.local",
|
|
128
|
+
"ioc_type": "domain"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"ioc": 9443,
|
|
132
|
+
"ioc_type": "port"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"ioc": "username:password",
|
|
136
|
+
"ioc_type": "credentials"
|
|
137
|
+
},
|
|
138
|
+
...
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
### Map a **list** or **file** of indicators to SIEM fields using `map_fields()`
|
|
143
|
+
|
|
144
|
+
These examples use `map_ecs.toml` as a reference for parsing a file into [Elastic Common Schema (ECS)](https://www.elastic.co/docs/reference/ecs/ecs-field-reference) fields
|
|
145
|
+
```
|
|
146
|
+
>>> parser = ParseIOC("tests/ioc_examples.txt")
|
|
147
|
+
>>> list_of_dicts = parser.parse_many(mode="single")
|
|
148
|
+
>>> mapped_fields = parser.map_fields(list_of_dicts, map_file="map_ecs.toml")
|
|
149
|
+
>>> print(json.dumps(mapped_fields, indent=4))
|
|
150
|
+
{
|
|
151
|
+
"destination.domain": [
|
|
152
|
+
"email.local",
|
|
153
|
+
"emailsubdomain.bad.local",
|
|
154
|
+
"emailsubdomain.bad.local.xn--p1ai",
|
|
155
|
+
...
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Specify `keep_types=True` in `map_fields()` to nest the SIEM values under the original type categorizations
|
|
159
|
+
```
|
|
160
|
+
>>> parser = ParseIOC("tests/ioc_examples.txt")
|
|
161
|
+
>>> list_of_dicts = parser.parse_many(mode="single")
|
|
162
|
+
>>> mapped_fields = parser.map_fields(list_of_dicts, map_file="map_ecs.toml", keep_types=True)
|
|
163
|
+
>>> print(json.dumps(mapped_fields, indent=4))
|
|
164
|
+
{
|
|
165
|
+
"domain": {
|
|
166
|
+
"destination.domain": [
|
|
167
|
+
"email.local",
|
|
168
|
+
"emailsubdomain.bad.local",
|
|
169
|
+
"emailsubdomain.bad.local.xn--p1ai",
|
|
170
|
+
...
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
## Gotchas
|
|
175
|
+
- Domain names must have a period (`.`) otherwise the string will be considered a file.
|
|
176
|
+
- This regex in the domain checker, determines if the parsed domain name drops further down to the `file` checker or not:
|
|
177
|
+
```
|
|
178
|
+
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)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
## TODO
|
|
183
|
+
|
|
184
|
+
- add optional type conversions to match TheHive5 default observable types
|
|
185
|
+
- change the list of categorizations in README to a markdown table, showing the converted types
|
|
186
|
+
- need to streamline and optimize code
|
|
187
|
+
- remove the filepath regex statements created by AI
|