源码分析异常错误The request was rejected because the URL contained a potentially malicious String “//“

项目上线之后经常会在日志当中看到如下错误:

org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL contained a potentially malicious String "//"
	at org.springframework.security.web.firewall.StrictHttpFirewall.rejectedBlocklistedUrls(StrictHttpFirewall.java:456)
	at org.springframework.security.web.firewall.StrictHttpFirewall.getFirewalledRequest(StrictHttpFirewall.java:429)
	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:196)
	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:183)
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354)
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:96)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:890)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1743)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:748)

这是什么原因呢,我们可以在spring的源码FilterChainProxy类中看到(FilterChainProxy的作用是管理一组过滤器,定义请求的安全验证和处理顺序,并提供基于拦截规则的安全功能。它是Spring
Security框架中实现安全过滤器链的核心组件。)

public class FilterChainProxy extends GenericFilterBean {

    private static final Log logger = LogFactory.getLog(FilterChainProxy.class);

    private static final String FILTER_APPLIED = FilterChainProxy.class.getName().concat(".APPLIED");

    private List<SecurityFilterChain> filterChains;

    private FilterChainValidator filterChainValidator = new NullFilterChainValidator();

    private HttpFirewall firewall = new StrictHttpFirewall();

    private RequestRejectedHandler requestRejectedHandler = new DefaultRequestRejectedHandler();

    public FilterChainProxy() {
    }

    public FilterChainProxy(SecurityFilterChain chain) {
        this(Arrays.asList(chain));
    }

    public FilterChainProxy(List<SecurityFilterChain> filterChains) {
        this.filterChains = filterChains;
    }

    @Override
    public void afterPropertiesSet() {
        this.filterChainValidator.validate(this);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
        if (!clearContext) {
            doFilterInternal(request, response, chain);
            return;
        }
        try {
            request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
            doFilterInternal(request, response, chain);
        } catch (RequestRejectedException ex) {
            this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response, ex);
        } finally {
            SecurityContextHolder.clearContext();
            request.removeAttribute(FILTER_APPLIED);
        }
    }

    private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        //走到了这里,这里的firewall使用的是StrictHttpFirewall
        FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);
        HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);
        List<Filter> filters = getFilters(firewallRequest);
        if (filters == null || filters.size() == 0) {
            if (logger.isTraceEnabled()) {
                logger.trace(LogMessage.of(() -> "No security for " + requestLine(firewallRequest)));
            }
            firewallRequest.reset();
            chain.doFilter(firewallRequest, firewallResponse);
            return;
        }
        if (logger.isDebugEnabled()) {
            logger.debug(LogMessage.of(() -> "Securing " + requestLine(firewallRequest)));
        }
        VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
        virtualFilterChain.doFilter(firewallRequest, firewallResponse);
    }
}

代码执行到这里抛出了异常this.firewall.getFirewalledRequest((HttpServletRequest) request);
我们可以从上面代码看到firewall使用的是new StrictHttpFirewall(),让我们进入到StrictHttpFirewall的getFirewalledRequest看看:

public class StrictHttpFirewall implements HttpFirewall {
    @Override
	public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException {
		rejectForbiddenHttpMethod(request);
		rejectedBlocklistedUrls(request);
		rejectedUntrustedHosts(request);
		if (!isNormalized(request)) {
			throw new RequestRejectedException("The request was rejected because the URL was not normalized.");
		}
		String requestUri = request.getRequestURI();
		if (!containsOnlyPrintableAsciiCharacters(requestUri)) {
			throw new RequestRejectedException(
					"The requestURI was rejected because it can only contain printable ASCII characters.");
		}
		return new StrictFirewalledRequest(request);
	}

	private void rejectForbiddenHttpMethod(HttpServletRequest request) {
		if (this.allowedHttpMethods == ALLOW_ANY_HTTP_METHOD) {
			return;
		}
		if (!this.allowedHttpMethods.contains(request.getMethod())) {
			throw new RequestRejectedException(
					"The request was rejected because the HTTP method \"" + request.getMethod()
							+ "\" was not included within the list of allowed HTTP methods " + this.allowedHttpMethods);
		}
	}

	private void rejectedBlocklistedUrls(HttpServletRequest request) {
		for (String forbidden : this.encodedUrlBlocklist) {
		    //这个方法抛出了错误
			if (encodedUrlContains(request, forbidden)) {
				throw new RequestRejectedException(
						"The request was rejected because the URL contained a potentially malicious String \""
								+ forbidden + "\"");
			}
		}
		for (String forbidden : this.decodedUrlBlocklist) {
			if (decodedUrlContains(request, forbidden)) {
				throw new RequestRejectedException(
						"The request was rejected because the URL contained a potentially malicious String \""
								+ forbidden + "\"");
			}
		}
	}
    private static boolean encodedUrlContains(HttpServletRequest request, String value) {
        if (valueContains(request.getContextPath(), value)) {
            return true;
        }
        return valueContains(request.getRequestURI(), value);
    }
    private static boolean valueContains(String value, String contains) {
        return value != null && value.contains(contains);
    }
}

从以上代码我们找出来了原因,因为用户请求的路径中包含spring制定的违禁地址,所以抛出了RequestRejectedException异常

然而从FilterChainProxy中我们可以看到RequestRejectedException被requestRejectedHandler捕获了,而且处理器默认使用的是DefaultRequestRejectedHandler处理器,那DefaultRequestRejectedHandler是如何实现的呢?

public class DefaultRequestRejectedHandler implements RequestRejectedHandler {

	@Override
	public void handle(HttpServletRequest request, HttpServletResponse response,
			RequestRejectedException requestRejectedException) throws IOException, ServletException {
		throw requestRejectedException;
	}

}

从DefaultRequestRejectedHandler源码中我们可以看到,他是直接把异常给抛出去了,所以会导致出现很多的RequestRejectedException错误日志

那么如何解决呢?
从上面的源码分析,我们可以看到问题出在firewall的校验url方法,我们可以替换为spring的DefaultHttpFirewall类,但是我个人不建议这么做。

还有一种方法,requestRejectedHandler的默认处理器DefaultRequestRejectedHandler直接抛出了异常导致程序出现异常信息,对于Spring安全版本5.4和更高版本,我们可以简单地创建一个类型为RequestRejectedHandler的bean,该bean将被注入Spring安全过滤器链中:

import org.springframework.security.web.firewall.HttpStatusRequestRejectedHandler;
import org.springframework.security.web.firewall.RequestRejectedHandler;
 
@Bean
    public RequestRejectedHandler requestRejectedHandler() {
        // sends an error response with a configurable status code (default is 400 BAD_REQUEST)
        // we can pass a different value in the constructor
        return new HttpStatusRequestRejectedHandler();
    }

附下HttpStatusRequestRejectedHandler的源码:

public class HttpStatusRequestRejectedHandler implements RequestRejectedHandler {

	private static final Log logger = LogFactory.getLog(HttpStatusRequestRejectedHandler.class);

	private final int httpError;

	/**
	 * Constructs an instance which uses {@code 400} as response code.
	 */
	public HttpStatusRequestRejectedHandler() {
		this.httpError = HttpServletResponse.SC_BAD_REQUEST;
	}

	/**
	 * Constructs an instance which uses a configurable http code as response.
	 * @param httpError http status code to use
	 */
	public HttpStatusRequestRejectedHandler(int httpError) {
		this.httpError = httpError;
	}

	@Override
	public void handle(HttpServletRequest request, HttpServletResponse response,
			RequestRejectedException requestRejectedException) throws IOException {
		logger.debug(LogMessage.format("Rejecting request due to: %s", requestRejectedException.getMessage()),
				requestRejectedException);
		response.sendError(this.httpError);
	}

}

如果您对我的问题分析感兴趣,不妨看看之前的文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据提供的引用内容,可以看出这是一个Spring Security的异常,它是由于URL中包含了潜在的恶意字符串而被拒绝的。其中"%3B"和"//"都是常见的恶意字符串,因为它们可以用于绕过URL过滤器,执行一些恶意操作,例如SQL注入、跨站脚本攻击等。 为了解决这个问题,可以采取以下措施: 1. 在Spring Security配置中启用URL编码过滤器,它可以自动将URL中的特殊字符进行编码,从而防止恶意字符串的出现。 2. 对于一些敏感的URL,可以采用白名单机制,只允许特定的URL通过过滤器,其他的URL都将被拒绝。 3. 对于一些需要包含特殊字符的URL,可以采用安全编码的方式,例如Base64编码,将特殊字符转换为安全的字符,从而避免恶意字符串的出现。 ```java // 启用URL编码过滤器 @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .and() .httpBasic() .and() .csrf() .ignoringAntMatchers("/h2-console/**") // 忽略H2控制台的CSRF保护 .and() .headers() .frameOptions().sameOrigin() // 允许H2控制台的iframe加载 .and() .addFilterBefore(new RequestRejectedExceptionFilter(), ChannelProcessingFilter.class) // 添加URL编码过滤器 .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); } } // URL编码过滤器 public class RequestRejectedExceptionFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String requestUri = request.getRequestURI(); if (requestUri.contains("%3B") || requestUri.contains("//")) { throw new RequestRejectedException("The request was rejected because the URL contained a potentially malicious String"); } filterChain.doFilter(request, response); } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值