第一次分析Java的代码。错误的请大家见谅
一、环境准备
首先需要安装IDEA
随便那个那边都可以了。然后下载一个tomcat
具体怎么配置tomat 百度吧。
Shiro 代码 【p神是永远的神】
https://github.com/phith0n/JavaThings/tree/master/shirodemo
下载完成之后。解压即可。
选择你下载的文件目录
打开这个pom.xml 、然后进行等待即可。会自动下载包的。
设置一下tomcat 如果你需要设置其他端口就换成其他端口。不需要则不需要动
然后选择debug 启动
启动之后。首先需要看看能否访问
启动成功之后,是可以打开登陆页面的。
二、代码调试
首先要知道shiro是一个用来做身份验证的框架,其原理是基于servlet的filter进行的。在web.xml中定义了ShiroFilter,作用范围是当前目录下所有的url
<listener> <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class> </listener> <filter> <filter-name>ShiroFilter</filter-name> <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class> </filter> <filter-mapping> <filter-name>ShiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list>
漏洞原理:
Apache Shiro框架提供了记住我的功能(RememberMe),用户登陆成功后会生成经过加密并编码的cookie。cookie的key为RememberMe,
cookie的值是经过对相关信息进行序列化,然后使用aes加密,最后在使用base64编码处理形成的。
rememberMe生成过程:
序列化 》AES加密》Base64 编码》 生成rememberMe内容
服务端接收cookie值时:
检索cookie中的rememberMe内容 》 Base64 解密》 AES解密 (加密密钥硬编码)》反序列化(未作过滤处理)
AES的加密密钥在shiro的1.2.4之前版本中使用的是硬编码,其默认密钥的base64编码后的值可在代码中找到。只要找到密钥后就可以通过构造恶意的序列化对象进行编码,
加密,然后作为cookie加密发送,服务端接收后会解密并触发反序列化漏洞
首先使用Xray 扫描一下得到结果
验证Key
GET /shirodemo_war/login.jsp HTTP/1.1 Host: 192.168.0.11:8081 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: rememberMe=rk0ArlKoRNWDhClOxAZyFEK7LO14GfjNLFXhDNErl7LTXu3D2Ne/HCOyNYciJ7RMntXtKBZEHBIvlh2tZ4JUZvXlutMbY24GEp0SwFkYZBB/IWDSJWjzr/3WZN+/1IqJzhSwQ1r9Pcy/EuoDk0dXMabi08nrY1T+VH34K3L8r+mmsLvxfdDEVPPoRtDn5nLF Connection: close
三、代码断点
1. 加密
序列化 》AES加密》Base64 编码》 生成rememberMe内容
处理Cookie的类是CookieRememberMeManaer,该类继承AbstractRememberMeManager类,跟进AbstractRememberMeManager类,很容易看到AES的key。
断点打在AbstractRememberMeManager类的onSuccessfulLogin
用户名root 密码secret。选择Remember me 然后段点进行跟踪
首先是onSuccessfulLogin 后面进入到rememberIdentity –>convertPrincipalsToBytes –>encrypt
主要的函数在convertPrincipalsToBytes 。首先是进行序列化,然后进行AES加密
protected byte[] convertPrincipalsToBytes(PrincipalCollection principals) { byte[] bytes = serialize(principals); // 进行序列化 if (getCipherService() != null) { bytes = encrypt(bytes); // AES加密 } return bytes; }
encrypt 函数
protected byte[] encrypt(byte[] serialized) { byte[] value = serialized; CipherService cipherService = this.getCipherService(); if (cipherService != null) { ByteSource byteSource = cipherService.encrypt(serialized, this.getEncryptionCipherKey()); value = byteSource.getBytes(); } return value; }
进行AES加密,利用arraycopy()方法将随机的16字节IV放到序列化后的数据前面,完成后再进行AES加密。 秘钥为Base64.decode(“kPH+bIxk5D2deZiIxcaaaA==”);
AES操作在 JcaCipherService类的encrypt
private ByteSource encrypt(byte[] plaintext, byte[] key, byte[] iv, boolean prependIv) throws CryptoException { int MODE = true; byte[] output; if (prependIv && iv != null && iv.length > 0) { byte[] encrypted = this.crypt(plaintext, key, iv, 1); output = new byte[iv.length + encrypted.length]; System.arraycopy(iv, 0, output, 0, iv.length); System.arraycopy(encrypted, 0, output, iv.length, encrypted.length); } else { output = this.crypt(plaintext, key, iv, 1); } if (log.isTraceEnabled()) { log.trace("Incoming plaintext of size " + (plaintext != null ? plaintext.length : 0) + ". Ciphertext " + "byte array is size " + (output != null ? output.length : 0)); } return Util.bytes(output); }
最后在CookieRememberMeManager类的rememberSerializedIdentity()方法中进行base64加密:
protected void rememberSerializedIdentity(Subject subject, byte[] serialized) { if (!WebUtils.isHttp(subject)) { if (log.isDebugEnabled()) { String msg = "Subject argument is not an HTTP-aware instance. This is required to obtain a servlet request and response in order to set the rememberMe cookie. Returning immediately and ignoring rememberMe operation."; log.debug(msg); } } else { HttpServletRequest request = WebUtils.getHttpRequest(subject); HttpServletResponse response = WebUtils.getHttpResponse(subject); String base64 = Base64.encodeToString(serialized); Cookie template = this.getCookie(); Cookie cookie = new SimpleCookie(template); cookie.setValue(base64); cookie.saveTo(request, response); } }
2、解密
检索cookie中的rememberMe内容 –> Base64 解密 –> AES解密 (加密密钥硬编码) —>反序列化(未作过滤处理)
有了AES的key、加密模式AES/CBC/PKCS5Padding,由于AES是对称加密,所以我们已经可以解密AES的密文了。
第一步:获取rememberMe的Cookie
第二步:base64解码。CookieRememberMeManager类的getRememberedSerializedIdentity()方法
protected byte[] getRememberedSerializedIdentity(SubjectContext subjectContext) { if (!WebUtils.isHttp(subjectContext)) { if (log.isDebugEnabled()) { String msg = "SubjectContext argument is not an HTTP-aware instance. This is required to obtain a servlet request and response in order to retrieve the rememberMe cookie. Returning immediately and ignoring rememberMe operation."; log.debug(msg); } return null; } else { WebSubjectContext wsc = (WebSubjectContext)subjectContext; if (this.isIdentityRemoved(wsc)) { return null; } else { HttpServletRequest request = WebUtils.getHttpRequest(wsc); HttpServletResponse response = WebUtils.getHttpResponse(wsc); String base64 = this.getCookie().readValue(request, response); if ("deleteMe".equals(base64)) { return null; } else if (base64 != null) { base64 = this.ensurePadding(base64); if (log.isTraceEnabled()) { log.trace("Acquired Base64 encoded identity [" + base64 + "]"); } byte[] decoded = Base64.decode(base64); if (log.isTraceEnabled()) { log.trace("Base64 decoded byte array length: " + (decoded != null ? decoded.length : 0) + " bytes."); } return decoded; } else { return null; } } } }
解密出Base64
第三步解密出AES,
解密完之后会到AbstractRememberMeManager 类的getRememberedPrincipals 函数
关键函数。
public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) { PrincipalCollection principals = null; try { byte[] bytes = this.getRememberedSerializedIdentity(subjectContext); if (bytes != null && bytes.length > 0) { principals = this.convertBytesToPrincipals(bytes, subjectContext); } } catch (RuntimeException var4) { principals = this.onRememberedPrincipalFailure(var4, subjectContext); } return principals; }
进入到convertBytesToPrincipals
protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext) { if (this.getCipherService() != null) { bytes = this.decrypt(bytes); //AES 解密 } return this.deserialize(bytes); //反序列化 }
第四步 反序列化
protected PrincipalCollection deserialize(byte[] serializedIdentity) { return (PrincipalCollection)this.getSerializer().deserialize(serializedIdentity); }
GET /shirodemo_war/login.jsp HTTP/1.1 Host: 192.168.0.11:8081 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: rememberMe=rk0ArlKoRNWDhClOxAZyFEK7LO14GfjNLFXhDNErl7LTXu3D2Ne/HCOyNYciJ7RMntXtKBZEHBIvlh2tZ4JUZvXlutMbY24GEp0SwFkYZBB/IWDSJWjzr/3WZN+/1IqJzhSwQ1r9Pcy/EuoDk0dXMabi08nrY1T+VH34K3L8r+mmsLvxfdDEVPPoRtDn5nLF Connection: close 0
因为是知道为AES 加密的方式和CBC的模式,外部的Cookie 可以控制。然后只需要爆破出Key 就能进行反序列化操作。对于xray 的命令执行的操作。下次研究一下。
参考:
https://y4er.com/post/shiro-rememberme-rce/
https://xz.aliyun.com/t/8445
https://xz.aliyun.com/t/6493
https://xz.aliyun.com/t/7207
https://paper.seebug.org/shiro-rememberme-1-2-4/
https://www.cnblogs.com/loong-hon/p/10619616.html
https://www.bilibili.com/read/cv9949257/
还没有评论,来说两句吧...