ocinferno 0.5.5__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.
Files changed (105) hide show
  1. ocinferno-0.5.5/LICENSE +28 -0
  2. ocinferno-0.5.5/PKG-INFO +273 -0
  3. ocinferno-0.5.5/README.md +247 -0
  4. ocinferno-0.5.5/cli/main.py +140 -0
  5. ocinferno-0.5.5/cli/module_actions.py +784 -0
  6. ocinferno-0.5.5/cli/workspace_instructions.py +1790 -0
  7. ocinferno-0.5.5/core/api_logger.py +297 -0
  8. ocinferno-0.5.5/core/config.py +105 -0
  9. ocinferno-0.5.5/core/console.py +643 -0
  10. ocinferno-0.5.5/core/contracts.py +75 -0
  11. ocinferno-0.5.5/core/db.py +1175 -0
  12. ocinferno-0.5.5/core/http_policy.py +97 -0
  13. ocinferno-0.5.5/core/session.py +3198 -0
  14. ocinferno-0.5.5/core/utils/module_helpers.py +1419 -0
  15. ocinferno-0.5.5/core/utils/selection_helpers.py +196 -0
  16. ocinferno-0.5.5/core/utils/service_runtime.py +341 -0
  17. ocinferno-0.5.5/modules/apigateway/enumeration/enum_apigateway.py +301 -0
  18. ocinferno-0.5.5/modules/apigateway/utilities/helpers.py +495 -0
  19. ocinferno-0.5.5/modules/artifactregistry/enumeration/enum_artifactregistry.py +186 -0
  20. ocinferno-0.5.5/modules/artifactregistry/utilities/helpers.py +217 -0
  21. ocinferno-0.5.5/modules/blockchain/enumeration/enum_blockchain.py +329 -0
  22. ocinferno-0.5.5/modules/blockchain/utilities/helpers.py +174 -0
  23. ocinferno-0.5.5/modules/cloudguard/enumeration/enum_cloudguard.py +136 -0
  24. ocinferno-0.5.5/modules/cloudguard/utilities/helpers.py +291 -0
  25. ocinferno-0.5.5/modules/containerinstances/enumeration/enum_container_instances.py +91 -0
  26. ocinferno-0.5.5/modules/containerinstances/utilities/helpers.py +48 -0
  27. ocinferno-0.5.5/modules/containerregistry/enumeration/enum_containerregistry.py +162 -0
  28. ocinferno-0.5.5/modules/containerregistry/utilities/helpers.py +81 -0
  29. ocinferno-0.5.5/modules/core/enumeration/enum_core_block_storage.py +120 -0
  30. ocinferno-0.5.5/modules/core/enumeration/enum_core_compute.py +890 -0
  31. ocinferno-0.5.5/modules/core/enumeration/enum_core_network.py +163 -0
  32. ocinferno-0.5.5/modules/core/utilities/blockstorage_helpers.py +173 -0
  33. ocinferno-0.5.5/modules/core/utilities/compute_helpers.py +1111 -0
  34. ocinferno-0.5.5/modules/core/utilities/compute_management_helpers.py +104 -0
  35. ocinferno-0.5.5/modules/core/utilities/virtual_network_helpers.py +445 -0
  36. ocinferno-0.5.5/modules/dataflow/enumeration/enum_dataflow.py +117 -0
  37. ocinferno-0.5.5/modules/dataflow/utilities/helpers.py +154 -0
  38. ocinferno-0.5.5/modules/datascience/enumeration/enum_datascience.py +149 -0
  39. ocinferno-0.5.5/modules/datascience/utilities/helpers.py +333 -0
  40. ocinferno-0.5.5/modules/desktops/enumeration/enum_desktops.py +167 -0
  41. ocinferno-0.5.5/modules/desktops/utilities/helpers.py +218 -0
  42. ocinferno-0.5.5/modules/devops/enumeration/enum_devops.py +198 -0
  43. ocinferno-0.5.5/modules/devops/utilities/helpers.py +166 -0
  44. ocinferno-0.5.5/modules/dns/enumeration/enum_dns.py +159 -0
  45. ocinferno-0.5.5/modules/dns/utilities/helpers.py +119 -0
  46. ocinferno-0.5.5/modules/email/enumeration/enum_email.py +186 -0
  47. ocinferno-0.5.5/modules/email/utilities/helpers.py +250 -0
  48. ocinferno-0.5.5/modules/everything/enumeration/enum_all.py +1002 -0
  49. ocinferno-0.5.5/modules/everything/enumeration/enum_config_check.py +78 -0
  50. ocinferno-0.5.5/modules/everything/utilities/config_audit.py +3475 -0
  51. ocinferno-0.5.5/modules/everything/utilities/enum_all_summary.py +352 -0
  52. ocinferno-0.5.5/modules/filestorage/enumeration/enum_filestorage.py +259 -0
  53. ocinferno-0.5.5/modules/filestorage/utilities/helpers.py +369 -0
  54. ocinferno-0.5.5/modules/functions/enumeration/enum_functions.py +149 -0
  55. ocinferno-0.5.5/modules/functions/utilities/helpers.py +88 -0
  56. ocinferno-0.5.5/modules/identityclient/enumeration/enum_comp.py +199 -0
  57. ocinferno-0.5.5/modules/identityclient/enumeration/enum_identity.py +88 -0
  58. ocinferno-0.5.5/modules/identityclient/utilities/helpers.py +2466 -0
  59. ocinferno-0.5.5/modules/iot/enumeration/enum_iot.py +139 -0
  60. ocinferno-0.5.5/modules/iot/utilities/helpers.py +262 -0
  61. ocinferno-0.5.5/modules/kubernetes/enumeration/enum_kubernetes.py +199 -0
  62. ocinferno-0.5.5/modules/kubernetes/utilities/helpers.py +137 -0
  63. ocinferno-0.5.5/modules/logging/enumeration/enum_logs.py +187 -0
  64. ocinferno-0.5.5/modules/logging/utilities/helpers.py +74 -0
  65. ocinferno-0.5.5/modules/managedkafka/enumeration/enum_managedkafka.py +193 -0
  66. ocinferno-0.5.5/modules/managedkafka/utilities/helpers.py +152 -0
  67. ocinferno-0.5.5/modules/networkfirewall/enumeration/enum_networkfirewall.py +185 -0
  68. ocinferno-0.5.5/modules/networkfirewall/utilities/helpers.py +149 -0
  69. ocinferno-0.5.5/modules/networkloadbalancer/enumeration/enum_network_load_balancers.py +84 -0
  70. ocinferno-0.5.5/modules/networkloadbalancer/utilities/helpers.py +53 -0
  71. ocinferno-0.5.5/modules/notifications/enumeration/enum_notifications.py +123 -0
  72. ocinferno-0.5.5/modules/notifications/utilities/helpers.py +91 -0
  73. ocinferno-0.5.5/modules/objectstorage/enumeration/enum_objectstorage.py +241 -0
  74. ocinferno-0.5.5/modules/objectstorage/utilities/helpers.py +455 -0
  75. ocinferno-0.5.5/modules/opengraph/enumeration/enum_oracle_cloud_hound_data.py +866 -0
  76. ocinferno-0.5.5/modules/opengraph/utilities/dynamic_group_membership_graph_builder.py +206 -0
  77. ocinferno-0.5.5/modules/opengraph/utilities/group_membership_graph_builder.py +278 -0
  78. ocinferno-0.5.5/modules/opengraph/utilities/helpers/__init__.py +61 -0
  79. ocinferno-0.5.5/modules/opengraph/utilities/helpers/constants.py +391 -0
  80. ocinferno-0.5.5/modules/opengraph/utilities/helpers/context.py +1114 -0
  81. ocinferno-0.5.5/modules/opengraph/utilities/helpers/core_helpers.py +398 -0
  82. ocinferno-0.5.5/modules/opengraph/utilities/helpers/graph_utils.py +533 -0
  83. ocinferno-0.5.5/modules/opengraph/utilities/helpers/iam_conditionals.py +4801 -0
  84. ocinferno-0.5.5/modules/opengraph/utilities/helpers/matching_rules_engine.py +792 -0
  85. ocinferno-0.5.5/modules/opengraph/utilities/helpers/policy_parser_enrichment.py +157 -0
  86. ocinferno-0.5.5/modules/opengraph/utilities/iam_policy_advanced_relation_graph_builder.py +2707 -0
  87. ocinferno-0.5.5/modules/opengraph/utilities/iam_policy_base_relation_graph_builder.py +2342 -0
  88. ocinferno-0.5.5/modules/opengraph/utilities/identity_domain_graph_builder.py +1948 -0
  89. ocinferno-0.5.5/modules/opengraph/utilities/resource_scope_graph_builder.py +1056 -0
  90. ocinferno-0.5.5/modules/resourcemanager/enumeration/enum_resourcemanager.py +181 -0
  91. ocinferno-0.5.5/modules/resourcemanager/utilities/helpers.py +354 -0
  92. ocinferno-0.5.5/modules/resourcescheduler/enumeration/enum_resource_schedules.py +94 -0
  93. ocinferno-0.5.5/modules/resourcescheduler/utilities/helpers.py +49 -0
  94. ocinferno-0.5.5/modules/tagging/enumeration/enum_tagging.py +154 -0
  95. ocinferno-0.5.5/modules/tagging/utilities/helpers.py +169 -0
  96. ocinferno-0.5.5/modules/vault/enumeration/enum_vault.py +273 -0
  97. ocinferno-0.5.5/modules/vault/utilities/helpers.py +929 -0
  98. ocinferno-0.5.5/ocinferno.egg-info/PKG-INFO +273 -0
  99. ocinferno-0.5.5/ocinferno.egg-info/SOURCES.txt +103 -0
  100. ocinferno-0.5.5/ocinferno.egg-info/dependency_links.txt +1 -0
  101. ocinferno-0.5.5/ocinferno.egg-info/entry_points.txt +2 -0
  102. ocinferno-0.5.5/ocinferno.egg-info/requires.txt +15 -0
  103. ocinferno-0.5.5/ocinferno.egg-info/top_level.txt +3 -0
  104. ocinferno-0.5.5/pyproject.toml +49 -0
  105. ocinferno-0.5.5/setup.cfg +4 -0
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, NetSPI
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,273 @@
1
+ Metadata-Version: 2.4
2
+ Name: ocinferno
3
+ Version: 0.5.5
4
+ Summary: OCI offensive security assessment and enumeration framework.
5
+ Author: NetSPI
6
+ License-Expression: BSD-3-Clause
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3 :: Only
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Intended Audience :: Information Technology
11
+ Classifier: Topic :: Security
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: PyYAML==6.0.3
16
+ Requires-Dist: oci==2.169.0
17
+ Requires-Dist: requests==2.33.1
18
+ Requires-Dist: prettytable==3.17.0
19
+ Requires-Dist: oci-lexer-parser==0.1.2
20
+ Requires-Dist: pandas==2.3.3; python_version < "3.11"
21
+ Requires-Dist: pandas==3.0.1; python_version >= "3.11"
22
+ Requires-Dist: xlsxwriter==3.2.9
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=8.0; extra == "dev"
25
+ Dynamic: license-file
26
+
27
+ # OCInferno
28
+
29
+ [![Unit Tests](https://img.shields.io/github/actions/workflow/status/NetSPI/OCInferno/ci.yml?branch=main&label=unit%20tests)](https://github.com/NetSPI/OCInferno/actions/workflows/ci.yml)
30
+ [![PyPI](https://img.shields.io/pypi/v/ocinferno)](https://pypi.org/project/ocinferno/)
31
+ [![Python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue)](https://www.python.org/)
32
+ [![License](https://img.shields.io/badge/license-BSD--3--Clause-blue.svg)](./LICENSE)
33
+ [![Contributions Welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg)](./CONTRIBUTING.md)
34
+ [![OCI SDK](https://img.shields.io/badge/oci--sdk-2.169.0-orange)](https://pypi.org/project/oci/)
35
+
36
+ ## Overview
37
+
38
+ > In the spirit of transparency: parts of this project and documentation were developed with LLM coding assistance. Review code and behavior in your environment before operational use. Notably, the enum_config_checks module is still in-progress based off original content generated.
39
+
40
+ OCInferno (O-C-Inferno) is an OCI offensive security assessment framework for workspace-driven credential handling, service enumeration, artifact download, and graph-based attack-path analysis. It includes a module to generate a custom **OpenGraph output** which can be fed into BloodHound, as shown below, to map privilege-escalation paths.
41
+
42
+ <p align="center" style="margin: 0.35em 0 0 0;">
43
+ <img src="./images/README_OVERVIEW.png" alt="Sample OpenGraph output in BloodHound" />
44
+ </p>
45
+ <p align="center" style="margin: 0.15em 0 1em 0;"><em>Figure 1. Example OpenGraph/BloodHound relationship view.</em></p>
46
+
47
+ <p align="center" style="margin: 0.35em 0 0 0;">
48
+ <img src="./images/MODULE_RUN.png" alt="OCInferno terminal output example" />
49
+ </p>
50
+ <p align="center" style="margin: 0.15em 0 1em 0;"><em>Figure 2. Example module output in the CLI.</em></p>
51
+
52
+ ## High-Level Features
53
+
54
+ - **CLI UX:** Interactive CLI with command and argument tab auto-complete and history.
55
+ - **Authentication:** Multiple supported auth methods:
56
+ - **Config Profile:** API key-backed and session-token-backed OCI profiles.
57
+ - **Instance Principal:** Compute-instance identity flow.
58
+ - **Resource Principal:** Runtime/workload identity flow.
59
+ - **Module Model:** Service-specific modules across OCI services, with proxy and rate-limiting support.
60
+ - **Mass Enumeration:** Broad OCI module coverage with `enum_all` orchestration support (implemented in `modules/everything`).
61
+ - **Config Audits:** `enum_config_check` findings based on enumerated/saved data (implemented in `modules/everything`).
62
+ - **Reporting Exports:** Resource export support for HTML, CSV, JSON, Excel, and graph image outputs.
63
+ - **Artifact Downloads:** Download support across many modules with `--download` and selective routing.
64
+ - **OpenGraph / BloodHound:** OpenGraph export for BloodHound ingestion, including:
65
+ - A default focused view of high-impact edges, with `--include-all` available for broader relationship output to ideally see all relationships regardless of priv escalation weight.
66
+ - Privilege-escalation path modeling across OCI IAM and Identity Domain app-role/grant relationships.
67
+ - Inheritance-aware modeling (`--expand-inherited`) and conditional evaluation (`--cond-eval`) to improve graph accuracy.
68
+
69
+ ## Documentation
70
+
71
+ Documentation is maintained on the GitHub Wiki:
72
+
73
+ - https://github.com/NetSPI/OCInferno/wiki
74
+
75
+ Contributing guidance:
76
+
77
+ - `CONTRIBUTING.md`
78
+
79
+ Roadmap guidance:
80
+
81
+ - `ROADMAP.md`
82
+
83
+ Sample OpenGraph JSON:
84
+
85
+ - `opengraph_examples/example_input.json`
86
+
87
+ ## Installation TLDR
88
+
89
+ ```bash
90
+ git clone https://github.com/NetSPI/OCInferno.git
91
+
92
+ # You don't need pytests to run the tool
93
+ rm -r tests/
94
+
95
+ virtualenv .venv
96
+ source .venv/bin/activate
97
+ pip install -r requirements.txt
98
+ python -m cli.main
99
+ ```
100
+
101
+ ## Launch and Data Download TLDR
102
+
103
+ Run the tool:
104
+
105
+ ```bash
106
+ python -m cli.main
107
+ ```
108
+
109
+ At startup:
110
+
111
+ 1. Create/select a workspace.
112
+ 2. Add credentials using your OCI config profile (example uses `MY_PROFILE` to add an API key):
113
+ ```text
114
+ profile MY_PROFILE --filepath ~/.oci/config --profile MY_PROFILE
115
+ ```
116
+ 3. Start the first full run.
117
+ `enum_all` runs all modules. `--comp` recursively enumerates compartments to maximize coverage.
118
+ `--get` follows LIST calls with GET calls where supported for deeper detail.
119
+ `--download` downloads data where possible, choosing `--not-downloads buckets` attempts to download all content **except** bucket object content.
120
+ ```bash
121
+ # Download as much as you can
122
+ modules run enum_all --get --comp --download
123
+ # Download everything EXCEPT bucket contents
124
+ modules run enum_all --get --comp [--not-downloads buckets]
125
+ ```
126
+ 4. Export a compartment tree image and Excel data output to quickly review what your current permissions can see:
127
+ ```bash
128
+ (TENANT-2snxfsaa:TEST)> data export treeimage
129
+ [*] Compartment tree export complete -> ./ocinferno_output/1_TEST/exports/data/global/resource_reports/compartment_tree.svg (format=svg, renderer=svg-interactive, compartments=6)
130
+
131
+ (TENANT-2snxfsaa:TEST)> data export excel
132
+ [*] Excel export complete -> ./ocinferno_output/1_TEST/exports/data/global/sqlite_excel/sqlite_blob.xlsx (format=xlsx, databases=1, tables=173, rows=54951, single_sheet=True, condensed=True)
133
+
134
+ ```
135
+
136
+ <p align="center" style="margin: 0.35em 0 0 0;">
137
+ <img src="./images/COMP_HIERARCHY_1.png" alt="Compartment Tree Export" />
138
+ </p>
139
+ <p align="center" style="margin: 0.15em 0 1em 0;"><em>Figure 1. Tree image export from <code>data export treeimage</code>.</em></p>
140
+
141
+ <p align="center" style="margin: 0.35em 0 0 0;">
142
+ <img src="./images/DATA_EXPORT_1.png" alt="Excel Data Export" />
143
+ </p>
144
+ <p align="center" style="margin: 0.15em 0 1em 0;"><em>Figure 2. Excel export output from <code>data export excel</code>.</em></p>
145
+
146
+ ## OpenGraph TLDR
147
+
148
+ ### Where JSON is saved
149
+
150
+ Once all data is collected, use the `enum_oracle_cloud_hound_data` module as seen in example below:
151
+ ```bash
152
+ modules run enum_oracle_cloud_hound_data [--include-all] [--expand-inherited] [--cond-eval] --reset --out opengraph_output.json
153
+ ```
154
+ Optional OpenGraph flags:
155
+
156
+ - `[--include-all]`: include broader non-default relationship output, not just default high-impact allowlist-focused edges.
157
+ - `[--expand-inherited]`: expand inherited IAM scope/location relationships.
158
+ - `[--cond-eval]`: evaluate IAM statement conditions (when resolvable) to improve edge accuracy.
159
+ - `[--reset]`: Wipes Opengraph database before creating JSON. Advised to always run this if you want a fresh generation each time else there might be legacy content from past runs.
160
+
161
+ ### Import into BloodHound
162
+
163
+ 1. Open BloodHound CE. Installation instructions can be found [here](https://bloodhound.specterops.io/get-started/quickstart/community-edition-quickstart)
164
+ 2. Go to data import.
165
+ 3. Upload `oracle_cloud_hound.json`.
166
+ 4. Run path queries against high-impact OCI edges.
167
+
168
+ <p align="center" style="margin: 0.35em 0 0 0;">
169
+ <img src="./images/BLOODHOUND_UPLOAD.png" alt="BloodHound upload workflow for oracle_cloud_hound.json" />
170
+ </p>
171
+ <p align="center" style="margin: 0.15em 0 1em 0;"><em>Figure 3. Uploading OpenGraph JSON into BloodHound CE.</em></p>
172
+
173
+ ### Sample Cypher Queries
174
+
175
+ ```cypher
176
+ // 0) See All nodes and edges
177
+ MATCH (n)
178
+ OPTIONAL MATCH (n)-[r]-(m)
179
+ RETURN n, r, m
180
+
181
+ // 1) Find all users not in any group
182
+ MATCH (u:OCIUser)
183
+ WHERE NOT (u)-[:OCI_GROUP_MEMBER]->(:OCIGroup)
184
+ RETURN u
185
+ ORDER BY coalesce(u.name, u.id);
186
+
187
+ // 2a) Find standard groups with no members
188
+ MATCH (g:OCIGroup)
189
+ WHERE NOT (:OCIUser)-[:OCI_GROUP_MEMBER]->(g)
190
+ RETURN g
191
+ ORDER BY coalesce(g.name, g.id);
192
+
193
+ // 2b) Find dynamic groups with no matched members
194
+ MATCH (dg:OCIDynamicGroup)
195
+ WHERE NOT ()-[:OCI_DYNAMIC_GROUP_MEMBER]->(dg)
196
+ RETURN dg
197
+ ORDER BY coalesce(dg.name, dg.id);
198
+
199
+ // 3) Find all paths from all principals to all-resources scopes (depth 1..6)
200
+ MATCH (p0)
201
+ WHERE p0:OCIUser OR p0:OCIGroup OR p0:OCIDynamicGroup
202
+ MATCH p = (p0)-[*1..6]->(r:OCIAllResources)
203
+ RETURN p
204
+ LIMIT 500;
205
+
206
+ // 4) Find all paths to all-resources scopes regardless of start node type (depth 1..6)
207
+ MATCH p = (s)-[*1..6]->(r:OCIAllResources)
208
+ RETURN p
209
+ LIMIT 500;
210
+ ```
211
+
212
+ ### Add a custom allowlist edge (TLDR)
213
+
214
+ If you want to add your own default OpenGraph edge (for example tie `GROUP_INSPECT` to an edge), add a rule in:
215
+
216
+ - `modules/opengraph/utilities/helpers/data/static_constants.json` under `ALLOW_RULE_DEFS`
217
+
218
+ Example:
219
+
220
+ ```json
221
+ {
222
+ "id": "GROUP_INSPECT",
223
+ "match": {
224
+ "resource_tokens": ["groups"],
225
+ "permissions_all": ["GROUP_INSPECT"]
226
+ },
227
+ "edge": {
228
+ "label": "OCI_GROUP_INSPECT",
229
+ "description": "Inspect IAM groups in scope."
230
+ },
231
+ "destination": {
232
+ "token": "groups",
233
+ "node_type": "OCIResourceGroup",
234
+ "allow_specific": true
235
+ }
236
+ }
237
+ ```
238
+
239
+ Field quick reference:
240
+
241
+ - `id`: internal rule identifier used by the builder/tests.
242
+ - `match.resource_tokens`: OCI policy resource token(s) the statement must target (for example `groups`).
243
+ - `match.permissions_all`: permission(s) that must all be present in the same statement to trigger the edge.
244
+ - `edge.label`: relationship kind written into OpenGraph.
245
+ - `edge.description`: human-readable explanation stored on the edge.
246
+ - `destination.token`: logical destination scope/resource token represented in the graph.
247
+ - `destination.node_type`: node class to emit for the destination (commonly `OCIResourceGroup`).
248
+ - `destination.allow_specific`: when `true`, conditionals can resolve to specific resources (for example a specific group) instead of only generic scope nodes.
249
+
250
+ Then rerun `enum_oracle_cloud_hound_data` and update tests/golden outputs if behavior changed.
251
+
252
+ ## Dependency Inventory
253
+
254
+ | Dependency | Where Used | Purpose |
255
+ | --- | --- | --- |
256
+ | `oci==2.169.0` | Core modules | OCI SDK clients/auth/providers for enumeration and actions. |
257
+ | `requests==2.33.1` | HTTP helpers/integrations | HTTP operations and API helper requests. |
258
+ | `PyYAML>=6.0.3` | Config/parsing layers | YAML config and mapping parsing. |
259
+ | `prettytable==3.17.0` | CLI output | Terminal table rendering. |
260
+ | `oci-lexer-parser==0.1.2` | Policy/OpenGraph logic | OCI IAM policy lexing/parsing support. |
261
+ | `pandas>=2.2.0` | Data export | Excel export pipeline. |
262
+ | `xlsxwriter>=3.2.0` | Data export | `.xlsx` writer engine for exports. |
263
+ | `pytest>=8.0`* | Unit tests | Test framework (`tests/unit`). |
264
+
265
+ *Dev/test-scoped dependency.
266
+
267
+ ## Repository Layout
268
+
269
+ - `cli/`: interactive command processor and startup flow.
270
+ - `core/`: session, config, data, utility, and logging controllers.
271
+ - `modules/`: service modules plus `Everything` (`enum_all`, `enum_config_check`).
272
+ - `utils/`: shared module utilities and exports.
273
+ - `tests/`: unit tests run in CI.
@@ -0,0 +1,247 @@
1
+ # OCInferno
2
+
3
+ [![Unit Tests](https://img.shields.io/github/actions/workflow/status/NetSPI/OCInferno/ci.yml?branch=main&label=unit%20tests)](https://github.com/NetSPI/OCInferno/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/ocinferno)](https://pypi.org/project/ocinferno/)
5
+ [![Python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue)](https://www.python.org/)
6
+ [![License](https://img.shields.io/badge/license-BSD--3--Clause-blue.svg)](./LICENSE)
7
+ [![Contributions Welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg)](./CONTRIBUTING.md)
8
+ [![OCI SDK](https://img.shields.io/badge/oci--sdk-2.169.0-orange)](https://pypi.org/project/oci/)
9
+
10
+ ## Overview
11
+
12
+ > In the spirit of transparency: parts of this project and documentation were developed with LLM coding assistance. Review code and behavior in your environment before operational use. Notably, the enum_config_checks module is still in-progress based off original content generated.
13
+
14
+ OCInferno (O-C-Inferno) is an OCI offensive security assessment framework for workspace-driven credential handling, service enumeration, artifact download, and graph-based attack-path analysis. It includes a module to generate a custom **OpenGraph output** which can be fed into BloodHound, as shown below, to map privilege-escalation paths.
15
+
16
+ <p align="center" style="margin: 0.35em 0 0 0;">
17
+ <img src="./images/README_OVERVIEW.png" alt="Sample OpenGraph output in BloodHound" />
18
+ </p>
19
+ <p align="center" style="margin: 0.15em 0 1em 0;"><em>Figure 1. Example OpenGraph/BloodHound relationship view.</em></p>
20
+
21
+ <p align="center" style="margin: 0.35em 0 0 0;">
22
+ <img src="./images/MODULE_RUN.png" alt="OCInferno terminal output example" />
23
+ </p>
24
+ <p align="center" style="margin: 0.15em 0 1em 0;"><em>Figure 2. Example module output in the CLI.</em></p>
25
+
26
+ ## High-Level Features
27
+
28
+ - **CLI UX:** Interactive CLI with command and argument tab auto-complete and history.
29
+ - **Authentication:** Multiple supported auth methods:
30
+ - **Config Profile:** API key-backed and session-token-backed OCI profiles.
31
+ - **Instance Principal:** Compute-instance identity flow.
32
+ - **Resource Principal:** Runtime/workload identity flow.
33
+ - **Module Model:** Service-specific modules across OCI services, with proxy and rate-limiting support.
34
+ - **Mass Enumeration:** Broad OCI module coverage with `enum_all` orchestration support (implemented in `modules/everything`).
35
+ - **Config Audits:** `enum_config_check` findings based on enumerated/saved data (implemented in `modules/everything`).
36
+ - **Reporting Exports:** Resource export support for HTML, CSV, JSON, Excel, and graph image outputs.
37
+ - **Artifact Downloads:** Download support across many modules with `--download` and selective routing.
38
+ - **OpenGraph / BloodHound:** OpenGraph export for BloodHound ingestion, including:
39
+ - A default focused view of high-impact edges, with `--include-all` available for broader relationship output to ideally see all relationships regardless of priv escalation weight.
40
+ - Privilege-escalation path modeling across OCI IAM and Identity Domain app-role/grant relationships.
41
+ - Inheritance-aware modeling (`--expand-inherited`) and conditional evaluation (`--cond-eval`) to improve graph accuracy.
42
+
43
+ ## Documentation
44
+
45
+ Documentation is maintained on the GitHub Wiki:
46
+
47
+ - https://github.com/NetSPI/OCInferno/wiki
48
+
49
+ Contributing guidance:
50
+
51
+ - `CONTRIBUTING.md`
52
+
53
+ Roadmap guidance:
54
+
55
+ - `ROADMAP.md`
56
+
57
+ Sample OpenGraph JSON:
58
+
59
+ - `opengraph_examples/example_input.json`
60
+
61
+ ## Installation TLDR
62
+
63
+ ```bash
64
+ git clone https://github.com/NetSPI/OCInferno.git
65
+
66
+ # You don't need pytests to run the tool
67
+ rm -r tests/
68
+
69
+ virtualenv .venv
70
+ source .venv/bin/activate
71
+ pip install -r requirements.txt
72
+ python -m cli.main
73
+ ```
74
+
75
+ ## Launch and Data Download TLDR
76
+
77
+ Run the tool:
78
+
79
+ ```bash
80
+ python -m cli.main
81
+ ```
82
+
83
+ At startup:
84
+
85
+ 1. Create/select a workspace.
86
+ 2. Add credentials using your OCI config profile (example uses `MY_PROFILE` to add an API key):
87
+ ```text
88
+ profile MY_PROFILE --filepath ~/.oci/config --profile MY_PROFILE
89
+ ```
90
+ 3. Start the first full run.
91
+ `enum_all` runs all modules. `--comp` recursively enumerates compartments to maximize coverage.
92
+ `--get` follows LIST calls with GET calls where supported for deeper detail.
93
+ `--download` downloads data where possible, choosing `--not-downloads buckets` attempts to download all content **except** bucket object content.
94
+ ```bash
95
+ # Download as much as you can
96
+ modules run enum_all --get --comp --download
97
+ # Download everything EXCEPT bucket contents
98
+ modules run enum_all --get --comp [--not-downloads buckets]
99
+ ```
100
+ 4. Export a compartment tree image and Excel data output to quickly review what your current permissions can see:
101
+ ```bash
102
+ (TENANT-2snxfsaa:TEST)> data export treeimage
103
+ [*] Compartment tree export complete -> ./ocinferno_output/1_TEST/exports/data/global/resource_reports/compartment_tree.svg (format=svg, renderer=svg-interactive, compartments=6)
104
+
105
+ (TENANT-2snxfsaa:TEST)> data export excel
106
+ [*] Excel export complete -> ./ocinferno_output/1_TEST/exports/data/global/sqlite_excel/sqlite_blob.xlsx (format=xlsx, databases=1, tables=173, rows=54951, single_sheet=True, condensed=True)
107
+
108
+ ```
109
+
110
+ <p align="center" style="margin: 0.35em 0 0 0;">
111
+ <img src="./images/COMP_HIERARCHY_1.png" alt="Compartment Tree Export" />
112
+ </p>
113
+ <p align="center" style="margin: 0.15em 0 1em 0;"><em>Figure 1. Tree image export from <code>data export treeimage</code>.</em></p>
114
+
115
+ <p align="center" style="margin: 0.35em 0 0 0;">
116
+ <img src="./images/DATA_EXPORT_1.png" alt="Excel Data Export" />
117
+ </p>
118
+ <p align="center" style="margin: 0.15em 0 1em 0;"><em>Figure 2. Excel export output from <code>data export excel</code>.</em></p>
119
+
120
+ ## OpenGraph TLDR
121
+
122
+ ### Where JSON is saved
123
+
124
+ Once all data is collected, use the `enum_oracle_cloud_hound_data` module as seen in example below:
125
+ ```bash
126
+ modules run enum_oracle_cloud_hound_data [--include-all] [--expand-inherited] [--cond-eval] --reset --out opengraph_output.json
127
+ ```
128
+ Optional OpenGraph flags:
129
+
130
+ - `[--include-all]`: include broader non-default relationship output, not just default high-impact allowlist-focused edges.
131
+ - `[--expand-inherited]`: expand inherited IAM scope/location relationships.
132
+ - `[--cond-eval]`: evaluate IAM statement conditions (when resolvable) to improve edge accuracy.
133
+ - `[--reset]`: Wipes Opengraph database before creating JSON. Advised to always run this if you want a fresh generation each time else there might be legacy content from past runs.
134
+
135
+ ### Import into BloodHound
136
+
137
+ 1. Open BloodHound CE. Installation instructions can be found [here](https://bloodhound.specterops.io/get-started/quickstart/community-edition-quickstart)
138
+ 2. Go to data import.
139
+ 3. Upload `oracle_cloud_hound.json`.
140
+ 4. Run path queries against high-impact OCI edges.
141
+
142
+ <p align="center" style="margin: 0.35em 0 0 0;">
143
+ <img src="./images/BLOODHOUND_UPLOAD.png" alt="BloodHound upload workflow for oracle_cloud_hound.json" />
144
+ </p>
145
+ <p align="center" style="margin: 0.15em 0 1em 0;"><em>Figure 3. Uploading OpenGraph JSON into BloodHound CE.</em></p>
146
+
147
+ ### Sample Cypher Queries
148
+
149
+ ```cypher
150
+ // 0) See All nodes and edges
151
+ MATCH (n)
152
+ OPTIONAL MATCH (n)-[r]-(m)
153
+ RETURN n, r, m
154
+
155
+ // 1) Find all users not in any group
156
+ MATCH (u:OCIUser)
157
+ WHERE NOT (u)-[:OCI_GROUP_MEMBER]->(:OCIGroup)
158
+ RETURN u
159
+ ORDER BY coalesce(u.name, u.id);
160
+
161
+ // 2a) Find standard groups with no members
162
+ MATCH (g:OCIGroup)
163
+ WHERE NOT (:OCIUser)-[:OCI_GROUP_MEMBER]->(g)
164
+ RETURN g
165
+ ORDER BY coalesce(g.name, g.id);
166
+
167
+ // 2b) Find dynamic groups with no matched members
168
+ MATCH (dg:OCIDynamicGroup)
169
+ WHERE NOT ()-[:OCI_DYNAMIC_GROUP_MEMBER]->(dg)
170
+ RETURN dg
171
+ ORDER BY coalesce(dg.name, dg.id);
172
+
173
+ // 3) Find all paths from all principals to all-resources scopes (depth 1..6)
174
+ MATCH (p0)
175
+ WHERE p0:OCIUser OR p0:OCIGroup OR p0:OCIDynamicGroup
176
+ MATCH p = (p0)-[*1..6]->(r:OCIAllResources)
177
+ RETURN p
178
+ LIMIT 500;
179
+
180
+ // 4) Find all paths to all-resources scopes regardless of start node type (depth 1..6)
181
+ MATCH p = (s)-[*1..6]->(r:OCIAllResources)
182
+ RETURN p
183
+ LIMIT 500;
184
+ ```
185
+
186
+ ### Add a custom allowlist edge (TLDR)
187
+
188
+ If you want to add your own default OpenGraph edge (for example tie `GROUP_INSPECT` to an edge), add a rule in:
189
+
190
+ - `modules/opengraph/utilities/helpers/data/static_constants.json` under `ALLOW_RULE_DEFS`
191
+
192
+ Example:
193
+
194
+ ```json
195
+ {
196
+ "id": "GROUP_INSPECT",
197
+ "match": {
198
+ "resource_tokens": ["groups"],
199
+ "permissions_all": ["GROUP_INSPECT"]
200
+ },
201
+ "edge": {
202
+ "label": "OCI_GROUP_INSPECT",
203
+ "description": "Inspect IAM groups in scope."
204
+ },
205
+ "destination": {
206
+ "token": "groups",
207
+ "node_type": "OCIResourceGroup",
208
+ "allow_specific": true
209
+ }
210
+ }
211
+ ```
212
+
213
+ Field quick reference:
214
+
215
+ - `id`: internal rule identifier used by the builder/tests.
216
+ - `match.resource_tokens`: OCI policy resource token(s) the statement must target (for example `groups`).
217
+ - `match.permissions_all`: permission(s) that must all be present in the same statement to trigger the edge.
218
+ - `edge.label`: relationship kind written into OpenGraph.
219
+ - `edge.description`: human-readable explanation stored on the edge.
220
+ - `destination.token`: logical destination scope/resource token represented in the graph.
221
+ - `destination.node_type`: node class to emit for the destination (commonly `OCIResourceGroup`).
222
+ - `destination.allow_specific`: when `true`, conditionals can resolve to specific resources (for example a specific group) instead of only generic scope nodes.
223
+
224
+ Then rerun `enum_oracle_cloud_hound_data` and update tests/golden outputs if behavior changed.
225
+
226
+ ## Dependency Inventory
227
+
228
+ | Dependency | Where Used | Purpose |
229
+ | --- | --- | --- |
230
+ | `oci==2.169.0` | Core modules | OCI SDK clients/auth/providers for enumeration and actions. |
231
+ | `requests==2.33.1` | HTTP helpers/integrations | HTTP operations and API helper requests. |
232
+ | `PyYAML>=6.0.3` | Config/parsing layers | YAML config and mapping parsing. |
233
+ | `prettytable==3.17.0` | CLI output | Terminal table rendering. |
234
+ | `oci-lexer-parser==0.1.2` | Policy/OpenGraph logic | OCI IAM policy lexing/parsing support. |
235
+ | `pandas>=2.2.0` | Data export | Excel export pipeline. |
236
+ | `xlsxwriter>=3.2.0` | Data export | `.xlsx` writer engine for exports. |
237
+ | `pytest>=8.0`* | Unit tests | Test framework (`tests/unit`). |
238
+
239
+ *Dev/test-scoped dependency.
240
+
241
+ ## Repository Layout
242
+
243
+ - `cli/`: interactive command processor and startup flow.
244
+ - `core/`: session, config, data, utility, and logging controllers.
245
+ - `modules/`: service modules plus `Everything` (`enum_all`, `enum_config_check`).
246
+ - `utils/`: shared module utilities and exports.
247
+ - `tests/`: unit tests run in CI.
@@ -0,0 +1,140 @@
1
+ import argparse
2
+ from typing import List, Tuple, Optional
3
+ import json
4
+
5
+ from cli.workspace_instructions import workspace_instructions
6
+ from core.db import DataController
7
+ from core.console import UtilityTools
8
+
9
+ def create_workspace(dc: DataController, workspace_name: str) -> Optional[int]:
10
+ workspace_name = (workspace_name or "").strip()
11
+ if workspace_name.isdigit():
12
+ print(
13
+ f"{UtilityTools.RED}{UtilityTools.BOLD}[X] Workspace name cannot be numeric-only."
14
+ f" Use a descriptive name (for example: TEST, PROD, LAB).{UtilityTools.RESET}"
15
+ )
16
+ return None
17
+
18
+ existing_names = dc.fetch_all_workspace_names()
19
+ if workspace_name in existing_names:
20
+ print(f"{UtilityTools.RED}{UtilityTools.BOLD}[X] A workspace with that name already exists.{UtilityTools.RESET}")
21
+ return None
22
+
23
+ starting_config_data = {
24
+ "proxy": None,
25
+ "current_default_region": "",
26
+ "module_auto_save": True,
27
+ }
28
+
29
+ starting_config_data_json_blob = json.dumps(starting_config_data)
30
+
31
+ workspace_id = dc.insert_workspace(workspace_name, starting_config_data_json_blob)
32
+ if workspace_id:
33
+ print(f"{UtilityTools.GREEN}{UtilityTools.BOLD}[*] Workspace '{workspace_name}' created.{UtilityTools.RESET}")
34
+ return workspace_id
35
+
36
+ print(f"{UtilityTools.RED}{UtilityTools.BOLD}[X] Failed to create workspace.{UtilityTools.RESET}")
37
+ return None
38
+
39
+ def prompt_new_workspace(dc: DataController) -> Tuple[str, int]:
40
+ while True:
41
+ name = input("> New workspace name: ").strip()
42
+ if 1 <= len(name) <= 80:
43
+ workspace_id = create_workspace(dc, name)
44
+ if workspace_id:
45
+ return name, workspace_id
46
+ else:
47
+ print(f"{UtilityTools.RED}{UtilityTools.BOLD}[X] Name must be between 1 and 80 characters.{UtilityTools.RESET}")
48
+
49
+ def list_workspaces(workspaces: List[Tuple[int, str]]) -> None:
50
+ print("[*] Found existing sessions:")
51
+ print(" [0] Create new workspace")
52
+ for idx, name in workspaces:
53
+ print(f" [{idx}] {name}")
54
+ print(f" [{len(workspaces)+1}] Exit")
55
+
56
+ def choose_workspace(
57
+ workspaces: List[Tuple[int, str]],
58
+ dc: DataController,
59
+ startup_auth_proxy: Optional[str] = None,
60
+ startup_silent: bool = False,
61
+ ) -> None:
62
+ workspace_map = {idx: name for idx, name in workspaces}
63
+
64
+ while True:
65
+ try:
66
+ choice = int(input("Choose an option: ").strip())
67
+ break
68
+ except ValueError:
69
+ print("Please enter a valid number.")
70
+
71
+ if choice == 0:
72
+ name, workspace_id = prompt_new_workspace(dc)
73
+ workspace_instructions(
74
+ workspace_id,
75
+ name,
76
+ startup_auth_proxy=startup_auth_proxy,
77
+ startup_silent=startup_silent,
78
+ )
79
+ elif choice == len(workspaces) + 1:
80
+ exit()
81
+ elif choice in workspace_map:
82
+ workspace_instructions(
83
+ choice,
84
+ workspace_map[choice],
85
+ startup_auth_proxy=startup_auth_proxy,
86
+ startup_silent=startup_silent,
87
+ )
88
+ else:
89
+ print(f"{UtilityTools.RED}{UtilityTools.BOLD}[X] Invalid workspace selected. Quitting...{UtilityTools.RESET}")
90
+ exit()
91
+
92
+ def main() -> None:
93
+ parser = argparse.ArgumentParser(add_help=True)
94
+ parser.add_argument(
95
+ "--auth-proxy",
96
+ dest="auth_proxy",
97
+ default=None,
98
+ help=(
99
+ "Startup-only proxy for credential auth exchanges during add/load at launch "
100
+ "(does not apply to module API traffic, set that in configs or per proxy). "
101
+ "Format: host:port or http(s)://host:port."
102
+ ),
103
+ )
104
+ parser.add_argument(
105
+ "--silent",
106
+ action="store_true",
107
+ help="Start OCInferno without printing the initial help banner.",
108
+ )
109
+ args = parser.parse_args()
110
+
111
+ dc = DataController()
112
+ workspaces = dc.get_workspaces()
113
+
114
+ # If we have no existing workspaces prompt for a new one
115
+ if not workspaces:
116
+ print("[*] No workspaces detected. Please create your first workspace.")
117
+ name, workspace_id = prompt_new_workspace(dc)
118
+ workspace_instructions(
119
+ workspace_id,
120
+ name,
121
+ startup_auth_proxy=args.auth_proxy,
122
+ startup_silent=args.silent,
123
+ )
124
+
125
+ # If workspaces exist presetn options and have user choose one
126
+ else:
127
+ list_workspaces(workspaces)
128
+ choose_workspace(
129
+ workspaces,
130
+ dc,
131
+ startup_auth_proxy=args.auth_proxy,
132
+ startup_silent=args.silent,
133
+ )
134
+
135
+ if __name__ == "__main__":
136
+ try:
137
+ main()
138
+ except KeyboardInterrupt:
139
+ print("\n[*] Interrupted. Exiting.")
140
+ raise SystemExit(130)