parse_ioc 0.5.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.
@@ -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
@@ -5,4 +5,5 @@ pyproject.toml
5
5
  parse_ioc.egg-info/PKG-INFO
6
6
  parse_ioc.egg-info/SOURCES.txt
7
7
  parse_ioc.egg-info/dependency_links.txt
8
- parse_ioc.egg-info/top_level.txt
8
+ parse_ioc.egg-info/top_level.txt
9
+ tests/test_parse_ioc.py