追記(2015年9月9日)

以下の記事は、2015年1月4日時点での内容となります。

2015年9月9日時点において、IE11で:visitedなリンクに対して透過度を変更できる仕様は修正されているようです。

参考: CVE-2015-1765, Internet Explorer 用の累積的なセキュリティ更新プログラム (3058515)

:visitedセレクタについて

半年以上前に少し調べたことについてまとめてみたいと思います。

まずCSSの擬似セレクタに:visitedがあり、:visitedセレクタではプライバシーの問題上、使用可能なプロパティが制限されていることはご存知の方も多いと思います。

訪問済みリンクの要素にbackground-imageを設定することで、訪問済みリンクの情報が漏れるという問題等の対策です。
(Calm brilliance – CSS Fingerprint: preliminary data)

詳細は Privacy and the :visited selector にある通りですが、そこでは

You will still be able to visually style visited links, but there are now limits on what styles you can use. Only the following properties can be applied to visited links:

– color
– background-color
– border-color (and its sub-properties)
– outline-color
– The color parts of the fill and stroke properties

In addition, even for the properties you can set for visited links, you won’t be able to change the transparency between unvisited and visited links, as you otherwise would be able to using rgba() or hsla() color values or the transparent keyword.

とあります。

CSS Fingerprint対策の他に、レンダリング速度で差が出ることで訪問済みかどうか判断できないようにRGB値以外のものは変化しないようになっています。(透過度も不可)

またJavaScriptでも訪問済みサイトを判定できないようにするため、getComputedStyleでstyleを取得した際は非訪問時のstyleが返るようになっています。

各ブラウザでの実装

ただし上記はMozillaの方針でして、他ブラウザでの実装はそれによりまちまちのようです。

W3Cのほうでは以下の様な記述はありますが、具体的な内容については見つかりませんでした。)

UAs may therefore treat all links as unvisited links, or implement other measures to preserve the user’s privacy while rendering visited and unvisited links differently.

試しに http://jsfiddle.net/tyage/fhrojfdn/ を表示させてみました。


Google Chrome (39.0.2171.95)

スクリーンショット 2015-01-02 16.17.33

Firefox (33.1.1)

スクリーンショット 2015-01-02 16.18.41

Safari (8.0.2)

スクリーンショット 2015-01-02 16.20.18

Internet Explorer (11.0.7)

スクリーンショット 2015-01-02 16.20.46


上が:visitedで透過度を変化させた場合、下が色だけを変化させた場合です。

おっと。IEでは:visitedで透過度を変化させることができるようです。

Chromeではなぜかアンダーラインだけ適用されています。

これによって描画にかかる時間の差から、訪問済みか訪問済みでないかが分かる可能性がありますが、うまくテストできなかったのでスキップします…

Twitter IDの表示

透過度を変化させ、SNSのIDを表示することを考えてみました。

Twitterを例に出すと、https://analytics.twitter.com にアクセスした場合 https://analytics.twitter.com/user/[id]/tweets にリダイレクトされます。

これを利用して、https://analytics.twitter.com/user/aaa/tweets から https://analytics.twitter.com/user/zzz/tweets までのリンクを全て重ねて表示するページを作ってみます。
(訪問済みリンク以外はtransparentになるCSSを利用するため、IEのみで動作します)

http://jsfiddle.net/tyage/dtna2qsg/

上記URLではTwitter IDが3文字の場合のリンクしか表示しないので多くの方は何も表示されないかと思いますが、5文字まで表示したところ以下のように私のTwitter IDが表示されました。

スクリーンショット 2015-01-02 18.51.30

表示されたIDを自動的に取得することはできませんが、Captchaと称してユーザに入力させれば取得することができそうです。(この場合、表示する内容はIDではなくてもよいですね)

5文字の半角英数字(小文字)IDを全て表示すると 36^5 = 60466176 個となるので現実的ではありませんが、4文字程度ならギリギリ表示できるのではないでしょうか。

IE以外でも

先ほどの例はIEで透過度を変更可能であることを利用したものでしたが、それ以外のブラウザでも利用できるものを考えてみます。

Twitter以外のサービスでいうと、pixivが利用できるでしょうか。

pixivでは http://www.pixiv.net/mypage.php にアクセスすると http://www.pixiv.net/mypage.php#id=[id] へとリダイレクトされます。

これを利用して以下のページを作ってみました。

http://jsfiddle.net/tyage/vwdxysmf/

このページではpixiv idから適当な4桁の数字を引いたものを入力することが求められるのですが、そのIDからID+9999までのリンクを表示するようになっています。

unvisitedなページは黄色い四角で、visitedなページは赤い四角で出力され、赤い四角をクリックするとpixiv idが表示されます。

background-colorを変えているだけなのでChromeやFirefoxでも動作します。

10万程度までなら難なくリンクを描画できるのですが、ユーザに選択させるのが難しくなりそうです。(今回は10000個表示しています。)

まとめ

IEでは:visited要素に対しても透過度を変化させることができるようでした。

パフォーマンステストによる訪問済みリンクの検出は今後の課題としします。

今回はSNSのIDの取得について考えてみましたが、「ID数が膨大であるため現実的な速度でページを表示できない」、「表示された内容の取得に際してユーザの操作を必要とする」といった理由からあまり現実的な方法は見つかりませんでした。

ただし、表示するリンクが数万程度で済む場合は現実的な手段があるかもしれません。
(また訪問者が「100個のWebサービスのうちどれを利用しているユーザか」程度なら問題なく表示できそうです。)

表示された内容の取得に関しては、偽物のCaptchaを取り上げましたが、他にもゲームの操作で取得する方法もあるかと思います。

今のところこれを利用した実際の情報漏洩は起こっていないと思いますが(【要出典】)、ブラウザの更新で似たような穴が生まれる可能性もあるので気にしておきたい点ではあります。