专栏首页FreeBuf甲方视角:SHIRO-721临时修复方案

甲方视角:SHIRO-721临时修复方案

0x00 问题简介

14日晚上,发现了一个公开的Shiro的RCE漏洞,作为在甲方工作了挺久的人,一听Shiro立马虎躯一震,来不及分析poc,立马先得确认公司业务是否受到影响。确定了影响后,立马看怎么修复漏洞。结果悲催的时候出现了,这个漏洞官方居然没有升级修复。这让笔者想起差不多半年前给Shiro官方推的一个加固,结果一直没有收到官方回复,直接杳无音讯。

受影响的组件漏洞版本

以下组件的全量版本均收到漏洞影响,当然这个这个漏洞需要可以登录才能利用。

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
</dependency>

0x01 问题分析

参考https://www.anquanke.com/post/id/192819。

笔者看漏洞分析,大致清楚了漏洞原理及其触发条件,但来不及分析更多细节。也更加聚焦于怎么让业务修复这个漏洞。大致想了下几种常见的漏洞处理办法:

(1)升级:上面说了官方没有解决,pass (2)下线业务:除非业务真的没有用了,否则面谈,pass (3)WAF拦截EXP:Shiro的EXP是加密值,特征不够明显(这里先不谈绕过),而且有些甲方的有些业务还没接WAF,pass。

如此看来,似乎只能希望攻击者没有账号不来搞事情了。但是想想,这么搞也太不负责任,并且业务还在等着方案,通知了业务有没有修复方案难免有点有损安全部门门脸。是在没有办法,只能想着自己出修复方案了。

经过和业务沟通,发现有些业务虽然用了Shiro框架,但是并不需要rememberMe这个功能,于是想着,能不能找到个配置方法,把这个功能直接干掉,经过分析,在极短的时间内没有发现。于是又想,Shiro这个漏洞不是从Filter触发的吗,能不能在Filter做点事情,把rememberMe这个功能干掉。于是,有两个思路来干掉rememberMe功能。

思路(1):提供一个Filter,在Shiro的Filter被调用前就调用,然后将cookie里的rememberMe直接置空。 思路(2):覆盖Shiro Filter的一些行为,然后去掉rememberMe功能。 思路(1)实现起来很简单,但是有个问题,就怕业务配置filer的顺序错误,导致问题没有修复。所有想着用思路(2)。

于是问题聚焦于怎么找到Shiro的Filter,以及怎么覆盖其行为。

1.1 寻找Shiro的Filter

用poc直接大断点,发现Filter是ShiroFilterFactoryBean$SpringShiroFilter。

但是发现这个内部类是个final Class,因此是没有办法被继承的。

private static final class SpringShiroFilter extends AbstractShiroFilter {
        protected SpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
            super();
            if (webSecurityManager == null) {
                throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
            }
            setSecurityManager(webSecurityManager);
            if (resolver != null) {
                setFilterChainResolver(resolver);
            }
        }
    }

1.2 覆盖默认的Filter

由于这个内部类是个final Class,只能去更底一级覆盖。我们看下Shiro的配置:

<filter>
        <!--filter 配置-->
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
</filter>
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <!--省略-->
</bean>

于是,我们发现可以复写ShiroFilterFactoryBean类,然后让业务使用我们配置的类,从而覆盖Shiro Filter默认的行为,去除rememberMe功能。于是得到以下的临时pactch方案。

这里强调几个注意点:(1)复写的类出得request是ServletRequest实例,没有操作cookie的方法。实际上在tomcat下,这个类是RequestFacade实例。(2)RequestFacade实例写代码时需要加额外的provided maven依赖。由于这个类和tomcat版本有关,因此需要测兼容性,不通用,可以用反射方式解决。

0x02 临时patch

(1)在源码中添加以下类

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.mgt.FilterChainManager;
import org.apache.shiro.web.filter.mgt.FilterChainResolver;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.apache.shiro.web.mgt.WebSecurityManager;
import org.apache.shiro.web.servlet.AbstractShiroFilter;
import org.springframework.beans.factory.BeanInitializationException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Field;
/**
 * @author: Venscor
 * @date: 2019/11/14
 * @description
 */
public class SafeShiroFilterFactoryBean extends ShiroFilterFactoryBean {
    @Override
    protected AbstractShiroFilter createInstance() throws Exception {
        SecurityManager securityManager = getSecurityManager();
        if (securityManager == null) {
            String msg = "SecurityManager property must be set.";
            throw new BeanInitializationException(msg);
        }

        if (!(securityManager instanceof WebSecurityManager)) {
            String msg = "The security manager does not implement the WebSecurityManager interface.";
            throw new BeanInitializationException(msg);
        }
        FilterChainManager manager = createFilterChainManager();
        //Expose the constructed FilterChainManager by first wrapping it in a
        // FilterChainResolver implementation. The AbstractShiroFilter implementations
        // do not know about FilterChainManagers - only resolvers:
        PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
        chainResolver.setFilterChainManager(manager);
        //Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
        //FilterChainResolver.  It doesn't matter that the instance is an anonymous inner class
        //here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
        //injection of the SecurityManager and FilterChainResolver:
        return new SafeShiroFilterFactoryBean.SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
    }
    private static final class SpringShiroFilter extends AbstractShiroFilter {
        protected SpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
            if (webSecurityManager == null) {
                throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
            } else {
                this.setSecurityManager(webSecurityManager);
                if (resolver != null) {
                    this.setFilterChainResolver(resolver);
                }
            }
        }
        @Override
        protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws ServletException, IOException {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            Cookie[] cookies = request.getCookies();
            boolean needResetCookie = false;
            if (cookies != null && cookies.length > 0) {
                for (Cookie cookie : cookies) {
                    if (cookie.getName().equalsIgnoreCase("rememberMe")) {
                        cookie.setValue("");
                        needResetCookie = true;
                        break;
                    }
                }
            }
            if (needResetCookie) {
                Object innerReq = getField(request, "request");
                setField(innerReq, "cookies", cookies);
                setField(request, "request", innerReq);
            }
            super.doFilterInternal(servletRequest, servletResponse, chain);
        }
        public void setField(Object instance, String fieldName, Object fieldValue) {
            try {
                Field field = instance.getClass().getDeclaredField(fieldName);
                field.setAccessible(true);
                field.set(instance, fieldValue);
            } catch (Exception e) {
                throw new RuntimeException();
            }
        }
        public static Object getField(Object instance, String fieldName) {
            try {
                Field field = instance.getClass().getDeclaredField(fieldName);
                field.setAccessible(true);
                return field.get(instance);
            } catch (Exception e) {
                throw new RuntimeException();
            }
        }
    }
}

(2)替换原始的ShiroFilter Bean

SpringMVC中配置示例:

<filter>
        <!--filter 配置-->
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
</filter>
<!--替换原始的Shiro Bean配置-->
<bean id="shiroFilter" class="[包名].SafeShiroFilterFactoryBean">
    <!--中间的配置不需要做任何改变-->
</bean>

SpringBoot配置示例:在原来配置Shiro的Config类中修改

@Bean(name = "shiroFilter")
//返回值修改:ShiroFilterFactoryBean改为SafeShiroFilterFactoryBean
public SafeShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager,
                                                        CasFilter casFilter) {
    //这个地方改成SafeShiroFilterFactoryBean
    //ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 这个地方替换掉
    SafeShiroFilterFactoryBean shiroFilterFactoryBean = new SafeShiroFilterFactoryBean(); // 这个地方替换掉
    shiroFilterFactoryBean.setSecurityManager(securityManager);
    String loginUrl = cas.getServerUrlPrefix() + "/login?service=" + cas.getClientHostUrl() + cas.getCasFilterUrlPattern();
    shiroFilterFactoryBean.setLoginUrl(loginUrl);
    shiroFilterFactoryBean.setSuccessUrl("/");
    // 未授权 url
    shiroFilterFactoryBean.setUnauthorizedUrl(markProperties.getShiro().getUnauthorizedUrl());
    Map<String, Filter> filters = new HashMap<>();
    filters.put("casFilter", casFilter);
    LogoutFilter logoutFilter = new LogoutFilter();
    logoutFilter.setRedirectUrl(cas.getServerUrlPrefix() + "/logout?");
    filters.put("logout", logoutFilter);
    shiroFilterFactoryBean.setFilters(filters);
    loadShiroFilterChain(shiroFilterFactoryBean);
    return shiroFilterFactoryBean;
}

0x03 写在最后

Shiro还有其他配置方式,可以类似处理。笔者水平有限,如果不当之处,还望不吝指正。

本文分享自微信公众号 - FreeBuf(freebuf)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-11-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 面试中常问的List去重问题,你都答对了吗?

    面试中经常被问到的list如何去重,用来考察你对list数据结构,以及相关方法的掌握,体现你的java基础学的是否牢固。

    JAVA葵花宝典
  • JAVA8十大新特性详解

    Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,示例如下:

    用户3467126
  • Java技术面试问题

    好好学java
  • Spring Boot2 系列教程(十八)Spring Boot 中自定义 SpringMVC 配置

    用过 Spring Boot 的小伙伴都知道,我们只需要在项目中引入 spring-boot-starter-web 依赖,SpringMVC 的一整套东西就会...

    江南一点雨
  • Spring的BeanUtils有坑?可能是你用错了!

    在这里,我们今天重点说的是第二点,第一点是因为用反射拿到set和get方法再去拿属性值和设置属性值的,不懂反射的人可以自行百度下。第三和第四点很简单了应该是不需...

    JAVA葵花宝典
  • 说说Netty的线程模型

    最近发现极客时间的很多课程中,都穿插到了 Netty,可见 Netty 的重要性。基于此,给大家推荐一下这篇文章!

    好好学java
  • shiro开发,shiro的环境配置(基于spring+springMVC+redis)

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    eguid
  • cas单点登录系统:客户端(client)详细配置(包含统一单点注销配置)

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    eguid
  • myBatis动态语句详解

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    eguid
  • 可以提高千倍效率的Java代码小技巧

    来源: https://www.cnblogs.com/Qian123/p/6046096.html 作者:萌小Q

    好好学java

扫码关注云+社区

领取腾讯云代金券