風柳メモ

ソフトウェア・プログラミング関連の覚書が中心

Firefox のアドオン(content_scripts)でXMLHttpRequestやfetchを使う場合の注意

先日、Firefox で Twitter メディアダウンローダが正常に動作しないという報告を受け(その後、近傍ツイート検索も異常な動作をすることが判明)、原因を突き止めるのに時間がかかったので、覚え書き。

結論としては、

ということ。





現象

Firefox 上で、Twitter 用のアドオン(Twitter メディアダウンローダ(0.1.1.2801以前)/近傍ツイート検索(0.2.6.1200以前))を動かすと、jQuery.getJSON() や jQuery.ajax() で、本来 JSON が返されるべきところが HTML が返ってきてしまい、正常に動作しない。
基本的には同一のスクリプトであるにも関わらず、Google Chrome 拡張機能版や、Firefox であっても Tampermonkey 上のユーザースクリプトとしてであれば問題なく動作する。

原因

当該関数による GET リクエストの HTTP Request Header に

  • Origin: null がセットされる
  • Referer はセットされない

という動作になっていた。

なお、この現象が発生した際には、当該通信は開発者ツールのネットワークモニタ上に表示されない、という困った不都合もある(こちらは、未だに表示させる方法が不明のまま)。

Twitter 側で(リクエストの正当性検証のため?おそらく 最近になって)Referer等をチェックするようになったことが原因で、正常な応答が返されなくなっていたものと思われる。

追記(2018/07/19)

SWSが本脅威を対策するためには、Refererなどを確認してリクエストの正当性を検証したり、CookieにSameSite属性を付与する手法などが考えられます。

ソーシャルウェブサービスにおける新たなプライバシー脅威「Silhouette」を発見:NTT持株会社ニュースリリース:NTT HOME

実は、このプライバシー脅威(Silhouette)への対策の影響だった、とか?

対策

This is accomplished by exposing more privileged XHR and fetch instances in the content script, which has the side-effect of not setting the Origin and Referer headers like a request from the page itself would, this is often preferable to prevent the request from revealing its cross-orign nature. From version 58 onwards extensions that need to perform requests that behave as if they were sent by the content itself can use content.XMLHttpRequest and content.fetch() instead. For cross-browser extensions their presence must be feature-detected.

Content scripts - Mozilla | MDN

バージョン58以上の Firefox においては、アドオンで content_scripts 内で XMLHttpRequest や fetch (およびそれらを使用している jQuery 等のライブラリ)を使用するときには、window.XMLHttpRequest や window.fetch の代わりに、content.XMLHttpRequest や content.fetch を用いればよいらしい。
これにより、普通にページ(コンテンツ)上から発行されたのと同様に、Origin や Referer がセットされるようになる。

注意事項

content_scripts 内では、window.XMLHttpRequest や window.fetch は read-only になっているため、content.* で上書きしようとしてもできない。

自作スクリプト内ならば、使用するスコープの最初で、

const XMLHttpRequest = ( typeof content != 'undefined' && typeof content.XMLHttpRequest == 'function' ) ? content.XMLHttpRequest  : window.XMLHttpRequest;
const fetch = ( typeof content != 'undefined' && typeof content.fetch == 'function' ) ? content.fetch  : window.fetch;

のようにしておけばよいかも。

問題なのは、jQuery のようなライブラリを使用する場合で、作りによってはソースコードを直接書き換えるしか無いかもしれない。
なお、Firefox のアドオンを AMO にアップする際には、既知のライブラリを使う場合、改変されたものは同梱出来ない、という制約がある。レビューでチェックされ、改変が見つかった場合は当該バージョンが無効化されてしまう(既に経験済み(汗))。

幸い、jQuery の場合には、読み込んだ後にjQuery.ajaxSettings.xhrを次のような感じにオーバーライドしてやればよさげ。
追記と訂正:直接書き換えるよりも、jQuery.ajaxSetup() を介するべきだった。

if ( ( typeof content != 'undefined' ) && ( typeof content.XMLHttpRequest == 'function' ) ) {
    /*
    //jQuery.ajaxSettings.xhr = function () {
    //    try {
    //        return new content.XMLHttpRequest();
    //    } catch ( e ) {}
    //};
    */
    jQuery.ajaxSetup( {
        xhr : function () {
            try {
                return new content.XMLHttpRequest();
            } catch ( e ) {}
        }
    } );
}