Referrer を制御する

  • 271
    Like
  • 1
    Comment

Web ブラウザーは通常 HTTP 要求の Referer: ヘッダーに参照元ページの URL を入れますが (あるいは document.referrer で参照元ページの URL を取得できますが)、 Web サイト側でこれを制御したいことがあります。

例えば、次のような場面が想定されます。

  • URL にユーザー名や秘密の ID などを含めざるを得ない時は、プライバシーやセキュリティーの観点から、この URL を外部に漏らしたくありません。
  • 社内システムに URL を貼りたいことがありますが、社内システムの URL を外部に漏らしたくありません。
  • Web アプリケーションの開発用サーバーは、その所在を外部に漏らしたくありません。
  • 投稿者と友達のみに公開される SNS の投稿にリンクが含まれる時、その個別 URL を漏らしたくありません。 (SNS 全体の URL が漏れることは問題ありません。場合によっては、流入元を示すため、敢えて残したいです。)
  • 提携 Web サイト間での利用者の動向を分析するに当たり、 https: から http: へのリンクであっても Referer: が送信されてほしい場合もあります。
  • 外部リンクで Referer: は送出したくないとしても、同じサイトの POST では CSRF 検査のため Referer:Origin: を送ってほしいことがあります。

このような場合に、 Web サイト側で出来る対策を、古典的な方法から順に紹介します。

この記事は2013年に執筆したものですが、2016年に最新の状況に合わせて改訂しています。

中間ページを挟む

この方法は、おすすめできません。

本来のリンク先の URL に直接リンクするのではなく、その URL へのリンクを含むページへリンクします。例えば、 http://www.example.com/ にリンクしたいなら、 http://redirector.example.com/http://www.example.com/ にリンクして、そちらでは http://www.example.com/ へのリンクが表示されるようにします。 www.example.com に送られる Referer:redirector.example.com の URL になりますから、元のページの URL は漏れません。

これだけでは閲覧者が余分にリンクをたどる必要がありますから、 redirector.example.com にはリンクを自動的にたどる機能があると便利です。 <meta http-equiv=Refresh>location.replace を使ったり、リンクの a 要素の click メソッドを呼び出したりする方法があります。これらの場合には仕様上は redirector.example.comReferer: が送られるはずですが、ブラウザーによっては送られないことがあるようです。ただ単に特定のページの Referer: を送らないようにしたいのではなく、その値を制御したいのであれば、どの方法を採るか検討する必要がありそうです。なお、 location.href への代入だと履歴に余分なページが残ってしまい、「戻る」が正常に動かなくなってしまいます。また、 HTTP リダイレクトは元の www.example.com の URL がリダイレクト先の Referer: にも使われてしまうので、この目的では使えません。

この方法は img 要素などには使えません。

data: URL を使えば中間ページを別途用意する必要はありません。ただし対応していない古い Web ブラウザーではリンクをたどれなくなってしまいます。

<a href="data:text/html,<a href='http://www.example.com/'>進む</a>">リンク</a>

どの方法もリンクが汚くなってしまうのが難点です。 CSS の :visited や Web ブラウザーの「リンクの URL をコピー」のような機能が思った通りに動かなくなりますし、制作者側もリンク時に一手間必要になります。

10年以上前から 2ch で使われている ime.nu が有名です。かつてはこれがほとんど唯一の方法で、よく使われていました。

HTTPS (HTTP/TLS or HTTP/SSL) を使う

この方法は、今となっては不適切です。

https: から http: へのリンクでは、 Referer: は送らないことになっています。これを利用して、漏らしたくない URL を含むホスト全体を https で提供すれば、 http: URL への参照があっても Referer: の送信を抑制できます。

TLS (SSL) 証明書を用意しなければならないなど多少の手間はありますが、対象となるページやリンク自体に手を入れなくてもいいという意味では、とても手軽な方法です。

ただし、 https: のページへの Referer: は送信されてしまいます。 https: 化することで Referer: が送信されなくなったと安心してしまいがちですが、実際には対策として不完全で、危険です。最近は https: を使うのが当然という流れになってきていますから、 Referer: が送信されてしまうケースはどんどん増えていきます。

rel=noreferrer

a 要素や area 要素に rel=noreferrer を指定すると、そのリンクは Referer: を送信しなくなります。

<a href="http://www.example.com/" rel=noreferrer>リンク</a>

リンクそれぞれで Referer: を送信するか制御できるので便利ですが、先述のような用途だと指定漏れのおそれがあり、あまり安心できないかもしれません。 CMS などでサーバーやページ全体を変更できない場合には、自分で編集できるリンクに属性を指定するだけで良くて便利かもしれません。

Chrome、Safari など WebKit/Blink 系のブラウザーは早くから実装しています。 Firefox でも実装されました。以前は未対応の Web ブラウザーがまだ多いのが難点でしたが、現在となってはかなり実用的な方法です。

この方法は img 要素などには使えません。

この機能は HTML Standard で規定されています。

<meta name=referrer>

<meta name=referrer> は、ページ内のリンク等で referrer をどう処理するべきかを指定するものです。

Referer: の送信の有無に加え、送る場合に origin (URL scheme とホストの部分のみで、 path や query を除いたもの。) だけを送るオプションも選ぶことができます。 URL の path や query の部分に漏らしたくない文字列が含まれていても、どの Web サイトからの流入なのかリンク先に伝えたいなら、 origin のみ送信すれば良いというわけです。

  • 実際に Google 検索は origin のみを送るように指定されており、 query に含まれる検索キーワードは Referer: に含まれませんが、 Google のリンクをたどったアクセスであることは伝わるようになっています。
  • ユーザーIDがURLに含まれるソーシャルブックマークサービスや友達のみに公開される SNS の記事などで、プライバシー保護のため origin のみ送るように指定したいかもしれません。
<!-- Referer: を送らない -->
<meta name="referrer" content="no-referrer">
<!-- content="never" は旧仕様 -->
<!-- Referer: に origin のみ含める -->
<meta name="referrer" content="origin">

この指定はすべてのリンクに適用されますし、 imgiframe などにも有効です。また、通常 Referer: を送信しない https: のページから http: の URL への参照でも Referer: を送信するように指定できます。

(リンクごとに個別に指定することはできませんが、スクリプトで動的に書き換えて、同じドメインのサイトには URL 全体を、違うドメインのサイトには origin だけを送るように無理矢理しているサイトもあるようですw さすがにどうかと思いますが...)

unsafe-url という値を使うと、通常は referrer が送られないことになっている、 https: のページから http: URL へのリンクでも、 referrer を送信することができます。近年 HTTPS への移行が進んでいるとはいえ、まだ http: しかない Web サイトも多いですが、流入元が自サイトであることを知らせたいとき、便利です。 (もちろん、自ページの URL が流出するべきでないものであるとき、この値を使ってはいけません。値に「unsafe」とある通り、 https: の URL が平文で送信されることになります。)

利用できる値と動作をまとめると、次の表のようになります。

同じ origin 同じ scheme、http:https: https:http: 旧仕様の値
no-referrer 送信しない 送信しない 送信しない never
origin origin のみ origin のみ origin のみ
no-referrer-when-downgrade 送信する 送信する 送信しない default
origin-when-cross-origin 送信する origin のみ origin のみ
unsafe-url 送信する 送信する 送信する always

本記事の最初の執筆時点では残念ながらまだ Chrome しか実装していませんでしたが、現在は Firefox でも 実装されています

※ Safari と Edge は「旧仕様の値」にしか対応していません。「旧仕様の値」は新たに使うべきではないのですが、当面はやむなくそちらを使う方が良い場面もありそうです。「旧仕様の値」は Chrome や Firefox のような新しい Web ブラウザーでも正常に認識されます。

この他に、新たに追加された same-origin, strict-origin, strict-origin-when-cross-origin という値があります。まだ実装されていないかもしれませんが、

<meta name="referrer" content="origin">
<meta name="referrer" content="strict-origin">

... のように複数の値を指定すると、ブラウザーが対応しているもので最後に指定された値が適用されます。この例では、 古めのブラウザーなら origin が、新しいブラウザーなら strict-origin が適用されます。

この機能は HTML Standard で規定されています。 (当初 WHATWG Wiki で提案され、いずれ HTML Standard にも追加されるだろうと思われていましたが、Referrer Policy という仕様書で標準化されることになりました。この時、指定できる値が一部変更されました (旧仕様の値も認識はされますが、新しい値を使うべきだとされています)。その後改めて HTML Standard と統合されました。)

CSP

この方法は、廃止されました。

CSP (HTTP Content-Security-Policy: ヘッダー) で <meta name=referrer> 相当の指定をできるようにすることが検討されていました

Content-Security-Policy: referrer no-referrer;

個別の Web ページを書き換えずに Web サイト全体に適用したいときに、サーバーの設定を書き換えるだけで済むのは便利かもしれません。

Referrer Policy 仕様書で規定されていました。

本記事の最初の執筆時点ではどの Web ブラウザーも実装していませんでしたが、その後 Chrome と Firefox が実装しました。

しかし、 CSP よりも独立した HTTP ヘッダーとするのが適切と判断され、この方法は削除されました。

referrerpolicy=""

新たに a 要素、 img 要素などに referrerpolicy 属性が追加され、 <meta name=referrer> と同様の指定がリンクごとに行えるようになります。

この機能は HTML Standard で規定されています。 (当初は Referrer Policy 仕様書で規定されていました。)

request.referrerPolicy

新しい fetch API で使う Request オブジェクトには referrerPolicy 属性があり、 <meta name=referrer> 同様の値を指定できます。

Fetch Standard で規定されています。

Referrer-Policy: ヘッダー

CSP の方法にかわって、新設の独立した HTTP ヘッダー Referrer-Policy<meta name=referrer> 同様の値を指定できるようになります。

Referrer-Policy: origin

昔からある Referer ヘッダーとは違って、綴りが Referrer となっていることに注意が必要です。

Referrer Policy 仕様書 (と HTML Standard) で規定されています。

history.replaceState

ここまでの方法は Referer: の送信自体を制御していましたが、 Referer: で送信される URL の方を変更する手もあります。

history.replaceState を使うと現在のページの表示はそのままで、 URL を書き換えることができます (いわゆる Pjax です)。そうすると Referer: で送られる URL も変更されます。ただし新しい URL は元の URL と同じホスト (正確には同じ origin) でなければなりません。サーバーの存在自体を漏らしたくないときには使えませんが、 path や query に漏らしたくない文字列が含まれているときには有用です。なお history.pushState では Web ブラウザーの履歴に余分な項目が追加されてしまいますから不適当です。

注意したいのは、書き換えた新しい URL も同じ内容のページを表すものでないといけないことです。そうしないと「戻る」で移動してきた時に意図しないページに戻ってしまいます。 replaceState の第1引数には任意のデータを保持しておけますから、書き換える前の path や query に含まれていた情報をそこに入れておいて、書き換え先の URL はそれを使って (history.state でアクセスできます。) 元のページを復元すると良いでしょう。

<script>
  var m = location.href.match(/\/([^\/]+)\/$/);
  if (m) {
    history.replaceState ({user_name: m[1]}, document.title, location.href.replace(/\/[^\/]+\/$/, '/my/'));
  }
</script>

この方法は主要 Web ブラウザーすべてで使えます。実装の始まった頃には Referer: の URL が変更されないブラウザーもありましたが、現在では完全に無視して問題ありません。

サーバーの設定を変更したり個別のリンクに手を入れたりせずに、すべての Web ブラウザーで Referer: を制御できるのはこの方法だけです。

まとめ

Web ブラウザーの Referer: の送出を Web サイト側で制御する方法を紹介しました。本記事の最初の執筆 (2013年) 時点では、どのブラウザーでも実装されている replaceState の方法が (手間がかかるものの) 最善かもしれないと述べていました。現在では <meta name=referrer> が最も使い勝手が良さそうですが、ブラウザーによっては新しい値に対応していなかったりするので、注意が必要です。

ところで Referer: HTTP ヘッダーの名前以外はすべて「r」が2つの referrer ですので注意しましょう。

  • Referrer (r が2つの正しい綴り)
    • document.referrer (DOM)
    • request.referrerPolicy (DOM)
    • rel=noreferrer (HTML)
    • referrerpolicy="" (HTML)
    • <meta name=referrer> (HTML)
    • Referrer-Policy: (HTTP)
    • referrer no-referrer (CSP)
  • Referer (r が1つ)
    • Referer: (HTTP)