API安全:CSRF
Posted 阿里云应急响应
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了API安全:CSRF相关的知识,希望对你有一定的参考价值。
好吧,这篇文章要说的是CSRF。我知道这已经是个很老的话题了,也没多少人对它感兴趣。但在做API/微服务架构防御的时候我觉得还是有些值得探讨的地方。
先搞个demo吧,以下是两个servlet,分别处理get和post请求:
@WebServlet("/data/get")
public class GetData extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public GetData() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().print("Received data:"+request.getParameter("data"));
System.out.println("Received data:"+request.getParameter("data"));
}
}
@WebServlet("/data/post")
public class PostData extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public PostData() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().print("Received data:"+request.getParameter("data"));
System.out.println("Received data:"+request.getParameter("data"));
}
}
再来一个响应过滤器:
@WebFilter(filterName = "filter1")
public class CORSResponseFilter implements Filter {
@Override
public void destroy() {
// TODO Auto-generated method stub
}
@Override
public void doFilter(ServletRequest arg0, ServletResponse arg1,
FilterChain arg2) throws IOException, ServletException {
HttpServletResponse resp = (HttpServletResponse) arg1;
HttpServletRequest request = (HttpServletRequest) arg0;
resp.addHeader("Access-Control-Allow-Origin", "*");
arg2.doFilter(request, resp);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}
}
客户端代码:
<html>
<script ></script>
<script>
$.ajax({
url: "http://localhost:8080/TestWeb/data/get",
type: 'GET',
data: { data: "get method" },
success: function(data) {
alert(data);
},
error: function(e) {
alert("Error");
}
});
</script>
<body>
</body>
</html>
对于大多数开发者来说,都知道用Access-Control-Allow-Origin
来防御CSRF,但其实还有更多的问题需要考虑,比如:
1,你的配置是否正确。错误的Access-Control-Allow-Origin
配置并不少见,但这不是我想讨论的。
2,就算正确配置了,万一本站或者子站存在XSS,依旧有可能造成CSRF。而这也不是我想讨论的。。
3,再者就是,就算用了Access-Control-Allow-Origin
,你觉得就够了么?
我们先来做个实验,在响应过滤器中加入以下代码,那么通常的理解应该就是除了来自abc.com的请求,其他源发来的请求应该都无效。resp.addHeader("Access-Control-Allow-Origin", "abc.com")
;
运行客户端代码,得到以下结果:
如预想的一样,无法得到请求结果。但你真的以为Access-Control-Allow-Origin
阻止了这个请求么?完全没有。。。在服务器端的控制台可以看到如下输出:
如果发的是POST请求也会得到一样的结果:
POST请求代码:
<html>
<script ></script>
<script>
$.ajax({
url: "http://localhost:8080/TestWeb/data/post",
type: 'POST',
data: { data: "post method" },
success: function(data) {
alert(data);
},
error: function(e) {
alert("Error");
}
});
</script>
<body>
</body>
</html>
也就是说,就算你正确配置了Access-Control-Allow-Origin
,最多也就是防止别人从响应中偷数据。对于修改数据的请求完全没用,不知道有多少人意识到这点,我刚发现的时候觉得很是蛋疼。
之后呢,有人会说,restful API的GET
请求本就该设计为不修改数据(有木有见过同一个service
同时支持GET
和POST
的?),而POST
请求的话内容应该是JSON,Content-Type
也应该是application/json
,而当不同源客户端修改Content-Type
为application/json
的时候浏览器会进行预检,之后请求就会失败。这个思路是对的,也在此提醒开发者,一定要在服务器端坚持Content-type
,很多搞开发的会忘记这个。
然而,要考虑的不仅有这些。如果把其他响应头设置错误甚至会导致以上的防御失效。来看下一个例子,服务器端检查了所有POST请求的Content-type
,必须存在且必须为application/json
,允许任何源发OPTIONS
请求,其他请求只允许abc.com发,但不小心在Access-Control-Allow-Headers
中加入了Content-type
。
在响应过滤器代码中加入:
if (request.getMethod().equals("POST")
&& (request.getHeader("Content-Type") == null || !request
.getHeader("Content-Type").toLowerCase()
.startsWith("application/json"))) {
resp.setStatus(HttpServletResponse.SC_TEMPORARY_REDIRECT);
return;
}
if(request.getMethod().equals("OPTIONS")){
resp.addHeader("Access-Control-Allow-Origin", "*");
}
else
{
resp.addHeader("Access-Control-Allow-Origin", "abc.com");
}
resp.addHeader("Access-Control-Allow-Headers", "Content-Type");
resp.addHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
客户端代码:
<html>
<script ></script>
<script>
$.ajax({
url: "http://localhost:8080/TestWeb/data/post",
type: 'POST',
contentType: "application/json",
data: JSON.stringify({ data: "post method" }),
success: function(data) {
alert(data);
},
error: function(e) {
alert("Error");
}
});
</script>
<body>
</body>
</html>
结果就是服务器端依旧接收到了这个请求并处理了:
数据值为null是因为客户端发的是JSON格式,服务器端需要特别处理一下才会显示。
至此,你可以看到,在做API CSRF
防护的时候需要很谨慎,稍微配置错就可能导致防护失效。
首先你要确保GET请求就只是用来做数据获取的,而不是修改。
其次服务器端要检查Content-typ
。
然后在设置Access-Control-Allow-Origin
的时候不仅要把域名范围考虑周全,还要把OPTIONS
也考虑进去。
有人会说可以直接服务器端检查referral
或者origin
,这么做也是个解决方案,但并不是最佳实践。而如果遇到有要用API提供文件上传功能的时候,似乎也只能硬着头皮搞了,毕竟上传功能很可能需要支持multi-part
,这种Content-type
是不会触发预检的。
还有。。似乎在IE上预检的触发条件不一样。。。以上的例子在我机器上就没有触发。。
此文写出来仅是希望与大家共同探讨API安全,虽然现在微服务架构的应用还不多,但未来很可能成为趋势。
以上是关于API安全:CSRF的主要内容,如果未能解决你的问题,请参考以下文章