freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Spring Security forwardinclude 认证绕过漏洞 CVE-2022-316...
2022-11-09 19:38:57
所属地 北京

漏洞描述

CVE-2022-31692中, 在 Spring Security受影响版本范围内, 在使用 forward/include进行转发的情况下可能导致权限绕过. 但实际测试中发现需要修改为特定配置, 漏洞的实际危害不算太高。

受影响版本

5.7.0 <= Spring Security <= 5.7.4

5.6.0 <= Spring Security <= 5.6.8

POC

SpindleSec/cve-2022-31692: A project demonstrating an app that is vulnerable to Spring Security authorization bypass CVE-2022-31692 (github.com)

成因简析

调用过程中只在进入/forward的时候进行了一次鉴权, 由于在SecurityConfig.java中已配置.antMatchers("/forward").permitAll(), 故未能禁止未授权用户访问. 跳转/admin时未进行权限验证, 造成未授权访问。

复现

正常访问流程

首先看一下直接访问 /admin页面时的正常流程(从鉴权部分开始, 前面 run**invoke和处理其他 filter的部分就不看了)

/* 
this is a part of the stack when processing the invoking
texts in the commentary show the position of the function
the outside are the function in the target line
*/

// ......

chain.doFilter(request, response);
// doFilter:122, ExceptionTranslationFilter (org.springframework.security.web.access)
  /* processing the filter "org.springframework.security.web.access.ExceptionTranslationFilter" */

nextFilter.doFilter(request, response, this);
// doFilter:346, FilterChainProxy$VirtualFilterChain (org.springframework.security.web) [13]

/* 
ATTENTION, plz

now the Filter = "org.springframework.security.web.access.intercept.AuthorizationFilter"
which means it starts dealing with the Authorization, follow it and see what happends before the denying
*/

doFilterInternal(httpRequest, httpResponse, filterChain);
// doFilter:117, OncePerRequestFilter (org.springframework.web.filter) [9]

在进入最后一行的doFilterInternal后, 来到了doFilterInternal:68, AuthorizationFilter (org.springframework.security.web.access.intercept), 关键代码如下:

@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, request);
		this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, request, decision);
		if (decision != null && !decision.isGranted()) {
			throw new AccessDeniedException("Access Denied");
		}
		filterChain.doFilter(request, response);
	}

this::getAuthentication

private Authentication getAuthentication() {
		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
		if (authentication == null) {
			throw new AuthenticationCredentialsNotFoundException(
					"An Authentication object was not found in the SecurityContext");
		}
		return authentication;
	}

public Authentication getAuthentication()

@Override
	public Authentication getAuthentication() {
		return this.authentication;
	}

check()

@Override
	public AuthorizationDecision check(Supplier<Authentication> authentication, T object) {
		boolean granted = isGranted(authentication.get());
		return new AuthorityAuthorizationDecision(granted, this.authorities);
	}

isGranted()

private boolean isGranted(Authentication authentication) {
		return authentication != null && authentication.isAuthenticated() && isAuthorized(authentication);
	}

isAuthorized()

private boolean isAuthorized(Authentication authentication) {
		Set<String> authorities = AuthorityUtils.authorityListToSet(this.authorities);
		for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
			if (authorities.contains(grantedAuthority.getAuthority())) {
				return true;
			}
		}
		return false;
	}

isAuthorized()函数中authoritiesgrantedAuthority的值分别如图, 显然返回false

image.png

check()的返回值, 如图:

image-20221109144635803

然后各函数均返回, 在doFilterInternal中抛出权限错误。

image-20221109145042060

如此便是一个完整的鉴权流程, 接下来看一下/forward中的处理。

攻击访问流程

直接进入鉴权流程

首先获取 filter, 进入处理

image-20221109193307550

doFilter源码

@Override
	public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
			throw new ServletException("OncePerRequestFilter just supports HTTP requests");
		}
		HttpServletRequest httpRequest = (HttpServletRequest) request;
		HttpServletResponse httpResponse = (HttpServletResponse) response;

		String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
		boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;

		if (skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) {
			// Proceed without invoking this filter...
			filterChain.doFilter(request, response);
		}
		else if (hasAlreadyFilteredAttribute) {
			if (DispatcherType.ERROR.equals(request.getDispatcherType())) {
				doFilterNestedErrorDispatch(httpRequest, httpResponse, filterChain);
				return;
			}

			// Proceed without invoking this filter...
			filterChain.doFilter(request, response);
		}
		else {
			// Do invoke this filter...
			request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
			try {
				doFilterInternal(httpRequest, httpResponse, filterChain);
			}
			finally {
				// Remove the "already filtered" request attribute for this request.
				request.removeAttribute(alreadyFilteredAttributeName);
			}
		}
	}

alreadyFilteredAttributeName会获取当前 filter的名称, 若hasAlreadyFilteredAttribute=true, 即已经调用过这条过滤器, 则不进行重复调用, 否则将其标记后进行调用

开始对/forward鉴权

image-20221109150907038

/forward鉴权,check()源码如下:

@Override
	public AuthorizationDecision check(Supplier<Authentication> authentication, HttpServletRequest request) {
		if (this.logger.isTraceEnabled()) {
			this.logger.trace(LogMessage.format("Authorizing %s", request));
		}
		for (RequestMatcherEntry<AuthorizationManager<RequestAuthorizationContext>> mapping : this.mappings) {

			RequestMatcher matcher = mapping.getRequestMatcher();
			MatchResult matchResult = matcher.matcher(request);
			if (matchResult.isMatch()) {
				AuthorizationManager<RequestAuthorizationContext> manager = mapping.getEntry();
				if (this.logger.isTraceEnabled()) {
					this.logger.trace(LogMessage.format("Checking authorization on %s using %s", request, manager));
				}
				return manager.check(authentication,
						new RequestAuthorizationContext(request, matchResult.getVariables()));
			}
		}
		this.logger.trace("Abstaining since did not find matching RequestMatcher");
		return null;
	}

由于在SecurityConfig.java中已配置.antMatchers("/forward").permitAll(), 故check()直接跳转到permitAllAuthorzationManager, 验证直接通过, 相当于无权限验证。

image-20221109154053967

/forword请求更新为/admin请求后, 在processRequest(request,response,state)处进入处理调用

而后跟进到调用过滤器处, 为

filterChain.doFilter(request, response);
// invoke:711, ApplicationDispatcher (org.apache.catalina.core)

跟进, 运行到处理org.springframework.security.web.access.intercept.AuthorizationFilter过滤器处。

image-20221109174824401

跟进, 看看与直接访问/admin的区别。

image-20221109180418267

Proceed without invoking this filter...

破案...

成因总结

forward方法执行跳转, 且权限为permitAll. 而又有OncePerRequestFilter的条件, 只有一次使用org.springframework.security.web.access.intercept.AuthorizationFilter鉴权的机会, 该机会直接被浪费. 跳转/admin后过滤器无法进行处理, 从而越权。

修复方案

禁用OncePerRequestFilter功能或保证访问/forward跳转的目标网页所需的权限小于访问/forward的权限。

官方的修复方案是在AuthorizationFilter.java中新增了一个判断, 判断是否在当前request中使用过。

/*
* verify whether the filter has been set observeOncePerRequest = true and applied
*/
if (this.observeOncePerRequest && isApplied(request)) {
    chain.doFilter(request, response);
    return;
} 

if (skipDispatch(request)) {
    chain.doFilter(request, response);
    return;
}

String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
    this.authorizationManager.verify(this::getAuthentication, request);
    chain.doFilter(request, response);
}
finally {
    request.removeAttribute(alreadyFilteredAttributeName);
	}
}
# 漏洞 # web安全
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录