缘起
首先,用检查Referer的方式来防披御CSRF并不是很好的方法。因项目临时有需要,所以做为过渡方案。
为什么判断referer不是很好的办法?
- referer 可能为空
- https跳转http没有referer
- https跳转不同的域名的https没有referer
- 通过特殊构造的POST请求没有referer
- 一些的proxy会把referer去掉
- 用户直接在浏览器里访问(GET请求)
- 判断的逻辑复杂(用正则匹配?)
- 友站中招,殃及池鱼
- 可以作为过渡方案,非长久之计
构造空referer请求的一些参考资料
防御CSRF目前比较好的办法是CSRF Token,参考另一篇blog:Cookie & Session & CSRF。
收集资料
先搜索下前人有没有这类相关的工作。
搜索到的关于RefererFilter的信息并不多。
- 是否允许localhost, 127.0.0.1这样referer的请求?
- 是否允许本地的IP/host的请求?
再搜索下java里提取request的referer的方法,还有filter里重定向请求的方法。
再仔细看了下OWASP的文档:
https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet
确定方案
- 默认拦截“POST|PUT|DELETE|CONNECT|PATCH”的请求
- HttpServletRequest里提取到referer
- 用java.net.URL来提取referer里的host
- 判断host是否符合要求,支持完全匹配的域名和子域名
- 不符合要求的请求回应403或者重定向到指定的页面
为什么不用正则的方式来处理referer?
- 正则表达式通常比较慢
- 很难判断一个复杂的正则表达式是否真的正确
- URL是很复杂的,不要手动处理URL,参考URL的语法
思考需要提供的配置项
实际最终提供了这些配置项,考虑到像host这样的配置不是经常变动的,所以没有提供从外部配置文件加载配置的功能。1
2
3
4
5
6
7
8
9
10
11
12matchMethods 即拦截的方法,默认值"POST|PUT|DELETE|CONNECT|PATCH",通常不用配置
allowSubDomainHosts 匹配子域名,以"|"分隔,如"test.com|abc.com",
则http://test.com, http://xxx.test.com这样的请求都会匹配到,推荐优先使用这个配置
completeMatchHosts 完全匹配的域名,以"|"分隔,如"test.com|abc.com",则只有http://test.com 这样的请求会匹配
像http://www.test.com 这样的请求不会被匹配
responseError 被拦截的请求的response的返回值,默认是403
redirectPath 被拦截的请求重定向到的url,如果配置了这个值,则会忽略responseError的配置。
比如可以配置重定向到自己定义的错误页: /referer_error.html
bAllowEmptyReferer 是否允许空referer,默认是false,除非很清楚,否则不要改动这个
bAllowLocalhost 是否允许localhost, 127.0.0.1 这样的referer的请求,默认是true,便于调试
bAllowAllIPAndHost 是否允许本机的所有IP和host的referer请求,默认是false
编码的细节
重定向时,注意加上contextPath
1
response.sendRedirect(request.getContextPath() + redirectPath);
- 构造URL时,非法的URL会抛出RuntimeException,需要处理
正确地处理URL
感觉这个有必要再次说明下:
http://docs.oracle.com/javase/tutorial/networking/urls/urlInfo.html
用contain, indexOf, endWitch这些函数时都要小心。
1 | public static void main(String[] args) throws Exception { |
用curl来测试
最后用curl来做了一些测试:1
2
3
4
5
6curl --header "Referer:http://test.com" http://localhost:8080/filter-test/referer
curl -X POST --header "Referer:http://test.com" http://localhost:8080/filter-test/referer
curl -X POST --header "Referer:xxxxx" http://localhost:8080/filter-test/referer
curl -X POST http://localhost:8080/filter-test/referer
curl -X POST --header "Referer:http://abc.test.com" http://localhost:8080/filter-test/referer
curl -X POST --header "Referer:http://abc.hello.com.test.com" http://localhost:8080/filter-test/referer
实现的代码
1 | import org.slf4j.Logger; |
其它的一些东东
在浏览器里如何访问IPV6的地址?
用”[]”把IPV6地址包围起来,比如localhost的:1
http://[::1]
参考
https://msdn.microsoft.com/en-us/library/windows/desktop/ms740593(v=vs.85).aspx