第一次分析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/



		
		
		
		

还没有评论,来说两句吧...