Magic7
- 关注
漏洞描述
Apache Shiro before 1.9.1, A RegexRequestMatcher can be misconfigured to be bypassed on some servlet containers. Applications using RegExPatternMatcher with .
in the regular expression are possibly vulnerable to an authorization bypass.
漏洞条件
shiro < 1.9.1
配置了RegExPatternMatcher
配置路径(pattern)中有"."号
漏洞复现
环境配置
shiro : 1.8.0
springboot: 2.7.4
shiro
//先执行
@Bean
ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean bean = new MyShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
bean.setLoginUrl("/login");
bean.setSuccessUrl("/loginSuccess");
bean.setUnauthorizedUrl("/unauthorized");
//[urls]
//url --> filter1,filter2....
LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
map.put("/admin/.*", "authc");
map.put("/login","authc");
bean.setFilterChainDefinitionMap(map);
return bean;
}
private class MyShiroFilterFactoryBean extends ShiroFilterFactoryBean{
//后执行
@Override
public AbstractShiroFilter createInstance() throws Exception{
SecurityManager securityManager = getSecurityManager();
//确保上面的路径配置能够生效
FilterChainManager manager = createFilterChainManager();
//反射访问pathMatcher
PathMatchingFilter filter = (FormAuthenticationFilter) manager.getFilters().get("authc");
Field pathMatcher = PathMatchingFilter.class.getDeclaredField("pathMatcher");
pathMatcher.setAccessible(true);
pathMatcher.set(filter,new RegExPatternMatcher());
// 默认路径"/**"不符合正则表达规范,会报错,所以要去除。可以替换为 "/.*"
((DefaultFilterChainManager) manager).getFilterChains().remove("/**");
PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
chainResolver.setFilterChainManager(manager);
//匹配规则更改为RegEx风格
chainResolver.setPathMatcher(new RegExPatternMatcher());
return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}
}
class SpringShiroFilter extends AbstractShiroFilter {
public SpringShiroFilter(WebSecurityManager securityManager, FilterChainResolver filterChainResolver) {
setSecurityManager(securityManager);
setFilterChainResolver(filterChainResolver);
}
}
上图中标红处,"getChain()"是匹配过滤链的,而匹配到对应链后,由于每个filter是单例的,内部还要再匹配一次其管理的appliedPaths,因此不仅chainResolver需要配置,filter也要配置。
由于"authc"对应的Filter是PathMatchingFilter的子类,其patternMatcher固定为AntPathMatcher,且无法直接访问故需要反射设置。
controller
@GetMapping("/admin/{param}")
public String adminInfo(@PathVariable String param) {
if (!param.isEmpty()){
return "admin info: " + param;
}
return "admin info: no param";
}
测试
正常访问:"/admin/7878"
无法访问,重定向到登入页面恶意访问:"/admin/78%0a78" ,其中"%0a"是换行符
用回车符%0d效果相同:
补充
当controller路径配置为”/admin/info"这种固定的路径时,尽管绕过了shiro,以上两个payload都无法生效(spring无法匹配)
漏洞分析
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
FilterChainManager filterChainManager = getFilterChainManager();
if (!filterChainManager.hasChains()) {
return null;
}
final String requestURI = getPathWithinApplication(request);
final String requestURINoTrailingSlash = removeTrailingSlash(requestURI);
//the 'chain names' in this implementation are actually path patterns defined by the user. We just use them
//as the chain name for the FilterChainManager's requirements
for (String pathPattern : filterChainManager.getChainNames()) {
// If the path does match, then pass on to the subclass implementation for specific checks:
if (pathMatches(pathPattern, requestURI)) {
if (log.isTraceEnabled()) {
log.trace(...);
}
return filterChainManager.proxy(originalChain, pathPattern);
} else {
// in spring web, the requestURI "/resource/menus" ---- "resource/menus/" bose can access the resource
// but the pathPattern match "/resource/menus" can not match "resource/menus/"
// user can use requestURI + "/" to simply bypassed chain filter, to bypassed shiro protect
pathPattern = removeTrailingSlash(pathPattern);
if (pathMatches(pathPattern, requestURINoTrailingSlash)) {
if (log.isTraceEnabled()) {
log.trace(...);
}
return filterChainManager.proxy(originalChain, pathPattern);
}
}
}
return null;
}
正则模式匹配器:
package org.apache.shiro.util;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegExPatternMatcher implements PatternMatcher {
public RegExPatternMatcher() {
}
public boolean matches(String pattern, String source) {
if (pattern == null) {
throw new IllegalArgumentException("pattern argument cannot be null.");
} else {
Pattern p = Pattern.compile(pattern);
Matcher m = p.matcher(source);
return m.matches();
}
}
}
根据测试点号"."是不匹配 \n
和\r
的,因此无法匹配pattern,造成绕过
要想让其能够匹配则必须要多一个参数:Pattern.DOTALL
Pattern pattern = Pattern.compile(regex,Pattern.DOTALL);
漏洞修复
将参数带上即可:[2]
Reference
