#An ActionController::InvalidCrossOriginRequest occurred 问题

突然密集的收到这样的 exception emails: > An ActionController::InvalidCrossOriginRequest occurred in XXXX:

Security warning: an embedded <script> tag on another site requested protected JavaScript. If you know what you’re doing, go ahead and disable forgery protection on this action to permit cross-origin JavaScript embedding.

这个异常的意思是说这些请求触发了 CSRF (Cross-Site Request Forgery)。按照 W3C 的标准,GET 请求应该是对系统无损的(不会修改系统数据),因此不推荐做 CSRF 检测。按照 Rails 官方文档解释也是这样。

排查

一时之间找不到头绪。按照直觉,先看了 IP 地址。并没有发现什么异常。于是去看 User-Agent。下面这个 UA 看起来有点可疑。MQQBrowser 是手机 QQ 浏览器。QQ-URL-Manager 是 QQ 安全管家。所以这有可能是 安全管家发送的请求。那这样的请求有什么问题呢?

  HTTP_USER_AGENT: 
    Mozilla/5.0 (Linux; U; Android 4.4.2; zh-cn; GT-I9500 Build/KOT49H)
    AppleWebKit/537.36 (KHTML, like Gecko)Version/4.0 MQQBrowser/5.0 
    QQ-URL-Manager Mobile Safari/537.36

看到其他的请求中 UA 的信息如下。尽然出现了 curl 和 wget。不管这么多,先用 curl 给系统发个请求看看。果然触发了异常。

  HTTP_USER_AGENT : Wget/1.10.2
  HTTP_USER_AGENT : curl/7.19.7 (x86_64-redhat-linux-gnu) 
    libcurl/7.19.7 NSS/3.13.1.0 zlib/1.2.3 libidn/1.18 libssh2/1.2.2

Google 回来了这份文章:Googlebot causes an invalid Cross Origin Request (COR) on Rails 4.1。他的问题和我们的很相似。解决方法就是把 js 和 html 的处理模块顺序对调,让 html 在前面。按照推荐的修改,再用 curl 访问果然没有问题了。

原因和解决方法

那到底是什么原因呢?原来是 rails 在 4.1 之后,把 js 的请求也纳入到 CSRF 检测中。

2.8 CSRF protection from remote <script> tags

Cross-site request forgery (CSRF) protection now covers GET requests with JavaScript responses, too. That prevents a third-party site from referencing your JavaScript URL and attempting to run it to extract sensitive data.

因为我们的代码这样组织:

1
2
3
4
respond_to do |format|
  format.js # js 的处理模块优先。则碰到 accept: '*/*' 的请求就会落入这个 block,导致 CSRF 异常
  format.html
end

所以碰到爬虫或者是会审查网站安全性的第三方软件的时候,它们会给系统发送 accept 头值为全集请求,导致系统认为它们有攻击意图。修改很简单,把 format.html 提到 format.js 之前就可以了。