背景概述
公司安全测试要求接口的请求方法只能是GET, POST,并且响应头也只能为GET, POST.
问题描述
在了解到这个需求后,我在过滤器对所有进入服务的请求统一设置响应头:
代码语言:javascript复制@WebFilter(urlPatterns = "/*", filterName = "GlobalFilter")
public class GlobalFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException {
log.info(">>>>>>>>>>>>>>>>>>>> doFilter <<<<<<<<<<<<<<<<<<<<");
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
filterChain.doFilter(servletRequest, response);
}
}
写这段代码是因为我下意识地认为只要设置一个全局的响应头,将 Access-Control-Allow-Methods 设置为GET,POST 就可以轻松实现测试提出的,响应头只能为GET, POST的需求.
这段代码帮我解决了大部分问题,我在自测时“GET,HEAD,POST,PUT,DELETE,OPTIONS”它们的响应头都成功返回了 GET、POST。贴一张 OPTIONS 请求的截图,他的Allow-Methos成功的返回了 GET,POST.
到这你是不是觉得问题就已经解决了?
并没有,事情没这么简单,测试很快就让我打脸了,请看下图,当 uri 改为 * 时,Allow 的返回值让我不敢相信自己的眼睛。
问题分析
在使用 burpsuite 复现问题时,我发现服务的过滤器并没有拦截到 options * 请求,也就是说请求在进入过滤器前就已经被处理并响应了。
那么Filter之前可能会有哪些容器处理 OPTIONS * 请求了呢?
解决方案
找出问题的原因后,我提出了两个解决方案:
方案一:增加一个新的中间件来拦截请求处理不安全的请求方法,例如: Nginx。
方案二:修改Tomcat Adapter, 去除 allow.append("GET,HEAD,POST,PUT,DELETE,OPTIONS").
让我猜猜,故事到这你是不是会秒选方案二。
一旦选择方案二,恶梦就开始了,因为Tomcat 不能直接修改 Adapter, 你以为只需要修改一行代码,实际上你需要重写 ”整个“ Tomcat, 下回我专门写篇长文来讲讲具体是怎么个重写法。
如果选择方案一,恭喜你今天可以正常下班了。
代码语言:javascript复制location * {
if ($request_method = OPTIONS ) {
add_header Content-Length 0;
add_header Content-Type text/plain;
add_header 'Access-Control-Allow-Methods' 'GET, POST';
return 200;
}
}
思考
其实整篇文章看完,会发现我其实只是解决了一个很小的问题,所以我想说的是本篇的重点并不是问题的答案,而是分析并解决问题的思路。
到这你以为就结束了吗?不,其实我还想说一点东西 。
方案的设计目的是实现业务目标,不是为了设计而设计引入各种高大上的中间件,技术自嗨。
虽然引入 Nginx 可以很方便的解决问题,但是它也会带来新的安全风险。系统中的元素越多,为了维持系统的平衡,需要付出的势能必然也越大。系统拆解的粒度越大,各个组件之间的耦合越小,但是解决的组件间协同问题也就越多。在系统设计时,要避免过度设计,把握技术方案的核心目的,在这个基础上进行针对性设计。