前言
无。
环境搭建
按照漏洞通告,漏洞影响版本1.9.1之前的shiro,于是配置1.9.0版本的shiro依赖:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.9.0</version>
</dependency>
漏洞分析
根据漏洞通告,问题就是RegExPatternMatcher类处理’.’这个正则字符时可能导致认证绕过,根据参考文章所说,也就是常常看到的不加修饰符情况下’.’不会匹配的问题。
RegExPatternMatcher类是使用Java内部提供的Pattern类完成的正则表达式解析工作:
public boolean matches(String pattern, String source) {
if (pattern == null) {
throw new IllegalArgumentException("pattern argument cannot be null.");
}
Pattern p = Pattern.compile(pattern);
Matcher m = p.matcher(source);
return m.matches();
}
简单测试一下:
@RequestMapping("/test")
public String test() {
RegExPatternMatcher regExPatternMatcher = new RegExPatternMatcher();
System.out.println(regExPatternMatcher.matches(".*", "Twings\n"));
return "index";
}
可以看到此时RegExPatternMatcher无法匹配换行符,导致匹配失败,可以通过向URI写入换行符的方式绕过路径匹配。
然后问题就来了,RegExPatternMatcher并不是默认的路径匹配器,为了使用它我们还需要自定义一个东西将它配置进去。
搜索一下PatternMatcher.matches()函数会被调用的地方:
PathMatchingFilterChainResolver类的pathMatcher不好改也一般都不会改,可以看到另一个调用点是PathMatchingFilter类的pathsMatch函数会调用该函数:
protected boolean pathsMatch(String pattern, String path) {
boolean matches = pathMatcher.matches(pattern, path);
log.trace("Pattern [{}] matches path [{}] => [{}]", pattern, path, matches);
return matches;
}
其调用来自用于处理URI中非法字符的子类InvalidRequestFilter:
那么最容易想到的办法就是写一个继承了PathMatchingFilter的Filter,然后设置进shiro里面去。
先试一试,写一个自定义Filter:
public class MyFilter extends PathMatchingFilter {
public MyFilter() {
this.pathMatcher = new RegExPatternMatcher();
}
}
然后添加到shiro里面:
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(new MyRealm());
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
HashMap<String, Filter> filters = new HashMap();
filters.put("myFilter", new MyFilter());
shiroFilterFactoryBean.setFilters(filters);
map.put("/admin/.*", "myFilter");
没触发,看看是什么原因。
原因在于Tomcat要通过ApplicationFilterConfig获取要调用的filter,而在一般情况下,shiro配置的是一个DefaultWebSecurityManager的类中类SpringShiroFilter对象,而该类在实例化时会配置一个FilterChain生成器:
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);
}
}
往上追溯可以看到这是一个PathMatchingFilterChainResolver对象:
PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
chainResolver.setFilterChainManager(manager);
return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
这个FilterChain生成器在进行路径匹配时使用的是AntPathMatcher这个路径匹配器:
public PathMatchingFilterChainResolver() {
this.pathMatcher = new AntPathMatcher();
this.filterChainManager = new DefaultFilterChainManager();
}
看起来这个漏洞不是这么玩的。
换句话说就是要触发这个漏洞,我们要做的不是自定义一个使用RegExPatternMatcher这个路径匹配器的Filter然后配置到shiro里面,而是将SpringShiroFilter-filterChainResolver-pathMatcher重新配置为RegExPatternMatcher,也就是通过上文中PatternMatcher.matches()的另一个触发点。
为了实现这个目标,按照正常项目需求应该就是自定义一个ShiroFilterFactoryBean,直接把ShiroFilterFactoryBean的大部分代码复制过去就行:
public class MyShiroFilterFactoryBean extends ShiroFilterFactoryBean {
@Override
protected AbstractShiroFilter createInstance() {
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();
PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
chainResolver.setPathMatcher(new RegExPatternMatcher());
chainResolver.setFilterChainManager(manager);
return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}
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);
}
}
}
}
再把shiro config还原一下。
然而问题又来了,此时应用环境还是没有配置完全,尽管给需要认证的路由配置了authc的权限要求,但是由于shiro内部的认证用的是filter对象FormAuthenticationFilter,它父类PathMatchingFilter的preHandle函数还要进行一次pathsMatch,里面使用的仍然是AntPathMatcher。
所以还得写一个自定义filter用于鉴权:
public class MyFilter extends AccessControlFilter {
public MyFilter() {
this.pathMatcher = new RegExPatternMatcher();
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
saveRequestAndRedirectToLogin(request, response);
return false;
}
}
再修改一下Config:
@Configuration
public class MyShiroConfig {
@Bean
public ShiroFilterFactoryBean myShiroFilterFactoryBean() {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(new MyRealm());
MyShiroFilterFactoryBean myShiroFilterFactoryBean = new MyShiroFilterFactoryBean();
myShiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
HashMap<String, Filter> filters = new HashMap<>();
filters.put("myFilter", new MyFilter());
myShiroFilterFactoryBean.setFilters(filters);
Map<String, String> map = new HashMap<>();
map.put("/shiro", "authc");
map.put("/logout", "user");
map.put("/user", "user");
map.put("/admin/.*", "myFilter");
myShiroFilterFactoryBean.setLoginUrl("/index");
myShiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return myShiroFilterFactoryBean;
}
}
此时能正确拦截对/admin路由的访问:
加入换行符,发现出现了路由匹配问题:
java.util.regex.PatternSyntaxException: Dangling meta character '*' near index 2
/**
^
追溯一下,可以看到ShiroFilterFactoryBean在调用createFilterChainManager函数创建FilterChainManager时会同时创建一个默认路由:
// create the default chain, to match anything the path matching would have missed
manager.createDefaultChain("/**"); // TODO this assumes ANT path matching, which might be OK here
然而这个写法是给AntPathMatcher用的,在RegExPatternMatcher里会发生错误,改一下自定义ShiroFilterFactoryBean的createInstance函数:
FilterChainManager manager = new DefaultFilterChainManager();
manager.addFilter("myFilter", new MyFilter());
manager.addToChain("/admin/.*", "myFilter");
再删掉Config中的路由配置,可以看到换行符可以成功绕过认证:
漏洞利用
如上。
后记
利用范围好像有点小。
参考
【技术干货】 CVE-2022-32532 Apache Shiro RegExPatternMatcher 认证绕过漏洞
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!