txt2detection 1.0.9__tar.gz → 1.0.10__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.

Potentially problematic release.


This version of txt2detection might be problematic. Click here for more details.

Files changed (61) hide show
  1. {txt2detection-1.0.9 → txt2detection-1.0.10}/.github/workflows/run-tests.yml +4 -0
  2. {txt2detection-1.0.9 → txt2detection-1.0.10}/PKG-INFO +1 -1
  3. {txt2detection-1.0.9 → txt2detection-1.0.10}/pyproject.toml +1 -1
  4. txt2detection-1.0.10/tests/src/conftest.py +18 -0
  5. txt2detection-1.0.10/tests/src/test_attack_flow.py +519 -0
  6. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/src/test_bundler.py +6 -0
  7. {txt2detection-1.0.9 → txt2detection-1.0.10}/txt2detection/attack_flow.py +1 -3
  8. {txt2detection-1.0.9 → txt2detection-1.0.10}/txt2detection/bundler.py +18 -0
  9. {txt2detection-1.0.9 → txt2detection-1.0.10}/.env.example +0 -0
  10. {txt2detection-1.0.9 → txt2detection-1.0.10}/.env.markdown +0 -0
  11. {txt2detection-1.0.9 → txt2detection-1.0.10}/.github/workflows/create-release.yml +0 -0
  12. {txt2detection-1.0.9 → txt2detection-1.0.10}/.gitignore +0 -0
  13. {txt2detection-1.0.9 → txt2detection-1.0.10}/LICENSE +0 -0
  14. {txt2detection-1.0.9 → txt2detection-1.0.10}/README.md +0 -0
  15. {txt2detection-1.0.9 → txt2detection-1.0.10}/config/detection_languages.yaml +0 -0
  16. {txt2detection-1.0.9 → txt2detection-1.0.10}/docs/README.md +0 -0
  17. {txt2detection-1.0.9 → txt2detection-1.0.10}/docs/txt2detection.png +0 -0
  18. {txt2detection-1.0.9 → txt2detection-1.0.10}/requirements.txt +0 -0
  19. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/files/CVE-2024-56520.txt +0 -0
  20. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/files/EC2-exfil.txt +0 -0
  21. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/files/observables.txt +0 -0
  22. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/files/sigma-rule-attack-enterprise.yml +0 -0
  23. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/files/sigma-rule-attack-flow.yml +0 -0
  24. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/files/sigma-rule-custom-tags.yml +0 -0
  25. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/files/sigma-rule-existing-related.yml +0 -0
  26. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/files/sigma-rule-master.yml +0 -0
  27. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/files/sigma-rule-no-author.yml +0 -0
  28. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/files/sigma-rule-no-date.yml +0 -0
  29. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/files/sigma-rule-no-description.yml +0 -0
  30. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/files/sigma-rule-no-level.yml +0 -0
  31. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/files/sigma-rule-no-license.yml +0 -0
  32. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/files/sigma-rule-no-status.yml +0 -0
  33. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/files/sigma-rule-no-tags.yml +0 -0
  34. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/files/sigma-rule-no-title.yml +0 -0
  35. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/files/sigma-rule-observables.yml +0 -0
  36. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/files/sigma-rule-one-date.yml +0 -0
  37. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/manual-tests/README.md +0 -0
  38. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/src/__init__.py +0 -0
  39. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/src/requirements.txt +0 -0
  40. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/src/test_main.py +0 -0
  41. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/src/test_main_run_txt2detction.py +0 -0
  42. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/src/test_models.py +0 -0
  43. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/src/test_observables.py +0 -0
  44. {txt2detection-1.0.9 → txt2detection-1.0.10}/tests/src/test_utils.py +0 -0
  45. {txt2detection-1.0.9 → txt2detection-1.0.10}/txt2detection/__init__.py +0 -0
  46. {txt2detection-1.0.9 → txt2detection-1.0.10}/txt2detection/__main__.py +0 -0
  47. {txt2detection-1.0.9 → txt2detection-1.0.10}/txt2detection/ai_extractor/__init__.py +0 -0
  48. {txt2detection-1.0.9 → txt2detection-1.0.10}/txt2detection/ai_extractor/anthropic.py +0 -0
  49. {txt2detection-1.0.9 → txt2detection-1.0.10}/txt2detection/ai_extractor/base.py +0 -0
  50. {txt2detection-1.0.9 → txt2detection-1.0.10}/txt2detection/ai_extractor/deepseek.py +0 -0
  51. {txt2detection-1.0.9 → txt2detection-1.0.10}/txt2detection/ai_extractor/gemini.py +0 -0
  52. {txt2detection-1.0.9 → txt2detection-1.0.10}/txt2detection/ai_extractor/models.py +0 -0
  53. {txt2detection-1.0.9 → txt2detection-1.0.10}/txt2detection/ai_extractor/openai.py +0 -0
  54. {txt2detection-1.0.9 → txt2detection-1.0.10}/txt2detection/ai_extractor/openrouter.py +0 -0
  55. {txt2detection-1.0.9 → txt2detection-1.0.10}/txt2detection/ai_extractor/prompts.py +0 -0
  56. {txt2detection-1.0.9 → txt2detection-1.0.10}/txt2detection/ai_extractor/utils.py +0 -0
  57. {txt2detection-1.0.9 → txt2detection-1.0.10}/txt2detection/credential_checker.py +0 -0
  58. {txt2detection-1.0.9 → txt2detection-1.0.10}/txt2detection/models.py +0 -0
  59. {txt2detection-1.0.9 → txt2detection-1.0.10}/txt2detection/observables.py +0 -0
  60. {txt2detection-1.0.9 → txt2detection-1.0.10}/txt2detection/utils.py +0 -0
  61. {txt2detection-1.0.9 → txt2detection-1.0.10}/txt2detection.py +0 -0
@@ -33,6 +33,10 @@ jobs:
33
33
  run: |
34
34
  echo > .env
35
35
  echo "INPUT_TOKEN_LIMIT=1000" >> .env
36
+ echo "CTIBUTLER_BASE_URL=https://api.ctibutler.com/" >> .env
37
+ echo "CTIBUTLER_API_KEY=${{ secrets.CTIBUTLER_API_KEY }}" >> .env
38
+ echo "VULMATCH_BASE_URL=https://api.vulmatch.com/" >> .env
39
+ echo "VULMATCH_API_KEY=${{ secrets.VULMATCH_API_KEY }}" >> .env
36
40
 
37
41
  - name: Run Tests
38
42
  id: run_tests
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: txt2detection
3
- Version: 1.0.9
3
+ Version: 1.0.10
4
4
  Summary: A command line tool that takes a txt file containing threat intelligence and turns it into a detection rule.
5
5
  Project-URL: Homepage, https://github.com/muchdogesec/txt2detection
6
6
  Project-URL: Issues, https://github.com/muchdogesec/txt2detection/issues
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "txt2detection"
7
- version = "1.0.9"
7
+ version = "1.0.10"
8
8
  authors = [
9
9
  { name = "dogesec" }
10
10
  ]
@@ -0,0 +1,18 @@
1
+ import pytest
2
+
3
+ from txt2detection.bundler import Bundler
4
+ from datetime import datetime
5
+
6
+
7
+
8
+ @pytest.fixture
9
+ def bundler_instance():
10
+ return Bundler(
11
+ name="Test Report",
12
+ identity=None,
13
+ tlp_level="red",
14
+ description="This is a test report.",
15
+ labels=["tlp.red", "test.test-var"],
16
+ created=datetime(2025, 1, 1),
17
+ report_id="74e36652-00f5-4dca-bf10-9f02fc996dcc",
18
+ )
@@ -0,0 +1,519 @@
1
+ from functools import lru_cache
2
+ from types import SimpleNamespace
3
+ import pytest
4
+ from unittest.mock import MagicMock, patch
5
+
6
+ from txt2detection.ai_extractor.models import AttackFlowList
7
+ from txt2detection.attack_flow import (
8
+ create_navigator_layer,
9
+ get_techniques_from_extracted_objects,
10
+ parse_domain_flow,
11
+ parse_flow,
12
+ extract_attack_flow_and_navigator,
13
+ )
14
+ from stix2 import Report
15
+
16
+ from txt2detection.bundler import Bundler
17
+
18
+
19
+ def test_parse_flow(dummy_report, dummy_objects, dummy_flow):
20
+ tactics = get_all_tactics()
21
+ techniques = get_techniques_from_extracted_objects(dummy_objects, tactics)
22
+ flow = dummy_flow
23
+ report = dummy_report
24
+ expected_ids = {
25
+ "attack-flow--57213643-98f5-5ca2-a12d-fbe5a97f54a2",
26
+ "attack-action--62080947-214c-5c4b-8587-dc507659250d",
27
+ "x-mitre-tactic--5bc1d813-693e-4823-9961-abf9af4b0e92",
28
+ "relationship--8f4e8003-25dc-51ef-8c5f-ab022d5ad458",
29
+ "attack-action--57faf9fd-87e4-55a8-b1a8-9ba779ef16f7",
30
+ "report--bc14a07a-5189-5f64-85c3-33161b923627",
31
+ "x-mitre-tactic--ffd5bcee-6e16-4dd2-8eca-7b3beedf33ca",
32
+ "attack-pattern--3f886f2a-874f-4333-b794-aa6075009b1c",
33
+ "attack-pattern--1ecb2399-e8ba-4f6b-8ba7-5c27d49405cf",
34
+ }
35
+
36
+ flow_objects = parse_flow(report, flow, techniques, tactics)
37
+ assert {obj["id"] for obj in flow_objects} == expected_ids
38
+
39
+
40
+ def test_parse_flow__no_success(dummy_report):
41
+ flow_objects = parse_flow(
42
+ dummy_report,
43
+ AttackFlowList(
44
+ success=False,
45
+ matrix="enterprise",
46
+ items=[],
47
+ tactic_selection=[],
48
+ ),
49
+ None,
50
+ None,
51
+ )
52
+ assert len(flow_objects) == 0
53
+
54
+
55
+ @lru_cache
56
+ def get_all_tactics():
57
+ return Bundler.get_attack_tactics()
58
+
59
+
60
+ def test_parse_domain_flow(dummy_report, dummy_objects, dummy_flow):
61
+ tactics = get_all_tactics()
62
+ techniques = get_techniques_from_extracted_objects(dummy_objects, tactics)
63
+ flow = dummy_flow
64
+ report = dummy_report
65
+ flow_objects = parse_domain_flow(
66
+ report, flow, techniques, tactics, "enterprise-attack"
67
+ )
68
+ assert {obj["id"] for obj in flow_objects} == {
69
+ "attack-flow--57213643-98f5-5ca2-a12d-fbe5a97f54a2",
70
+ "attack-pattern--1ecb2399-e8ba-4f6b-8ba7-5c27d49405cf",
71
+ "x-mitre-tactic--5bc1d813-693e-4823-9961-abf9af4b0e92",
72
+ "attack-pattern--3f886f2a-874f-4333-b794-aa6075009b1c",
73
+ "x-mitre-tactic--ffd5bcee-6e16-4dd2-8eca-7b3beedf33ca",
74
+ "attack-action--62080947-214c-5c4b-8587-dc507659250d",
75
+ "attack-action--57faf9fd-87e4-55a8-b1a8-9ba779ef16f7",
76
+ "relationship--8f4e8003-25dc-51ef-8c5f-ab022d5ad458",
77
+ }
78
+
79
+
80
+ def test_get_techniques_from_extracted_objects(dummy_objects):
81
+ tactics = get_all_tactics()
82
+ techniques = get_techniques_from_extracted_objects(dummy_objects, tactics)
83
+ stix_objects = [v.pop("stix_obj") for v in techniques.values()]
84
+ assert dummy_objects == stix_objects
85
+ print(techniques)
86
+ assert techniques == {
87
+ "T1190": {
88
+ "domain": "enterprise-attack",
89
+ "name": "Exploit Public-Facing Application",
90
+ "possible_tactics": {"initial-access": "TA0001"},
91
+ "id": "T1190",
92
+ "platforms": [
93
+ "Containers",
94
+ "IaaS",
95
+ "Linux",
96
+ "Network Devices",
97
+ "Windows",
98
+ "macOS",
99
+ "ESXi",
100
+ ],
101
+ },
102
+ "T1547": {
103
+ "domain": "enterprise-attack",
104
+ "name": "Boot or Logon Autostart Execution",
105
+ "possible_tactics": {
106
+ "persistence": "TA0003",
107
+ "privilege-escalation": "TA0004",
108
+ },
109
+ "id": "T1547",
110
+ "platforms": ["Linux", "macOS", "Windows", "Network Devices"],
111
+ },
112
+ }
113
+
114
+
115
+ def test_extract_attack_flow_and_navigator(
116
+ bundler_instance, dummy_objects, dummy_report
117
+ ):
118
+ bundler = MagicMock()
119
+ bundler.report = dummy_report
120
+ bundler.bundle.objects = dummy_objects
121
+ ai_extractor = MagicMock()
122
+ mock_extract_flow = ai_extractor.extract_attack_flow
123
+ text = "My awesome text"
124
+
125
+ tactics = get_all_tactics()
126
+ techniques = get_techniques_from_extracted_objects(bundler.bundle.objects, tactics)
127
+ bundler.get_attack_tactics.return_value = tactics
128
+
129
+ with (
130
+ patch("txt2detection.attack_flow.parse_flow") as mock_parse_flow,
131
+ patch(
132
+ "txt2detection.attack_flow.create_navigator_layer"
133
+ ) as mock_create_navigator_layer,
134
+ ):
135
+ # ================= Both flow and navigator ===================
136
+ flow, nav = extract_attack_flow_and_navigator(
137
+ bundler, text, True, True, ai_extractor
138
+ )
139
+ assert bundler.flow_objects == mock_parse_flow.return_value
140
+ assert (flow, nav) == (
141
+ mock_extract_flow.return_value,
142
+ mock_create_navigator_layer.return_value,
143
+ )
144
+ mock_parse_flow.assert_called_once_with(
145
+ bundler.report, mock_extract_flow.return_value, techniques, tactics
146
+ )
147
+ mock_extract_flow.assert_called_once_with(text, techniques)
148
+
149
+ mock_create_navigator_layer.assert_called_once_with(
150
+ bundler.report,
151
+ mock_extract_flow.return_value,
152
+ techniques,
153
+ tactics,
154
+ )
155
+
156
+ ### reset mocks
157
+ mock_parse_flow.reset_mock()
158
+ mock_create_navigator_layer.reset_mock()
159
+ mock_extract_flow.reset_mock()
160
+
161
+ # ================= only flow ===================
162
+ flow, nav = extract_attack_flow_and_navigator(
163
+ bundler, text, True, False, ai_extractor
164
+ )
165
+ assert bundler.flow_objects == mock_parse_flow.return_value
166
+ assert (flow, nav) == (mock_extract_flow.return_value, None)
167
+ mock_parse_flow.assert_called_once_with(
168
+ bundler.report, mock_extract_flow.return_value, techniques, tactics
169
+ )
170
+ mock_extract_flow.assert_called_once_with(text, techniques)
171
+
172
+ mock_create_navigator_layer.assert_not_called()
173
+
174
+ ### reset mocks
175
+ mock_parse_flow.reset_mock()
176
+ mock_create_navigator_layer.reset_mock()
177
+ mock_extract_flow.reset_mock()
178
+
179
+ # ================= only navigator ===================
180
+ flow, nav = extract_attack_flow_and_navigator(
181
+ bundler, text, False, True, ai_extractor
182
+ )
183
+ assert bundler.flow_objects == mock_parse_flow.return_value
184
+ mock_extract_flow.assert_called_once_with(text, techniques)
185
+ assert (flow, nav) == (
186
+ mock_extract_flow.return_value,
187
+ mock_create_navigator_layer.return_value,
188
+ )
189
+ mock_parse_flow.assert_not_called()
190
+
191
+ mock_create_navigator_layer.assert_called_once_with(
192
+ bundler.report,
193
+ mock_extract_flow.return_value,
194
+ techniques,
195
+ tactics,
196
+ )
197
+
198
+ ### reset mocks
199
+ mock_parse_flow.reset_mock()
200
+ mock_create_navigator_layer.reset_mock()
201
+ mock_extract_flow.reset_mock()
202
+ # ============ no technique object ============
203
+ bundler.bundle.objects = []
204
+ flow, nav = extract_attack_flow_and_navigator(
205
+ bundler, text, True, True, ai_extractor
206
+ )
207
+ mock_extract_flow.assert_not_called()
208
+ assert (flow, nav) == (None, None)
209
+ mock_parse_flow.assert_not_called()
210
+
211
+ mock_create_navigator_layer.assert_not_called()
212
+
213
+
214
+ def test_create_navigator_layer(dummy_report):
215
+ summary = "this is a summary"
216
+ flow = MagicMock()
217
+ tactics_1 = {
218
+ "TA01": "initial-access",
219
+ "TA02": "lateral-movement",
220
+ "TA03": "command-and-control",
221
+ }
222
+ tactics_2 = {
223
+ "TA11": "initial-access",
224
+ "TA12": "lateral-movement",
225
+ "TA25": "command-and-control",
226
+ "TA123": "persistence",
227
+ "TA91": "exfiltration",
228
+ }
229
+ flow.items = [
230
+ SimpleNamespace(
231
+ attack_technique_id="T0001",
232
+ attack_tactic_id="TA01",
233
+ description="description 1",
234
+ ),
235
+ SimpleNamespace(
236
+ attack_technique_id="T0003",
237
+ attack_tactic_id="TA03",
238
+ description="description 2",
239
+ ),
240
+ SimpleNamespace(
241
+ attack_technique_id="T1001",
242
+ attack_tactic_id="TA11",
243
+ description="description 3",
244
+ ),
245
+ SimpleNamespace(
246
+ attack_technique_id="T1002",
247
+ attack_tactic_id="TA12",
248
+ description="description 4",
249
+ ),
250
+ SimpleNamespace(
251
+ attack_technique_id="T2001",
252
+ attack_tactic_id="TA11",
253
+ description="description 28jhsjhs",
254
+ ),
255
+ SimpleNamespace(
256
+ attack_technique_id="T2003",
257
+ attack_tactic_id="TA91",
258
+ description="description sasa",
259
+ ),
260
+ ]
261
+ techniques = {
262
+ "T0001": dict(
263
+ id="T0001", domain="enterprise-attack", possible_tactics=tactics_1
264
+ ),
265
+ "T0002": dict(
266
+ id="T0002", domain="enterprise-attack", possible_tactics=tactics_1
267
+ ),
268
+ "T0003": dict(
269
+ id="T0003", domain="enterprise-attack", possible_tactics=tactics_1
270
+ ),
271
+ }
272
+
273
+ retval = create_navigator_layer(
274
+ dummy_report,
275
+ flow,
276
+ techniques,
277
+ tactics={"version": "16.1"},
278
+ )
279
+ assert len(retval) == 1
280
+ print(retval)
281
+ assert retval == [
282
+ {
283
+ "versions": {"layer": "4.5", "attack": "16.1", "navigator": "5.1.0"},
284
+ "name": "fake python vulnerability report",
285
+ "domain": "enterprise-attack",
286
+ "techniques": [],
287
+ "gradient": {
288
+ "colors": ["#ffffff", "#ff6666"],
289
+ "minValue": 0,
290
+ "maxValue": 100,
291
+ },
292
+ "legendItems": [],
293
+ "metadata": [
294
+ {
295
+ "name": "report_id",
296
+ "value": "report--bc14a07a-5189-5f64-85c3-33161b923627",
297
+ }
298
+ ],
299
+ "links": [
300
+ {
301
+ "label": "Generated using txt2detection",
302
+ "url": "https://github.com/muchdogesec/txt2detection/",
303
+ }
304
+ ],
305
+ "layout": {"layout": "side"},
306
+ }
307
+ ]
308
+
309
+
310
+ def test_create_navigator_layer__real_flow(dummy_report, dummy_flow, dummy_objects):
311
+ tactics = get_all_tactics()
312
+ techniques = get_techniques_from_extracted_objects(dummy_objects, tactics)
313
+ tactics["version"] = "16.2"
314
+ retval = create_navigator_layer(dummy_report, dummy_flow, techniques, tactics)
315
+ assert len(retval) == 1
316
+ assert retval == [
317
+ {
318
+ "versions": {"layer": "4.5", "attack": "16.2", "navigator": "5.1.0"},
319
+ "name": "fake python vulnerability report",
320
+ "domain": "enterprise-attack",
321
+ "techniques": [
322
+ {
323
+ "techniqueID": "T1190",
324
+ "tactic": "initial-access",
325
+ "score": 100,
326
+ "showSubtechniques": True,
327
+ "comment": "The adversary exploited a vulnerability in a public-facing application, identified as CVE-2024-56520, to gain initial access to the system.",
328
+ },
329
+ {
330
+ "techniqueID": "T1547",
331
+ "tactic": "persistence",
332
+ "score": 100,
333
+ "showSubtechniques": True,
334
+ "comment": "After gaining access, the adversary used boot or logon autostart execution techniques to maintain persistence on the compromised system.",
335
+ },
336
+ ],
337
+ "gradient": {
338
+ "colors": ["#ffffff", "#ff6666"],
339
+ "minValue": 0,
340
+ "maxValue": 100,
341
+ },
342
+ "legendItems": [],
343
+ "metadata": [
344
+ {
345
+ "name": "report_id",
346
+ "value": "report--bc14a07a-5189-5f64-85c3-33161b923627",
347
+ }
348
+ ],
349
+ "links": [
350
+ {
351
+ "label": "Generated using txt2detection",
352
+ "url": "https://github.com/muchdogesec/txt2detection/",
353
+ }
354
+ ],
355
+ "layout": {"layout": "side"},
356
+ }
357
+ ]
358
+
359
+
360
+ @pytest.fixture
361
+ def dummy_objects():
362
+ return [
363
+ {
364
+ "created": "2018-04-18T17:59:24.739Z",
365
+ "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5",
366
+ "description": "Adversaries may attempt to exploit a weakness in an Internet-facing host or system to initially access a network. The weakness in the system can be a software bug, a temporary glitch, or a misconfiguration.\n\nExploited applications are often websites/web servers, but can also include databases (like SQL), standard services (like SMB or SSH), network device administration and management protocols (like SNMP and Smart Install), and any other system with Internet-accessible open sockets.(Citation: NVD CVE-2016-6662)(Citation: CIS Multiple SMB Vulnerabilities)(Citation: US-CERT TA18-106A Network Infrastructure Devices 2018)(Citation: Cisco Blog Legacy Device Attacks)(Citation: NVD CVE-2014-7169) On ESXi infrastructure, adversaries may exploit exposed OpenSLP services; they may alternatively exploit exposed VMware vCenter servers.(Citation: Recorded Future ESXiArgs Ransomware 2023)(Citation: Ars Technica VMWare Code Execution Vulnerability 2021) Depending on the flaw being exploited, this may also involve [Exploitation for Defense Evasion](https://attack.mitre.org/techniques/T1211) or [Exploitation for Client Execution](https://attack.mitre.org/techniques/T1203).\n\nIf an application is hosted on cloud-based infrastructure and/or is containerized, then exploiting it may lead to compromise of the underlying instance or container. This can allow an adversary a path to access the cloud or container APIs (e.g., via the [Cloud Instance Metadata API](https://attack.mitre.org/techniques/T1552/005)), exploit container host access via [Escape to Host](https://attack.mitre.org/techniques/T1611), or take advantage of weak identity and access management policies.\n\nAdversaries may also exploit edge network infrastructure and related appliances, specifically targeting devices that do not support robust host-based defenses.(Citation: Mandiant Fortinet Zero Day)(Citation: Wired Russia Cyberwar)\n\nFor websites and databases, the OWASP top 10 and CWE top 25 highlight the most common web-based vulnerabilities.(Citation: OWASP Top 10)(Citation: CWE top 25)",
367
+ "external_references": [
368
+ {
369
+ "source_name": "mitre-attack",
370
+ "url": "https://attack.mitre.org/techniques/T1190",
371
+ "external_id": "T1190",
372
+ },
373
+ ],
374
+ "id": "attack-pattern--3f886f2a-874f-4333-b794-aa6075009b1c",
375
+ "kill_chain_phases": [
376
+ {"kill_chain_name": "mitre-attack", "phase_name": "initial-access"}
377
+ ],
378
+ "modified": "2025-04-15T19:58:25.266Z",
379
+ "name": "Exploit Public-Facing Application",
380
+ "object_marking_refs": [
381
+ "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"
382
+ ],
383
+ "revoked": False,
384
+ "spec_version": "2.1",
385
+ "type": "attack-pattern",
386
+ "x_mitre_attack_spec_version": "3.2.0",
387
+ "x_mitre_contributors": [
388
+ "Yossi Weizman, Azure Defender Research Team",
389
+ "Praetorian",
390
+ ],
391
+ "x_mitre_data_sources": [
392
+ "Network Traffic: Network Traffic Content",
393
+ "Application Log: Application Log Content",
394
+ ],
395
+ "x_mitre_deprecated": False,
396
+ "x_mitre_detection": "Monitor application logs for abnormal behavior that may indicate attempted or successful exploitation. Use deep packet inspection to look for artifacts of common exploit traffic, such as SQL injection. Web Application Firewalls may detect improper inputs attempting exploitation.",
397
+ "x_mitre_domains": ["enterprise-attack"],
398
+ "x_mitre_is_subtechnique": False,
399
+ "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5",
400
+ "x_mitre_platforms": [
401
+ "Containers",
402
+ "IaaS",
403
+ "Linux",
404
+ "Network Devices",
405
+ "Windows",
406
+ "macOS",
407
+ "ESXi",
408
+ ],
409
+ "x_mitre_version": "2.7",
410
+ },
411
+ {
412
+ "created": "2020-01-23T17:46:59.535Z",
413
+ "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5",
414
+ "description": "Adversaries may configure system settings to automatically execute a program during system boot or logon to maintain persistence or gain higher-level privileges on compromised systems. Operating systems may have mechanisms for automatically running a program on system boot or account logon.(Citation: Microsoft Run Key)(Citation: MSDN Authentication Packages)(Citation: Microsoft TimeProvider)(Citation: Cylance Reg Persistence Sept 2013)(Citation: Linux Kernel Programming) These mechanisms may include automatically executing programs that are placed in specially designated directories or are referenced by repositories that store configuration information, such as the Windows Registry. An adversary may achieve the same goal by modifying or extending features of the kernel.\n\nSince some boot or logon autostart programs run with higher privileges, an adversary may leverage these to elevate privileges.",
415
+ "external_references": [
416
+ {
417
+ "source_name": "mitre-attack",
418
+ "url": "https://attack.mitre.org/techniques/T1547",
419
+ "external_id": "T1547",
420
+ }
421
+ ],
422
+ "id": "attack-pattern--1ecb2399-e8ba-4f6b-8ba7-5c27d49405cf",
423
+ "kill_chain_phases": [
424
+ {"kill_chain_name": "mitre-attack", "phase_name": "persistence"},
425
+ {
426
+ "kill_chain_name": "mitre-attack",
427
+ "phase_name": "privilege-escalation",
428
+ },
429
+ ],
430
+ "modified": "2025-04-15T19:58:12.270Z",
431
+ "name": "Boot or Logon Autostart Execution",
432
+ "object_marking_refs": [
433
+ "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"
434
+ ],
435
+ "revoked": False,
436
+ "spec_version": "2.1",
437
+ "type": "attack-pattern",
438
+ "x_mitre_attack_spec_version": "3.2.0",
439
+ "x_mitre_data_sources": [
440
+ "Process: OS API Execution",
441
+ "Module: Module Load",
442
+ "Command: Command Execution",
443
+ "File: File Creation",
444
+ "Windows Registry: Windows Registry Key Creation",
445
+ "Windows Registry: Windows Registry Key Modification",
446
+ "File: File Modification",
447
+ "Kernel: Kernel Module Load",
448
+ "Process: Process Creation",
449
+ "Driver: Driver Load",
450
+ ],
451
+ "x_mitre_deprecated": False,
452
+ "x_mitre_detection": "Monitor for additions or modifications of mechanisms that could be used to trigger autostart execution, such as relevant additions to the Registry. Look for changes that are not correlated with known updates, patches, or other planned administrative activity. Tools such as Sysinternals Autoruns may also be used to detect system autostart configuration changes that could be attempts at persistence.(Citation: TechNet Autoruns) Changes to some autostart configuration settings may happen under normal conditions when legitimate software is installed. \n\nSuspicious program execution as autostart programs may show up as outlier processes that have not been seen before when compared against historical data.To increase confidence of malicious activity, data and events should not be viewed in isolation, but as part of a chain of behavior that could lead to other activities, such as network connections made for Command and Control, learning details about the environment through Discovery, and Lateral Movement.\n\nMonitor DLL loads by processes, specifically looking for DLLs that are not recognized or not normally loaded into a process. Look for abnormal process behavior that may be due to a process loading a malicious DLL.\n\nMonitor for abnormal usage of utilities and command-line parameters involved in kernel modification or driver installation.",
453
+ "x_mitre_domains": ["enterprise-attack"],
454
+ "x_mitre_is_subtechnique": False,
455
+ "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5",
456
+ "x_mitre_platforms": ["Linux", "macOS", "Windows", "Network Devices"],
457
+ "x_mitre_version": "1.3",
458
+ },
459
+ ]
460
+
461
+
462
+ @pytest.fixture
463
+ def dummy_report():
464
+ return Report(
465
+ **{
466
+ "type": "report",
467
+ "spec_version": "2.1",
468
+ "id": "report--bc14a07a-5189-5f64-85c3-33161b923627",
469
+ "created_by_ref": "identity--a4d70b75-6f4a-5d19-9137-da863edd33d7",
470
+ "created": "2024-05-01T08:53:31.000Z",
471
+ "modified": "2024-05-01T08:53:31.000Z",
472
+ "name": "fake python vulnerability report",
473
+ "description": "requirements.txt file that contains pypotr version 5.1.1. the version was uploaded by a state agent; Attack Technique used include T1547 and cve-2024-56520 and T1190; The attack takes control of dns 1.1.1.1 and domain-name google.com and goes to url https://datadome.net",
474
+ "published": "2024-05-01T08:53:31Z",
475
+ "object_refs": [
476
+ "indicator--f2e55328-d20a-4637-ba55-394fc56121a4",
477
+ "data-source--22684436-fab3-5847-b642-bcf23430e361",
478
+ ],
479
+ "labels": ["x1.we", "pop.aaa1"],
480
+ "external_references": [
481
+ {
482
+ "source_name": "description_md5_hash",
483
+ "external_id": "4b2ca609c2ae27843b251a761efb8cdf",
484
+ },
485
+ {
486
+ "source_name": "url",
487
+ "external_id": "https://example.com/pypotr-compromised",
488
+ },
489
+ ],
490
+ "object_marking_refs": [
491
+ "marking-definition--bab4a63c-aed9-4cf5-a766-dfca5abac2bb",
492
+ "marking-definition--a4d70b75-6f4a-5d19-9137-da863edd33d7",
493
+ ],
494
+ },
495
+ )
496
+
497
+
498
+ @pytest.fixture
499
+ def dummy_flow():
500
+ return AttackFlowList.model_validate(
501
+ {
502
+ "tactic_selection": [["T1190", "initial-access"], ["T1547", "persistence"]],
503
+ "items": [
504
+ {
505
+ "position": 0,
506
+ "attack_technique_id": "T1190",
507
+ "name": "Exploitation of Public-Facing Application",
508
+ "description": "The adversary exploited a vulnerability in a public-facing application, identified as CVE-2024-56520, to gain initial access to the system.",
509
+ },
510
+ {
511
+ "position": 1,
512
+ "attack_technique_id": "T1547",
513
+ "name": "Establishing Persistence via Boot or Logon Autostart",
514
+ "description": "After gaining access, the adversary used boot or logon autostart execution techniques to maintain persistence on the compromised system.",
515
+ },
516
+ ],
517
+ "success": True,
518
+ }
519
+ )
@@ -283,3 +283,9 @@ def test_get_cve_objects(bundler_instance):
283
283
  cves = ['CVE-2025-1234', 'CVE-2024-1234']
284
284
  retval = bundler_instance.get_cve_objects(cves)
285
285
  assert {r['name'] for r in retval} == set(cves)
286
+
287
+
288
+ def test_flow_objects__adds_extension_definition(bundler_instance):
289
+ bundler_instance.flow_objects = [{'id': 'some-other', 'type': ''}]
290
+ assert bundler_instance.all_objects.issuperset({'some-other', "extension-definition--fb9c968a-745b-4ade-9b25-c324172197f4"})
291
+ assert "extension-definition--fb9c968a-745b-4ade-9b25-c324172197f4" not in bundler_instance.report.object_refs
@@ -9,14 +9,12 @@ from .bundler import Bundler
9
9
  from .ai_extractor.base import BaseAIExtractor
10
10
  from .models import UUID_NAMESPACE
11
11
  from stix2extensions.attack_action import AttackAction, AttackFlow
12
- from stix2extensions._extensions import attack_flow_ExtensionDefinitionSMO
13
-
14
12
 
15
13
  def parse_flow(report, flow: AttackFlowList, techniques, tactics):
16
14
  logging.info(f"flow.success = {flow.success}")
17
15
  if not flow.success:
18
16
  return []
19
- objects = [report, attack_flow_ExtensionDefinitionSMO]
17
+ objects = [report]
20
18
  for domain in ["enterprise-attack", "mobile-attack", "ics-attack"]:
21
19
  flow_objects = parse_domain_flow(report, flow, techniques, tactics, domain)
22
20
  objects.extend(flow_objects)
@@ -42,6 +42,7 @@ class Bundler:
42
42
  uuid = None
43
43
  id_map = dict()
44
44
  data: DataContainer
45
+ ATTACK_FLOW_SMO_URL = "https://github.com/muchdogesec/stix2extensions/raw/refs/heads/main/remote-definitions/attack-flow.json"
45
46
  # https://raw.githubusercontent.com/muchdogesec/stix4doge/refs/heads/main/objects/identity/txt2detection.json
46
47
  default_identity = Identity(
47
48
  **{
@@ -361,6 +362,23 @@ class Bundler:
361
362
  for d in container.detections:
362
363
  self.add_rule_indicator(d)
363
364
 
365
+
366
+ @property
367
+ def flow_objects(self):
368
+ return self._flow_objects
369
+
370
+ @flow_objects.setter
371
+ def flow_objects(self, objects):
372
+ smo_objects = requests.get(self.ATTACK_FLOW_SMO_URL).json()["objects"]
373
+ objects.extend(smo_objects)
374
+ for obj in objects:
375
+ if obj["id"] == self.report.id:
376
+ continue
377
+ is_report_object = obj["type"] not in ["extension-definition", "identity"]
378
+ self.add_ref(obj, append_report=is_report_object)
379
+ self._flow_objects = objects
380
+
381
+
364
382
 
365
383
  def make_logsouce_string(source: dict):
366
384
  d = [
File without changes
File without changes
File without changes