前言
1 | 本来是没想把这个洞单独写篇文章的,因为也看到其他师傅已经发表了相应的文章。主要的绕过原理也比较简单。即使用正则表达式匹配路径时.*不会匹配\r或\n,因此当url中包含换行符则springsecurity使用RegexRequestMatcher(".*",null)正则表达式去匹配就会匹配不到从而导致了权限绕过。 |
问题
借用killer师傅文章中的Demo漏洞环境如下:
SpringSecurity配置
1 | protected void configure(HttpSecurity http) throws Exception { |
Controller配置
1 |
|
正常访问被SpringSecurity规则拦截,如下所示:

通过%0a绕过,这里和其他师傅们的分析有点不同,虽然绕过了springsecurity的限制但是并没有访问到Controller。

因为不止一个师傅用上面的demo代码做了复现,所以刚开始我以为是自己哪里配置错了,但是反复的检查发现配置没有问题,我也确实绕过了springsecurity的检测,只是没有访问到Controller而已。后来找killer师傅要了他的环境,在绕过以后确实是可以请求到的。

分析
对比了两套环境的差异,发现主要的区别在springmvc的版本上,而且其实根据结果我们也可以猜出来。在第一套环境中springmvc再匹配由/test/11%0a的路由没匹配到而第二套环境匹配到了。所以我们深入的分析下到底是什么原因导致了这种差异。
通过调试对比发现在DispatcherServlet#doDispatch中获取的mappedHandler是不同的,所以问题出现在getHandler的处理过程中。
1 | protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { |
循环调用保存的HandlerMapping#getHandler。
1 | protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { |
默认保存了下面5个HandlerMapping,通过简单查阅文档可知注解中配置的路由由RequestMappingHandlerMapping处理。

getHandler中通过getHandlerInternal获取handler构建HandlerExecutionChain并返回。
1 | public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { |
getHandlerInternal从request对象中获取请求的path并根据path找到handlerMethod。
1 | protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { |
lookupHandlerMethod首先直接根据路径获取获取不到才使用addMatchingMappings遍历所有的ReuqestMappingInfo对象并进行匹配。
1 | protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { |
1 | private void addMatchingMappings(Collection<T> mappings, List<AbstractHandlerMethodMapping<T>.Match> matches, HttpServletRequest request) { |
在getMatchingMapping中不同版本的SpringMVC代码不太一样:
springmvc 5.3.20:
1 | public RequestMappingInfo getMatchingCondition(HttpServletRequest request) { |
springmvc 5.1.9:
1 | public RequestMappingInfo getMatchingCondition(HttpServletRequest request) { |
也就是不同版本的springmvc使用了不同的RequestCondition导致的。经过简单的查阅资料PathPatternsRequestCondition是spring高版本引入的使用PathPattern来进行URL匹配。而在早些版本PatternsRequestCondition使用AntPathMatcher来进行匹配。
AntPathMatcher
主要是由AntPathMatcher#doMatch完成匹配,首先通过tokenizePattern对路由中的配置的url进行分割,再调用tokenizePath对传入的PathUrl分割,最后将分割后的结果分别通过matchStrings进行匹配。

当匹配后面的换行时,由于这里得到的正则也是.*所以也匹配不到换行符,因此找不到对应的Controller进行处理。

PathPattern
PathPatternsRequestCondition#getMatchingCondition将请求的URL转换为PathContainer对象并调用getMatchingPatterns进行匹配,匹配成功则创建PathPatternsRequestCondition并返回。

遍历PathPattern并进行匹配

通过SeparatorPathElement#matches匹配,SeparatorPathElement是分离器元素,默认是/。

继续往下调用LiteralPathElement#matches逐个字符匹配。

由于PathContainer和PathPattern中这里都保存的是test所以可以通过检测。

后面还有元素所以继续matches

最后针对*通过WildcardPathElement进行匹配。

在WildcardPathElement中只要pathElements的元素个数和PathPattern中的元素个数一致都会返回true。而元素个数的是由/分割的,换行符对元素分割没有影响,因此可以正常匹配。

总结
SpringSecurity的权限认证绕过只能在高版本的springmvc中使用,由于低版本使用AntPathMatcher也是通过正则.*来匹配的URL路径的,因此在绕过SpringSecurity权限认证的同时也会绕过springmvc路由的匹配,导致匹配失败。