Shiro RememberMe 1.2.4 反序列化命令执行漏洞复现
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Shiro RememberMe 1.2.4 反序列化命令执行漏洞复现相关的知识,希望对你有一定的参考价值。
参考技术A Apache Shiro <= 1.2.4shiro默认使用了 CookieRememberMeManager ,其处理cookie的流程是:得到 rememberMe的cookie值 --> Base64解码 --> AES解密 --> 反序列化 。
然而AES的密钥是硬编码的,就导致了攻击者可以构造恶意数据造成反序列化的RCE漏洞。
前16字节的密钥 -->后面 加入序列化参数 --> AES加密 --> base64编码 -->发送 cookie 。
使用大佬脚本生成 payload( ysoserial.jar 文件和运行目录处于同一目录)
图片.png 图片.png
这里依旧使用docker进行漏洞复现
图片.png 图片.png 图片.png 图片.png 图片.png 图片.png 图片.png 图片.png 图片.png 图片.png 图片.png 图片.png
参考链接:
https://blog.csdn.net/three_feng/article/details/52189559
http://www.db-sec.com/2019/06/apache-shiro%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0/
spring 配置 shiro rememberMe
1.shiro 提供记住我的功能,当将form表单中name="rememberMe" 的value设为true或者登陆的token中。token.setRememberMe(true) 的时候,用户关闭浏览器之后,现在进入需要认证的资源的时候就不需要再登陆。
2.form表单中的value不仅仅只有true 的状态位,还可以设置t、1、enabled、y、yes、on这集中状态位都表示记住我。
3.登陆页面login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>登录页面</title> </head> <body> <form action="/shiro/doLogin" > <table> <tr> <td>用户名:</td> <td><input type="text" value="" name="loginName"></td> </tr> <tr> <td>用户名:</td> <td><input type="password" value="" name="password"></td> </tr> <%--记住我功能--%> <tr> <td> <%--这里为了方便我默认将value设置为true,shiro对value 的判断除了true这个状态位还有:t,1,enabled,y,yes,on这几种--%> <input type="checkbox" value="true" name="rememberMe"> </td> <td>记住我</td> </tr> </table> <input type="submit" value="登录"> </form> </body> </html>
4.spring 配置rememberMe
<!--rememberMe cookie--> <bean id="rememberMe" class="org.apache.shiro.web.servlet.SimpleCookie"> <constructor-arg value="rememberMe"></constructor-arg> <property name="httpOnly" value="true"></property> <!--cookie 的最大失效时间 30天--> <property name="maxAge" value="259200"></property> </bean>
maxAge=-1 表示关闭浏览器cookie失效
<!--rememberMe 管理器--> <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager"> <property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode(\'4AvVhmFLUs0KTA3Kprsdag==\')}"></property> <property name="cookie" ref="rememberMe"></property> </bean>
cipherKey:表示设置cookie的加密算法,采用的是base64的加密
<!--form表单验证的过滤器--> <bean id="formAuthenticationFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"> <!--loginName 对应form表单的用户名--> <property name="usernameParam" value="loginName" /> <!--password 对应form表单的密码--> <property name="passwordParam" value="password" /> <!--rememberMe 记住我checkbox 是否记住我默认为false--> <property name="rememberMeParam" value="rememberMe" /> <!--form 的action--> <property name="loginUrl" value="/shiro/doLogin" /> </bean>
form表单登陆的过滤器,这个过滤器对应的认证key是authc,当登陆的请求匹配到这个key的时候,就将这个过滤器加入到当前请求的过滤器链中。
<!--指定shiro的核心管理器--> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!--可以设置一个或者多个安全域--> <property name="realm" ref="customRealm" /> <!-- 会话管理 --> <property name="sessionManager" ref="sessionManager" /> <!--记住我--> <property name="rememberMeManager" ref="rememberMeManager"/> </bean>
将rememberMe的管理器交给securityManager 管理。
<!-- Shiro主过滤器本身功能十分强大,其强大之处就在于它支持任何基于URL路径表达式的、自定义的过滤器的执行 --> <!-- Web应用中,Shiro可控制的Web请求必须经过Shiro主过滤器的拦截,Shiro对基于Spring的Web应用提供了完美的支持 --> <!--ShiroFilterFactoryBean 是一个shiroFilter的工厂类,负责实例化过滤器--> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- Shiro的核心安全接口,这个属性是必须的 --> <property name="securityManager" ref="securityManager"/> <!-- 要求登录时的链接,当request请求被解析为需要认证则跳转到这个链接进行登录 --> <property name="loginUrl" value="/shiro/goLogin"/> <!-- 登录成功后要跳转的连接(本例中此属性用不到,因为登录成功后的处理逻辑在LoginController里硬编码为main.jsp了) --> <!-- <property name="successUrl" value="/system/main"/> --> <!-- 用户访问未对其授权的资源时,所显示的连接 --> <!-- 若想更明显的测试此属性可以修改它的值,如unauthor.jsp,然后用[玄玉]登录后访问/admin/listUser.jsp就看见浏览器会显示unauthor.jsp --> <property name="unauthorizedUrl" value="/"/> <!-- Shiro连接约束配置,即过滤链的定义 --> <!-- 此处可配合这篇文章来理解各个过滤连的作用http://blog.csdn.net/jadyer/article/details/12172839 --> <!-- 下面value值的第一个\'/\'代表的路径是相对于HttpServletRequest.getContextPath()的值来的 --> <!--shiro的默认过滤器分为两种:认证过滤器:anon,authcBasic,auchc,user 和授权过滤器:perms,roles,ssl,rest,port--> <!-- anon:它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种 --> <!-- authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter --> <!-- user: 表示身份验证通过,或者记住我--> <!-- rememberMe:记住我设置后不用再需要登录--> <property name="filterChainDefinitions"> <value> <!--配置记住我拦截--> /shiro/userInfo=user <!-- /mydemo/login=anon /mydemo/getVerifyCodeImage=anon /main**=authc /user/info**=authc /admin/listUser**=authc,perms[admin:manage]--> </value> </property> </bean>
4.ShiroController.java
package com.xiao.core.shiro; import com.alibaba.druid.sql.visitor.functions.Char; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; @Controller @RequestMapping("/shiro") @Scope("prototype") public class ShiroController { //跳转到用户登录页面 @RequestMapping("/goLogin") public ModelAndView goLogin(){ ModelAndView modelAndView=new ModelAndView(); modelAndView.setViewName("/user/login"); return modelAndView; } //用户登录请求 @RequestMapping("/doLogin") public ModelAndView doLogin(UserEntity userEntity){ ModelAndView modelAndView=new ModelAndView(); String loginName=userEntity.getLoginName(); String password=userEntity.getPassword(); //获取主题 Subject subject=SecurityUtils.getSubject(); //创建token UsernamePasswordToken token=new UsernamePasswordToken(); token.setUsername(loginName); token.setPassword(password.toCharArray()); //设置记住我 token.setRememberMe(true); //开始登录 subject.login(token); //判断是否登录成功 if(subject.isAuthenticated()){ modelAndView.setViewName("/user/loginSuccess"); //登录成功 }else{ modelAndView.setViewName("/user/loginFailed"); //登录失败 } return modelAndView; } /*测试跳转到下一个页面是否能继续使用权限*/ @RequestMapping("/userInfo") public ModelAndView goUserInfo(){ //获取用户的信息 UserEntity userEntity=(UserEntity) SecurityUtils.getSubject().getPrincipal(); ModelAndView modelAndView=new ModelAndView(); modelAndView.setViewName("/user/userInfo"); /*modelAndView.addObject("user",userEntity);*/ return modelAndView; } }
UserEntity:用户实体类
6.启动server容器之后:
点击登陆之后,当登陆成功之后会调用DefaultSecurityManager的rememberMeSuccessfulLogin方法将记住我写入到cookie中
protected void rememberMeSuccessfulLogin(AuthenticationToken token, AuthenticationInfo info, Subject subject) { RememberMeManager rmm = getRememberMeManager(); if (rmm != null) { try { rmm.onSuccessfulLogin(subject, token, info); } catch (Exception e) { if (log.isWarnEnabled()) { String msg = "Delegate RememberMeManager instance of type [" + rmm.getClass().getName() + "] threw an exception during onSuccessfulLogin. RememberMe services will not be " + "performed for account [" + info + "]."; log.warn(msg, e); } } } else { if (log.isTraceEnabled()) { log.trace("This " + getClass().getName() + " instance does not have a " + "[" + RememberMeManager.class.getName() + "] instance configured. RememberMe services " + "will not be performed for account [" + info + "]."); } } }
之后再调用RememberManager的onSuccessfulLogin方法
public void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info) { //清除上一个subject主题 forgetIdentity(subject); //创建新的认证 if (isRememberMe(token)) { rememberIdentity(subject, token, info); } else { if (log.isDebugEnabled()) { log.debug("AuthenticationToken did not indicate RememberMe is requested. " + "RememberMe functionality will not be executed for corresponding account."); } } }
继续跟踪会到设置cookie 的方法
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); } return; } HttpServletRequest request = WebUtils.getHttpRequest(subject); HttpServletResponse response = WebUtils.getHttpResponse(subject); //serialized 是princple经过序列化之后的数据,将序列话的数据base64位编码 String base64 = Base64.encodeToString(serialized); Cookie template = getCookie(); //the class attribute is really a template for the outgoing cookies Cookie cookie = new SimpleCookie(template); cookie.setValue(base64);
//设置记住我的cookie cookie.saveTo(request, response); }
7.登陆成功的cookie
.
8.关闭浏览器,访问/shiro/userInfo 将可以直接访问,不会跳转到登录页面
以上是关于Shiro RememberMe 1.2.4 反序列化命令执行漏洞复现的主要内容,如果未能解决你的问题,请参考以下文章
Shiro RememberMe 1.2.4 反序列化命令执行漏洞复现