【LocalMess】Webサイトをネイティブアプリ経由でクロスサイトトラッキングする手法

Gigazineに[MetaがロシアのYandexと同じ手口でユーザーの行動を追跡していたことが判明、何百万ものウェブサイトに「スマホのアプリと通信するコード」を埋め込んで追跡しておりブラウザの履歴を削除しても無駄](https://gigazine.net/news/20250604-meta-yandex-tracking/)という記事がありました。
タイトル長い。

ここでトラッキングに使用された技術がLocalMessというものです。
Gigazineの記事だけだと何なのか正直よくわからなかったので、元ネタを読んでみました。

以下は該当の記事、[Disclosure: Covert Web-to-App Tracking via Localhost on Android](https://localmess.github.io/)の紹介です。


# Disclosure: Covert Web-to-App Tracking via Localhost on Android

![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/d2447ac7-1d96-4af6-b4ad-5dde7ed9fd91.png)

:::note
UPDATE
6月3日午前7時45分、Meta/Facebookはlocalhostへパケットやリクエストを送信しなくなりました。
Metaアプリからはコードがほぼ完全に削除されました。
:::

MetaとYandexによる、数十億人のAndroidユーザに影響のある新たなトラッキングを公開します。
FacebookとInstagram、そしてYandexのMapsやBrowserなど幾つかのネイティブAndroidアプリは、トラッキングの目的でローカルポートをひそかにリッスンしていることが判明しました。

たくさんのWebサイトに設置されているMeta Pixel・Yandex Metricaスクリプトは、ブラウザのメタデータ・Cookie・コマンドをネイティブAndroidアプリに送り込みます。
これらのJavaScriptはユーザのモバイルブラウザで読み込まれ、localhostを経由してネイティブアプリに接続します。
ネイティブアプリは[Android広告ID(AAID)](https://support.google.com/googleplay/android-developer/answer/6048248)やMetaアプリのユーザIDのような識別子を持つため、CookieをユーザIDとリンクさせることが可能になります。
従ってスクリプトが埋め込まれたサイトにアクセスした際の匿名性が損なわれます。

このWebとアプリのID共有は、Cookie消去・シークレットモード・Androidの権限管理といった一般的なプライバシー保護を回避します。
また、悪意のあるアプリがユーザのWebアクティビティを盗聴する危険性も存在します。


## How does this work?

モバイルコンテキストを橋渡しする方法はMetaとTandexそれぞれで微妙に異なりますが、localhostソケットへの検証されていないアクセスを用いるという点で本質的には同一です。
INTERNET権限を持つAndroidアプリは、ループバックインターフェイス127.0.0.1にリスニングソケットを開くことができます。
ブラウザは、ユーザの同意もなくプラットフォームの仲介も不要でループバックインターフェイスにアクセスできます。
これによって、Webページに埋め込まれたJavaScriptとネイティブAndroidアプリが識別子や閲覧履歴を共有できるようになり、Web上の一時的な識別子と長期的なモバイルアプリIDを紐付ける事が可能になります。

## Meta/Facebook Pixel sharing _fbp cookie from web to Meta Android apps

Meta (Facebook)のPixel JavaScriptは、WebRTCを使用してファーストパーティCookieをUDPポート12580-12585に送信します。
これらのポートをListenしている全てのアプリが、この情報を受信することが可能です。
GooglePlayストアで展開されているMetaアプリでは、Facebook515.0.0.23.90とInstagram382.0.0.43.84が、これらのポートをListenしていることが確認されました。

![07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/c2456f32-6d4f-428e-9f0c-496aeb268a1a.png)


Meta Pixelは[SDP Munging](https://webrtchacks.com/not-a-guide-to-sdp-munging/)という手法を用いて、ファーストパーティCookieの[_fbp](https://web.archive.org/web/20250602140111/https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/fbp-and-fbc/)をSDPの"ice-ufrag"フィールドに書き込みます。
結果として、ループバックアドレスにBinding RequestのSTUN Messageが送信されます。
これはChrome DevToolsなど通常のデバッグツールでは発見することができません。


![02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/7416b300-b824-4e3d-b7a6-dde161294d7a.png)


Metaの全体的なフローは以下のとおりです。

・ユーザがネイティブFacebookアプリ・Instagramアプリを開くと、アプリはバックエンドでTCP12387・12388とUDP12580-12585をListenするバックグランドサービスを生成する。

・ユーザがブラウザで、Meta Pixelスクリプトが埋め込まれているWebサイトを訪問する。

・Meta Pixelスクリプトは、_fbpをSDP Mungingでネイティブアプリに送信する。

・Meta Pixelスクリプトはさらに、URL・Webサイトのメタデータ・[イベントタイプ](https://web.archive.org/web/20250531104925/https://developers.facebook.com/docs/meta-pixel/reference/)などのパラメータと_fdpを`https://www.facebook.com/tr`に送信する。

・ネイティブアプリは_fbpを受け取ると、アプリのユーザIDと_fbpを`https://graph.facebook.com/graphql`に送信する。

![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/cd042916-21ce-4414-b792-4817ea8a2bef.png)

2025/05/17ごろ、Meta Pixelはスクリプトに新しい機能を追加しました。
新しいバージョンでは、_fbpをSTUNではなく[WebRTC TURN](https://webrtc.org/getting-started/turn-server?hl=es-419)を使って送信します。

2025/06/02、ネイティブアプリはポートをListenしなくなりました。


## About the _fbp cookie

`_fbp`とはなにか。

MetaのCookieポリシーによると、[_dbp](https://web.archive.org/web/20250602140832/https://www.facebook.com/privacy/policies/cookies/?subpage=subpage-1.3)は広告やサイト分析ツールを提供する目的でブラウザの分析を行い、その有効期限は90日です。
[Web Almanac 2024](https://almanac.httparchive.org/en/2024/cookies#top-first-and-third-party-cookies-and-domains-setting-them)によると、上位100万サイトのうち25%に存在し、ウェブ上で3番目に多いファーストパーティCookieになっています。

ファーストパーティCookieはドメイン単位で設定されるため、Webサイトを横断してユーザを追跡することはできません。
すなわち、異なるWebサイトでは_dbpの値が異なるということです。
しかし、ここで表した方法によって既存の保護策を回避し、異なる_dbpをひとりのユーザに紐付ける事が可能となります。


## Yandex using localhost communications since 2017

Yandexは2017年からこれを利用しています。

Yandex Metricaスクリプトは、TCPポート29009・29010・30102・30103を使って、localhostへ長いパラメータを含むHTTPリクエストを送信します。
調査の結果、Yandex Maps・Yandex Navigator・Yandex Search・Yandex BrowserがこれらのポートをListenしています。
さらに`yandexmetrica.com`がループバックアドレス127.0.0.1に解決され、Yandex Metricaスクリプトはローカルポート29010・30103とHTTPS通信していることがわかりました。
この設計のためデータは難読化されており、検出が困難になります。

![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/e862dc6f-326b-44b0-b713-d0015300ca5e.png)

またYandexアプリはyandexのドメイン`startup.mobile.yandex.net`等に接続します。
エンドポイントはListenするポート番号とパラメータ`first_delay_seconds`を含むJSONを返します。
このパラメータは、Listen開始を遅らせるために使用されると考えられます。
テストデバイスのひとつでは、first_delay_secondsの値と、YandexアプリがListenを開始するまでにかかった時間がほぼ一致しました。

Yandex Metricaスクリプトからリクエストを受け取ったモバイルアプリは、AAIDにGoogleのAdvertising IDやUUIDなど、JavaAPIからアクセス可能な識別子を取得して返します。

Meta pixelとは異なり、これらの情報はネイティブアプリではなくJavaScriptからYandex Metricaのサーバに送信されます。
Yandexの場合、ネイティブアプリはAndroid固有の識別子を収集して返すプロキシとして機能します。

![05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/8700bb4a-dd5e-4f0a-abd8-5552a46037ef.png)

Yandexの全体的なフローは以下のとおりです。

・ユーザがネイティブYandexアプリを開くと、アプリはバックエンドでHTTP29009・30102とHTTPS29010・30103をListenするバックグランドサービスを生成する。

・ユーザがブラウザで、Yandex Metricaスクリプトが埋め込まれているWebサイトを訪問する。

・Yandex MetricaスクリプトはYandexサーバにアクセスし、難読化されたパラメータを取得する。

・Yandex Metricaスクリプトは難読化されたパラメータをHTTP・HTTPSでlocalhostに送信する。

・ネイティブアプリはパラメータを受け取ると、Androidのデバイス情報を入れてレスポンスコード200を返す。

・Yandex Metricaスクリプトは、レスポンスと難読化されたパラメータをYandexサーバに送信する。

![06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/78c5dbae-53ee-46d3-9ae8-0b2e3959c030.png)

以下の表は、localhostをListenしていることが判明したYandexアプリのリストです。

![09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/8b0accc0-8d23-4ed9-a39c-b4435b6a4f76.png)

## Additional risk: Browsing history leak

Web-ネイティブ共有のためにHTTPリクエストを使用すると、履歴が第三者に流出するおそれがあります。
該当のポートをListenしているアプリは、Yandex Metricaスクリプトと、Metaがかつて使用していたバージョンのHTTPリクエストを傍受できます。

悪意のあるサードパーティアプリが履歴を収集できることを証明するため、概念実証アプリを作成しました。
Chrome・Firefox・Edgeは、デフォルトモードおよびプライベートモードのいずれにおいても、閲覧履歴を傍受できることを確認しました。
Braveブラウザはlocalhostへのリクエストをブロックしているため、この影響を受けませんでした。
DuckDuckGoは最小限の影響を受けます。

MetaとYandex以外で、実際にこれらのポートをListenしているアプリを見つけることはできませんでした。

![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/04fd2134-e7a1-4c26-b29e-07c3d4098789.png)


## Affected Sites

Webテクノロジーを調査しているBuiltWithによると、Meta Pixelは[580万以上](https://trends.builtwith.com/websitelist/Facebook-Pixel)、Yandex Metricaは[約300万](https://trends.builtwith.com/analytics/Yandex-Metrika)のWebサイトに埋め込まれています。
毎月1600万のサイトをクロールしているHTTP Archiveによると、それぞれ240万と575448のWebサイトに存在します。

以下の表は、[CrUXランキング](https://github.com/zakird/crux-top-lists)の上位10万サイトをクロールし、利用状況を確認した結果です。
`presence`は、全てのCookieを受け入れる選択をした場合にトラッカーを埋め込むサイトの数です。
`no consent`は、ユーザがブラウザでWebサイトを開いたらすぐにトラッカーを実行した、すなわちユーザの同意なしに情報収集を行ったサイトの数です。

![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/861b5262-bf9d-4639-acaf-c1aa0cee4d5f.png)



## When did this start?

これはいつ始まった？

以下の表は、YandexとMetaが使用した手法の時系列のうち、アーカイブから確認できるリストです。

![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/87cd4ec2-204a-4fb6-8788-474368cf03b9.png)



## Abuse Vectors

この新しい追跡手法は、Androidプラットフォームのブラウザがlocalhostソケットへ無制限のアクセスを許可していることを悪用しています。
上記のように、このトラッカーはユーザに気付かれることなく動作することが可能です。
現在のプライバシー制御では、この動作を制御・軽減することができないためです。

localhostソケット通信は、Web開発などの正当な目的でも使用されます。
しかし研究者コミュニティからは、localhostソケットがデータ漏洩やトラッキングに使用される可能性について[前から指摘されていました](https://www.ndss-symposium.org/wp-content/uploads/2019/02/ndss2019_06B-5_Wu_paper.pdf)。
我々の知るかぎり、トラッキングが実際に悪用されたという報告は今のところありません。


## Disclosure

主要なAndroidブラウザベンダへの責任ある開示 ( Responsible Disclosure ) により、この問題に対するパッチが公開されました。
既に適用済のものもあれば、まだ開発中のものもあります。
プロセスに積極的な関与をいただいたベンダー(Chrome・Mozilla・DuckDuckGo・Brave)に感謝します。
その他のChromiumベースのブラウザは、各自でパッチを適用する必要があります。

しかし、短期的な修正だけでは全ての問題に対策しきれていないため、より広範な対策が必要です。
具体的には、localhostへのアクセスをユーザに通知するコントロール、不正利用を防止するためのプラットフォームによる強権、そしてAndroidのプロセス間通信メカニズムのセキュリティ強化です。

Chrome：136.0.7103.125では脆弱。137でポートのブロック、SDP Mungingのブロックを予定。
Edge：136.0.3240.50では脆弱。対応予定不明。
Firefox：138.0.2では一部脆弱。139でポートのブロックを予定。
DuckDuckGo：5.233.0でごく一部のみ脆弱。ブロックリストを追加した。
Brave：1.78.102で安全。localhostアクセスにはユーザの許可が必要。


## Are website owners aware?

Webサイトの所有者はこのことを知っている？

この手段や目的について解説した公式ドキュメントは見当たりませんでした。
Meta Pixelについては、localhostと通信する理由がわからないWebサイト所有者からの苦情がフォーラムに寄せられています。

・[Facebook SDK config file making call to localhost](https://web.archive.org/web/20250531105747/https://developers.facebook.com/community/threads/317050484803752/)
・[Why does my Pixel Javascript access http://localhost when in an embedded web view on Android](https://web.archive.org/web/20250531105711/https://developers.facebook.com/community/threads/937149104821259/)

Meta担当者からの回答はありません。
ある開発者は以下のように述べています。

:::note
No acknowledgement has come from Meta at all on this though.
My support request with them got a generic response and then ignored thereafter.
It's a shame accountability hasn't been taken, we rely on these tools to work properly and have no control over them so at the very least we should be given an explanation on what went wrong.

Metaからは一切の回答がありません。
サポートリクエストには一般的な回答しか返ってこず、その後は無視されました。
説明責任が果たされていないのは残念です。
我々はツールが正常に機能することを期待しており、こちらで制御することもできないため、少なくとも何が間違っていたのかを説明する必要があります。
:::


## Are end-users aware?

エンドユーザはこのことを知っている？

一般ユーザは、この動作には気付いていないでしょう。
この追跡方法は、以下のような防衛手段を取っていても動作します。

・モバイルブラウザでFacebook・Instagram・Yandexなどにログインしていない。
・シークレットモードを利用している。
・Cookieや閲覧履歴を削除している。

このトラッキングは、パーティション分割、サンドボックス、プロセス間分離といったトラッキング保護を貫通します。
調査によると、これらは明示的なCookie同意フォームが無いWebサイトでも動作しています。
ユーザが同意しなくても、FacebookやYandexのスクリプトを読み込んだだけで動作します。


## ☝️ Questions & Answers

### どうして、この調査が一般公開された日にFacebookはこれを利用しなくなったのですか？

さあ ¯\_(ツ)_/¯

しかしながら、Androidユーザが(今のところ)この不正アクセスの影響を受けなくなったことは喜ばしいことです。


### この調査は査読済ですか？

情報を開示した関係者によって確認されましたが、査読は受けていません。

### MetaとYandexは、この手法をドキュメントで公開していましたか？

MetaとYandexから、localhostへの通信について説明したドキュメントは一切みつかりませんでした。

### 影響を受けるのはAndroidだけですか？iOSや他プラットフォームは？

MetaとYandexについて、我々が確認した中ではAndroidユーザのみを対象にしていました。
iOSブラウザやアプリで不正使用の証拠は見つかっていません。
ただし[技術的には可能](https://bugs.webkit.org/show_bug.cgi?id=279249)です。
トラッカーの標的にならなかったのはiOSネイティブアプリの[バックグランド実行制限](https://developer.apple.com/documentation/xcode/configuring-background-execution-modes)が考えられます。

モバイル以外の、デスクトップOSやスマートTVなどにも脅威が存在する可能性はありますが、これらについてはまだ調査を行っていません。



# 感想

・Androidブラウザはlocalhostにリクエストを投げられる。
・AndroidアプリはlocalhostをListenできる。

単にこのふたつの"仕様"を組み合わせただけです。
特に高度なハッキング技術を使ったというわけでもありません。

つまり何を言いたいかというと、この手口を使っているのがMetaとYandexたったふたつだけとはとても考えられない。

なにせ[10年近く前から](https://issues.chromium.org/issues/40467316#comment4)[同じような懸念](https://bugs.webkit.org/show_bug.cgi?id=171934#c1)が持ち上がっていたのです。
こんな周知の技術、悪用されていない方がおかしい。

というか[このコメント](https://bugs.webkit.org/show_bug.cgi?id=171934#c8)、今回のやつとやってること完全に同じなのでは？？？？？？

さて、この技を利用したクロスサイトトラッキングは、想像以上の紐付けを可能にします。
具体的にはシークレット☆モードを横断し、ブラウザを横断し、それどころか端末間すらも横断します。
たとえば貴方が仕事用とプライベート用端末のスマホ2丁持ちをしていたとしましょう。
Facebookアカウントも完全に別にしていて仕事用では真面目で意識高い話ばかり、プライベート用では自堕落で赤裸々な日々を公開しており、話題に全く共通点がなくこの2アカウントを紐付ける術は全くありません。
しかしある日、Facebookとは一切何の関係もない[Yahooオークション](https://auctions.yahoo.co.jp/)のWebサイトに2端末から同じIDでログインしました。
その瞬間、2つのFacebookアカウントが完全に紐付けられます。

この攻撃についてユーザ側でできる対策は、基本的にたったひとつだけです。
すなわち、Androidアプリを削除することです。
Listenするためにはバックグラウンド稼働が必要であり、バックグラウンド稼働するためにはインストールされる必要があるわけで、インストールされていなければさすがにListenされることはありません。

しかし攻撃手法が公開されたということで、他の無関係なアプリもガンガンlocalhostをListenしてくるのではないかという懸念が生じます。
ブラウザがlocalhostへのリクエストを禁止する、AndroidアプリのlocalhostへのListenを禁止する、少なくともどちらかの対策が必要となりますが、互換性維持のため完全な対策は困難かもしれません。
果たして今後の対策はどうなるでしょうかね？