前言

最近一直在面试,感觉有好多想搞的东西没时间搞,不面了,收收心搞技术了
在一次src挖掘中遇到了XXL-JOB,不得不说bp的RouteVulScan插件还是有点用的,简单总结一下

简单使用

XXL-Job是一个springboot架构搭建的分布式任务调度平台,主要用于解决大规模分布式系统中的任务调度和管理问题。

源码结构:

1
2
3
- /xxl-job-admin :调度中心,项目源码
- /xxl-job-core :公共Jar依赖
- /xxl-job-executor-samples :执行器,Sample示例项目(大家可以在该项目上进行开发,也可以将现有项目改造生成执行器项目)

XXL-JOB分为:
admin后台管理页面(默认端口8080)
executor任务执行器(原作者给了两个示例,端口分别为9999,9998)

直接访问返回{"code":500,"msg":"invalid request, HttpMethod not support."}
QQ20241027-1441

管理界面大概长这个样子
QQ20241027-204941
默认账号密码为admin/123456

操作起来也很简单

job(任务)

image
任务有两种
BEAN模式
Bean模式任务,每个任务对应一个Java或者java方法,指定JobHandler,需要使用@XxlJob()标签提前进行注册

GLUE模式(Java)
任务以源码方式存在数据库中,支持通过Web IDE在线更新,实时编译和生效,因此不需要指定JobHandler。

executor(执行器)

实际上是一台内嵌Server,原作者提供springboot 版本执行器,可直接使用

请求流程

  1. “调度中心”向“执行器”发送http调度请求:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    地址格式:{执行器内嵌服务根地址}/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 // 错误提示消息
    }
  2. “执行器”中接收请求,“执行器”执行任务逻辑;

  3. “执行器”http回调“调度中心”调度结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    说明:执行器执行完任务后,回调任务结果时使用
    ------
    地址格式:{调度中心根地址}/api/callback
    Header:
    XXL-JOB-ACCESS-TOKEN : {请求令牌}
    请求数据格式如下,放置在 RequestBody 中,JSON格式:
    [{
    "logId":1, // 本次调度日志ID
    "logDateTim":0, // 本次调度日志时间
    "handleCode":200, // 200 表示任务执行正常,500表示失败
    "handleMsg": null
    }
    ]
    响应数据格式:
    {
    "code": 200, // 200 表示正常、其他失败
    "msg": null // 错误提示消息
    }

    漏洞

Hessian反序列化(XxlJob<=2.1.2)

XXL-JOB <= 2.0.2,其/xxl-job-admin/api接口可以未授权访问,该接口存在Hessian2反序列化漏洞
curl -XPOST -H "Content-Type: x-application/hessian" --data-binary @test.ser http://127.0.0.1:8080/xxl-job-admin/api
关于Hessian2反序列化前面nacos已经写了,这里不仅有原生链,还可以打spring链实现jndi

Spring framework jndi反序列化

漏洞sink点在org/springframework/jndi/JndiTemplate#lookup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public Object lookup(final String name) throws NamingException {
if (logger.isDebugEnabled()) {
logger.debug("Looking up JNDI object with name [" + name + "]");
}
return execute(new JndiCallback<Object>() {
public Object doInContext(Context ctx) throws NamingException {
Object located = ctx.lookup(name); //jndi触发点
if (located == null) {
throw new NameNotFoundException(
"JNDI object with [" + name + "] not found: JNDI implementation returned null");
}
return located;
}
});
}
//下为ctx的构造
..........................................
public <T> T execute(JndiCallback<T> contextCallback) throws NamingException {
Context ctx = getContext();
try {
return contextCallback.doInContext(ctx);
}
finally {
releaseContext(ctx);
}
}
..........................................
public Context getContext() throws NamingException {
return createInitialContext();
}
....................
protected Context createInitialContext() throws NamingException {
Hashtable icEnv = null;
Properties env = getEnvironment();
if (env != null) {
icEnv = new Hashtable(env.size());
CollectionUtils.mergePropertiesIntoMap(env, icEnv);
}
return new InitialContext(icEnv);
}

所以接下来就是寻找哪里可以调用JndiTemplate#lookup
调用栈如下:
lookup:179, JndiTemplate (org.springframework.jndi)
lookup:96, JndiLocatorSupport (org.springframework.jndi)
doGetSingleton:271, SimpleJndiBeanFactory (org.springframework.jndi.support)
doGetType:279, SimpleJndiBeanFactory (org.springframework.jndi.support)
getType:245, SimpleJndiBeanFactory (org.springframework.jndi.support)
getType:238, SimpleJndiBeanFactory (org.springframework.jndi.support)
getOrder:136, BeanFactoryAspectInstanceFactory (org.springframework.aop.aspectj.annotation)
getOrder:223, AbstractAspectJAdvice (org.springframework.aop.aspectj)
getOrder:66, AspectJPointcutAdvisor (org.springframework.aop.aspectj)
toString:147, AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder (org.springframework.aop.aspectj.autoproxy)
equals:392, XString (com.sun.org.apache.xpath.internal.objects)
equals:104, HotSwappableTargetSource (org.springframework.aop.target)
putVal:635, HashMap (java.util)

客户端命令执行漏洞(2.2.0<=XxlJob<=2.4.0)

executor的/run接口未授权可以自定义任务,从而导致任意代码执行

  1. executor默认没有配置认证,未授权的攻击者可以通过RESTful API接口执行任意命令。

  2. 当客户端命令执行漏洞(XxlJob>=2.2.0),可以使用默认accessToken绕过

  3. ssrf获取XXL-JOB-ACCESS-TOKEN

内存马注入

没找到执行器端口,遇到了再写