(Eugene Onischenko / Shutterstock.com)
こんにちは、中山です(写真は私ではありません)。
以前の 3rd party Cookie いただきます にて
Parasite Cookie の場合、クロスドメインでトラッキングできないことが(業者側の)課題です。
しかし Super Cookie と呼ばれる方法ならばクロスドメインでのトラッキングも可能です。
こちらについては別途ご紹介します。
と結びました。
そこで、今回は Super Cookie と呼ばれる技術についてご紹介したいと思います。
例えばこんな Super Cookie
Super Cookie とは、各種擬似 Cookie 技術の総称で、Flash の LSO を利用したシンプルな実装から、ブラウザの閲覧履歴(参考 : css history knocker)や Criteo の ITP 対策として話題になった HSTS 機能を利用したトリッキーな実装などが存在します。
例えば HSTS 機能ではドメイン毎にプロトコル(HTTPS を利用するか否か)を不揮発領域に記録しますが、それを利用した Super Cookie は以下のような動作原理となっています。
- ブラウザに付与する識別子(ここでは 256 ビット乱数)を生成する = NN
- 以下のような 256 個のドメインに https: でアクセスする
https://00.tracking.com/NN
https://01.tracking.com/NN
...
https://FE.tracking.com/NN
https://FF.tracking.com/NN - 各 XX.tracking.com の処理時 NN の XX ビット目をチェックして
0 の場合は "Strict-Transport-Security: max-age=expireTime" を応答
1 の場合は "Strict-Transport-Security: max-age=0" を応答 - 結果として識別子 NN が利用プロトコル情報としてブラウザに記録される
- 識別子の復元時は以下のような 256 個のドメインに http: でアクセスする
http://00.tracking.com/
http://01.tracking.com/
...
http://FE.tracking.com/
http://FF.tracking.com/ - HTTPS へのリダイレクトの有無から各ビットを復元し、最終的に 256 ビットの値 NN を復元
なんという創意工夫 !!
(この創意工夫を建設的な用途に向けてほしい ……)
さらにこんな話題もありました。
これは Verizon の Gateway により HTTP Header に挿入されたユーザー識別子を利用し、DSP 業者が削除された Cookie を復元していた、という話題です。
世の中の Super Cookie に対する評価は、ベンダー目線では反則スレスレ、エンドユーザー目線では反則アウト !! といったところかと思いますが、後ほど考察をまとめます。
手前味噌な Super Cookie 実装
さて、折角の機会ですので、私の考案した Cool な Super Cookie の実装をご紹介します。
(利用したい方はご自由にどうぞ !! ちなみに弊社は業務用途では利用しておりません)
この実装は Cookie を利用しないことは当然として HTML5 やプラグインにも依存せず、主要なブザウザで動作します。
まずはコードをご覧ください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
<?php /* [how to use] <html> <body> <script type='text/javascript' src='http://HOST/PATH/THISFILE'></script> </body> </html> */ define('SELF', "http://{$_SERVER['HTTP_HOST']}{$_SERVER['SCRIPT_NAME']}"); define('OBJECTNAME', 'MyCookie'); function fingerPrint($in_prefix = '') { $elems = array('REMOTE_ADDR', 'HTTP_USER_AGENT', 'HTTP_ACCEPT_LANGUAGE'); foreach ($elems as $elem) { $in_prefix .= $_SERVER[$elem]; } return md5($in_prefix); } function js1st($in_cookie) { $SELF = SELF; $OBJECTNAME = OBJECTNAME; print <<<EOJS (function() { var target = document.getElementsByTagName('SCRIPT').item(0); var makeScript = function(in_src) { var ret = document.createElement('SCRIPT'); ret.type = 'text/javascript'; ret.src = in_src; return ret; }; window.setTimeout(function() { if (window.{$OBJECTNAME}) { return; } target.parentNode.insertBefore(makeScript('{$SELF}?cookie={$in_cookie}'), target); }, 1000); target.parentNode.insertBefore(makeScript('{$SELF}?cookie='), target); })(); EOJS; } function js2nd($in_cookie) { $OBJECTNAME = OBJECTNAME; print <<<EOJS /* identifier in browser cache */ (function() { if (!window.{$OBJECTNAME}) { window.{$OBJECTNAME} = {}; } window.{$OBJECTNAME}.cookie = '{$in_cookie}'; /* put tracking code here */ alert(window.{$OBJECTNAME}.cookie); })(); EOJS; } date_default_timezone_set('Asia/Tokyo'); if ($_SERVER['QUERY_STRING']) { $params = explode('&', $_SERVER['QUERY_STRING']); $fv = explode('=', $params[0]); if (count($fv) != 2) { print '// error'; exit; } $fname = fingerPrint($fv[0]); if ($fv[1]) { /* 1.2 */ $fh = fopen($fname, 'w+'); fwrite($fh, $fv[1]); fclose($fh); header("Location: " . SELF . "?{$fv[0]}="); } else { if (is_file($fname)) { /* 1.3 */ $fh = fopen($fname, 'r'); header('Content-Type: text/javascript'); header('Last-Modified: ' . date(DATE_RFC822)); header('X-Finger-Print: ' . $fname); js2nd(fread($fh, filesize($fname))); fclose($fh); unlink($fname); } else { /* 2, 3, 4 ... */ header("HTTP/1.1 304 Not Modified"); } } } else { /* 1.1 */ header('Content-Type: text/javascript'); js1st(fingerPrint(rand())); } ?> |
この Super Cookie の基本的な動作原理は以下のとおりです。
- 識別子(ここでは L.6 の "ABCDEF")を格納した JavaScript を [x] とする
1 2 3 4 5 6 7 8 9 |
/* identifier in browser cache */ (function() { if (!window.MyCookie) { window.MyCookie = {}; } window.MyCookie.cookie = 'ABCDEF'; /* put tracking code here */ alert(window.MyCookie.cookie); })(); |
- サーバはブラウザからの [x] の要求に対して常に「Cache を見ろ」と回答したい
- 結果としてブラウザの Cache に [x] が存在する限り識別子 "ABCDEF" が永続することになる
- しかし「Cache を見ろ」と言っても、それに先立ち [x] が Cache に存在しなければ無意味
- つまり、サーバは [x] の要求に対して以下を使い分けたい
- 304(= Cache を見ろ)を応答
- Cache に存在しない場合は実体を応答
- しかしブラウザからの HTTP Request は a. と b. で同様(サーバ側で判別不可能)
- そこで b. の場合は以下の処理を通じて Cache に実体を送り込む
- ブラウザは [y] を要求
- サーバは一時ファイルを作成し [y] への要求を [x] にリダイレクトする
- ブラウザは [x] を要求
- サーバは一時ファイルが存在する場合に限り [x] の実体を応答
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
(function() { var target = document.getElementsByTagName('SCRIPT').item(0); var makeScript = function(in_src) { var ret = document.createElement('SCRIPT'); ret.type = 'text/javascript'; ret.src = in_src; return ret; }; window.setTimeout(function() { /* window.MyCookie が定義済ならば [x] が Cache に存在するので何もしない */ if (window.MyCookie) { return; } /* [x] が Cache に存在しない場合は [y] を要求 */ target.parentNode.insertBefore(makeScript('[y]'), target); }, 1000); /* [x] の要求 → Cache を見ろと 304 を返される */ target.parentNode.insertBefore(makeScript('[x]'), target); })(); |
Cookie を無効にしたブラウザであってもキャッシュを消去するか、プライベートブラウジングモードでない限りクロスドメインでのトラッキングが可能になります。
ETag を用いた実装と比較した場合、リロードや Cache の実装や設定の影響を受けにくいのが強みです。
考察
Super Cookie は興味深い技術ですが、技術的に可能とはいえ利用の是非には慎重な判断が必要です。
エンドユーザーが意図的に
- Cookie の機能を制限する(無効にする)
- DNT を有効にする
- ITP を有効にする
etc ...
といった選択をした場合、ユーザーは本質的にはトラッキングそのものを拒否しているのであって、その意図は尊重されるべきです。
とはいえ、ベンダー側にも言い分はあります。
- ブラウザの初期設定は必ずしもエンドユーザーの意図とは言えない
(ベンダー側では初期設定と意図的な設定の区別が困難) - 各種 Web サービスが無料で提供できる理由は、トラッキングした情報をヒントに広告等でマネタイズできるからであり、トラッキングの停止(≒ 生態系の破壊)は最終的にサービスの無料提供というエンドユーザーの利益を守れなくなる可能性がある
ちなみにマーケター向けの記事 ITP 時代にアドレサブル広告を活用すべき理由 では
Criteo やその他ベンダーもなにがしかの ITP 対策を行う可能性がありますが、アドブロックをめぐるいたちごっこのような戦いに陥る懸念もあります。
のように予測しましたが、クロスドメインでのトラッキングを可能にする Super Cookie は「なにがしかの ITP 対策」技術として採用される可能性があります(現時点では各ベンダーの公表する「ITP 対策」には Parasite Cookie が利用されています)。
できれば Super Cookie に頼ることなく Web サービスの生態系を維持できるような着地を期待したいところです。
また別の機会に各ベンダー側のスタンスを調査し、考察を掘り下げてみたいと思います。