freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Shiro CVE-2022-40664 请求转发导致的验证绕过
mag1c7 2025-02-05 23:06:48 68208
所属地 广东省

漏洞描述

Apache Shiro before 1.10.0, Authentication Bypass Vulnerability in Shiro when forwarding or including via RequestDispatcher.

[1]

漏洞条件

  • shiro < 1.10.0

  • 请求转发(forwarding)或者including:
    存在一个不需要验证的路径转发(或including)到需要验证的路径。

漏洞复现

环境

shiro:1.9.1
springboot:2.7.4(tomcat 9.0.96)

shiro:

@Bean
    public IniRealm getIniRealm() {
        return new IniRealm("classpath:shiro.ini");
    }

    @Bean
    public DefaultWebSecurityManager securityManager(IniRealm iniRealm) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(iniRealm);
        return manager;
    }

    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);
        bean.setLoginUrl("/login");
        bean.setSuccessUrl("/loginSuccess");
        bean.setUnauthorizedUrl("/unauthorized");

        // URL 过滤规则
        LinkedHashMap<String, String> map = new LinkedHashMap<>();
        map.put("/withAuthcPage", "authc");
        map.put("/login", "authc"); 
        bean.setFilterChainDefinitionMap(map);

        return bean;
    }

	//【补丁必要配置】!!!!
    @Bean
    public FilterRegistrationBean<AbstractShiroFilter> shiroFilterRegistration(ShiroFilterFactoryBean shiroFilterFactoryBean) throws Exception{
        FilterRegistrationBean<AbstractShiroFilter> registrationBean = new FilterRegistrationBean<>();

        // 获取 ShiroFilter 的实例
        registrationBean.setFilter(shiroFilterFactoryBean.getObject());


		// 设置 DispatcherType
        registrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD); 

        return registrationBean;
    }

最后一项配置等效于原生环境:

<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>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>

controller:注意controller不要用@RestController,这会使得字符串关键字“forward”无效

//@Controller
//....

	@GetMapping("/noAuthcPage")
    public String noAuthcPage() {

        return "forward:/withAuthcPage";
    }
	
	//以上方法等效于:
	@GetMapping("/noAuthcPage")
    public void noAuthcPage(HttpServletRequest request, HttpServletResponse response) {
        try{
            RequestDispatcher requestDispatcher = request.getRequestDispatcher("/withAuthcPage");
            requestDispatcher.forward(request,response);
        }catch (Exception e){
            //ignore
        }

    }


	@ResponseBody
    @GetMapping("/withAuthcPage")
    public String withAuthcPage() {
        return "withAuthcPage";
    }

测试

image

漏洞分析

OncePerRequestFilter所在类图:

img

左边的分支是Shiro内部的过滤器,右分支AbstractShiroFilter才是正真注册在Web容器中的Filter,shiro-spring中注册到Web容器中的Filter是org.apache.shiro.spring.web.ShiroFilterFactoryBean$SpringShiroFilter(implements AbstractShiroFilter)

OncePerRequestFilter.doFilter(...):省去日志相关内容

public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
    	//漏洞点
        if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {

            filterChain.doFilter(request, response);
        } else //noinspection deprecation
            if (!isEnabled(request, response) ||
                 shouldNotFilter(request) ) {

            filterChain.doFilter(request, response);
        } else {
            // Do invoke this filter...
            //第一次会进入这个分支
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

            try {
                //由子类实现
                doFilterInternal(request, response, filterChain);
            } finally {
                // Once the request has finished, we're done and we don't
                // need to mark as 'already filtered' any more.
                request.removeAttribute(alreadyFilteredAttributeName);
            }
        }
    }

SpringShiroFilter第一次doFilter:
image

请求转发后SpringShiroFilter第二次进入:

image

由于请求转发后的请求本质上还是同一个请求对象,因此会进入图中分支。这里filterChain是ApplicationFilterChain,也就是web容器内的过滤链,filterChian.doFilter()将会导致,filterChian直接跨过当前的SpringShiroFilter,进入下一个过滤器(或直接进入servlet),从而绕过shiro。

漏洞修复

改变了判定条件:[3]

imageimage

但是这个补丁,是有限制的,必须要用上文环境部分的【补丁必要配置】,才能够生效

为什么要有【补丁必要配置】?

其实如果没有【补丁必要配置】,则转发的请求根本不会进入shiro,最终也能得到相同的测试结果。这是因为tomcat在请求转发后会重新生成生成过滤链,然后request重新经过filterChains-->servlet,如果没有该配置,则SpringShiroFilter不会被添加进这个过滤链,从而绕过shiro限制.[2]

核心类以及对应方法:

//org.apache.catalina.core.ApplicationDispatcher implements AsyncDispatcher, RequestDispatcher


private void invoke(ServletRequest request, ServletResponse response,
            State state) throws IOException, ServletException {

        // Checking to see if the context classloader is the current context
        // classloader. If it's not, we're saving it, and setting the context
        // classloader to the Context classloader
        ClassLoader oldCCL = context.bind(false, null);

        // Initialize local variables we may need
        ....

        // Check for the servlet being marked unavailable
        if (wrapper.isUnavailable()) {
           ......
        }

        // Allocate a servlet instance to process this request
        try {
            if (!unavailable) {
                servlet = wrapper.allocate();
            }
        } catch (ServletException e) {
            ....
        }

        // Get the FilterChain Here
    	//如果没有【补丁必要配置】,则SpringShiroFilter不会被添加进去
        ApplicationFilterChain filterChain =
                ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

        // Call the service() method for the allocated servlet instance
        try {
            // for includes/forwards
            if ((servlet != null) && (filterChain != null)) {
               filterChain.doFilter(request, response);
             }
            // Servlet Service Method is called by the FilterChain
        } catch (ClientAbortException e) {
          .....
        }

        //.....释放资源

读者可以自行尝试在大于等于shiro 1.10.0的版本中,不用【补丁必要配置】,会发生什么。本人在shiro 1.13.0条件下,不用【补丁必要配置】(其余不变),发现是可以绕过的。这里就不展开了
补充
【补丁必要配置】,可以不用手动配置,可以通过引入以下依赖,完成自动配置

<dependency>
	<groupId>org.apache.shiro</groupId>
	<artifactId>shiro-spring-boot-starter</artifactId>
	<version>对应版本</version>
</dependency>

其中具体配置类是org.apache.shiro.spring.config.web.autoconfigure.ShiroWebFilterConfiguration

Reference


[1] Security Reports | Apache Shiro

[2] 关于filter过滤器为何不能过滤转发的请求 - mrsl - 博客园

# 漏洞分析 # Shiro # Java代码审计
本文为 mag1c7 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
漏洞分析:shiro
mag1c7 LV.5
这家伙太懒了,还未填写个人描述!
  • 24 文章数
  • 10 关注者
[PoC] Tomcat CVE-2025-24813 RCE
2025-03-12
DJL CVE-2025-0851 绝对路径遍历
2025-03-09
Tomcat CVE-2024-50379 条件竞争导致命令执行
2025-02-27
文章目录