漏洞描述
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";
}
测试:
漏洞分析
OncePerRequestFilter所在类图:
左边的分支是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:
请求转发后SpringShiroFilter第二次进入:
由于请求转发后的请求本质上还是同一个请求对象,因此会进入图中分支。这里filterChain是ApplicationFilterChain,也就是web容器内的过滤链,filterChian.doFilter()
将会导致,filterChian直接跨过当前的SpringShiroFilter,进入下一个过滤器(或直接进入servlet),从而绕过shiro。
漏洞修复
改变了判定条件:[3]
但是这个补丁,是有限制的,必须要用上文环境部分的【补丁必要配置】,才能够生效
为什么要有【补丁必要配置】?
其实如果没有【补丁必要配置】,则转发的请求根本不会进入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条件下,不用【补丁必要配置】(其余不变),发现是可以绕过的。这里就不展开了
Reference
[1] Security Reports | Apache Shiro
[2] 关于filter过滤器为何不能过滤转发的请求 - mrsl - 博客园
[3] commit
4A评测 - 免责申明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。
不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。
如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!
程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。
侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)