Firefox built-in VPN does not set media.peerconnection.ice.proxy_only while active, so WebRTC STUN over UDP bypasses the proxy and exposes the user's real IP, defeating the feature's stated IP-protection with no user warning
Categories
(Firefox :: IP Protection, defect)
Tracking
()
People
(Reporter: mihalis.haatainen, Unassigned)
References
Details
(4 keywords, Whiteboard: [client-bounty-form])
Attachments
(1 file)
|
4.57 KB,
text/html
|
Details |
Description
The Firefox built-in VPN (launched Firefox 149, March 2026) is marketed as an IP-protection feature. Mozilla's own descriptions state it "routes your traffic through a proxy server, so websites see a different IP address instead of your real one" (firefox.com) and is "a new IP-protection feature designed to keep you even more private while you browse" (blog.mozilla.org).
The feature is implemented as a browser-layer HTTPS proxy. WebRTC ICE gathering uses STUN requests over UDP, which the HTTPS proxy does not tunnel. As a result, when the built-in VPN is active, WebRTC still reflects the user's real public IP address via a server-reflexive (srflx) ICE candidate, while HTTP(S) traffic correctly shows the proxy exit IP.
The practical effect: a user who enables the built-in VPN believing it masks their IP has their real IP silently exposed to any website that runs a few lines of RTCPeerConnection JavaScript, with no camera/microphone prompt and no warning.
Relationship to bug 959893
I am aware of bug 959893, which covers the general WebRTC IP-exposure mechanism and was resolved as conformant to the RTCWEB spec, with mitigation left to opt-in prefs (media.peerconnection.ice.proxy_only / proxy_only_if_behind_proxy) and mDNS for local addresses.
This report is narrower and concerns the new product, not the engine default. 959893 is about Firefox's default WebRTC behavior for a user who has not configured any proxy. This report is about the built-in VPN feature, when explicitly enabled by the user, failing to activate the pref that already exists to close this exact hole. The user has taken an explicit action (toggling on an "IP-protection" feature) and has a reasonable, Mozilla-stated expectation that their real IP is now masked. The feature does not set proxy_only, so that expectation is silently violated.
In other words: the mechanism is known (959893), but the product-integration decision to ship an IP-protection feature without enabling the available WebRTC mitigation is the issue here.
Steps to reproduce
- Enable the Firefox built-in VPN (toolbar VPN icon).
- Confirm the proxy is active: visit an IP-check site; it shows the proxy exit IP (ISP "Mozilla Corporation" / Fastly).
- On any page, run:
const pc = new RTCPeerConnection({iceServers:[{urls:'stun:stun.l.google.com:19302'}]});
pc.onicecandidate = e => { if (e.candidate) console.log(e.candidate.candidate); };
pc.createDataChannel('x');
pc.createOffer().then(o => pc.setLocalDescription(o));
or
run poc.
- Observe the srflx candidate.
Actual results
With the built-in VPN active:
- HTTP(S) exit IP (IP-check site): 45.58.232.100 (Mozilla / Fastly proxy exit)
- WebRTC srflx candidate: ...(my real ISP public IP, confirmed identical to the IP shown with the VPN disabled)
Host candidates were correctly mDNS-obfuscated (.local), so the local LAN IP did not leak. The issue is specifically the real public IP leaking via the srflx candidate while the VPN is on.
Expected results
When the built-in VPN is active, WebRTC should not expose the real public IP that the feature is masking for all other browser traffic. Enabling the VPN should also constrain WebRTC (for example by setting media.peerconnection.ice.proxy_only, or proxy_only_if_behind_proxy, or otherwise forcing ICE through the proxy / relay-only) so that the real IP is not reflected. If a trade-off with WebRTC functionality is unavoidable, the limitation should at minimum be disclosed to the user when the VPN is enabled, rather than silently leaking.
Impact
The feature's entire stated purpose is masking the user's IP. A user on public Wi-Fi, or a user trying not to link activity across sites (both use cases Mozilla names explicitly in its announcement), can be deanonymized by any site running trivial WebRTC JavaScript, despite having the VPN enabled and believing they are protected. The gap is invisible: there is no prompt, no indicator, and the HTTP-level IP check that a user might perform to "verify" the VPN shows the proxy IP, giving false confidence.
Suggested fix
When the built-in VPN is enabled, set media.peerconnection.ice.proxy_only (or proxy_only_if_behind_proxy) for the duration, so WebRTC ICE is forced through the proxy and the real IP is not gathered as a candidate. This is a small, targeted change scoped to the VPN feature's active state; it does not change default WebRTC behavior for users who are not using the built-in VPN. Alternatively, surface the WebRTC limitation clearly in the VPN UI/support documentation.
| Reporter | ||
Comment 1•2 months ago
|
||
Confirmed in about:config: with the built-in VPN active, media.peerconnection.ice.proxy_only is false.
So the mitigation that exists specifically to keep WebRTC from reflecting the real IP is not enabled while the VPN is on. The feature masks HTTP(S) through the Fastly proxy but leaves WebRTC ICE gathering on the direct path, which is why the srflx candidate returns the real public IP.
This is the whole gap in one line: the pref that would close it ships false while an IP-protection feature is active. Setting it true for the duration of the VPN session (scoped to the feature, no change to default behavior for non-VPN users) closes it.
| Reporter | ||
Updated•2 months ago
|
| Reporter | ||
Comment 2•2 months ago
|
||
| Reporter | ||
Comment 3•2 months ago
|
||
| Reporter | ||
Updated•2 months ago
|
| Reporter | ||
Comment 4•2 months ago
|
||
Can someone from mozilla side, fix the bug... getting notification triage owner cant see this ticket...
Comment 5•2 months ago
|
||
Hello, this bug is ~7 hours old and has not been triaged yet. Please be patient.
Comment 6•2 months ago
|
||
It's probably more up to the "IP Protection" folks to modify prefs like this. The WebRTC engine itself doesn't necessarily know when that is active. I suppose you could argue it either way.
| Reporter | ||
Comment 7•2 months ago
|
||
Re the sec-low rating. The closest precedent for this bug class is bug 1657916 (CVE-2020-35111), and reading it through is what makes me think low is the wrong landing spot here.
In comment 5 of that bug, :freddy set the test up front: sec-low unless there is a way to trigger the load from web content, and if a leak from an evil website were found, sec-moderate. In comment 6 the reporter reported back that he could only reach it by flipping security.view-source.reachable-from-inner-protocol and adding user interaction, and correctly did not count that. In comment 11, :freddy stated the general rule plainly, that proxy bypasses triggered from web content in a non-webextension setting are generally considered sec-high, and said he had kept the bug private in case that path turned up.
It never turned up, so the bug settled at sec-low. The rating was a function of unreachability, not of the leak being unimportant.
This report satisfies the condition that one failed:
triggered from web content, any page, first load
no user interaction, no prompt, no pref changes
no extension involved, this is a first-party shipped feature
deterministic, five lines of script
what leaks is the user's real public IP, which is the specific value the enabled feature exists to mask
By the standard written into 1657916, content-triggerable puts this at sec-moderate at minimum.
On the shape of the defect: this is not a code fault so much as a protection that was not applied. Firefox already carries the mitigation. media.peerconnection.ice.proxy_only exists for exactly this leak and ships false while IP Protection is on. Worth noting that bug 1677047, adding H323, PPTP and RTSP ports to the restricted list, was also a missing-protection issue with no external exploit attached, and it was keyworded sec-moderate. A gap in a defensive measure has been rated moderate here before.
I am not pushing for high. I am asking that it not be rated below the level the team itself set for the content-triggerable version of this exact leak.
Comment 8•2 months ago
|
||
I thought bug 2026571 was meant to have fixed this - Basti?
Comment 9•2 months ago
|
||
Yes it should. Using the webrtc_ip_leak.html while vpn-on this does just seem to timeout on my machine using FF-154.
While the vpn is active we do set proxy_only_if_behind_proxy, unless a user has touched the pref value, in which case we leave it at whatever the user had.
The bug also report only mentioned Firefox 149, we shipped the fix in 150.
| Reporter | ||
Comment 10•2 months ago
|
||
155.0a1 (2026-07-27) (aarch64)
seems working fine
| Reporter | ||
Comment 11•2 months ago
|
||
And so does on 153.0 (aarch64)
Comment 12•2 months ago
|
||
(In reply to Mihalis Haatainen from comment #10)
Created attachment 9616735 [details]
Screenshot 2026-07-27 at 16.00.05.png155.0a1 (2026-07-27) (aarch64)
seems working fine
So I'll close this as a dupe, then? I'm confused why you filed this if you also tested this before and it was already fixed... 149/150 are now several months old...
| Reporter | ||
Comment 13•2 months ago
|
||
No, I mean :Gijs bypass working fine..
| Reporter | ||
Comment 14•2 months ago
|
||
So its not yet fixed... dont close it, or some how the fix is incomplete, cause it leaks real ip...
| Reporter | ||
Updated•2 months ago
|
| Reporter | ||
Comment 15•2 months ago
|
||
Can someone give me access to => https://bugzilla.mozilla.org/show_bug.cgi?id=2026571 so I can investigate why the ip leak still works..
| Reporter | ||
Updated•2 months ago
|
Comment 17•2 months ago
|
||
(In reply to Mihalis Haatainen from comment #15)
Can someone give me access to => https://bugzilla.mozilla.org/show_bug.cgi?id=2026571 so I can investigate why the ip leak still works..
It's basically exactly the same kind of report as this one. The report will be opened up once fixed versions have been public for a while.
The fix here (which by our open source nature is already public) was https://hg.mozilla.org/mozilla-central/rev/27e9dcd7d2fa , if that's helpful at all.
Comment 18•2 months ago
|
||
The relevant file is :) https://searchfox.org/firefox-main/source/toolkit/components/ipprotection/IPPSessionPrefManager.sys.mjs
Thank you for the screenshot! We do set proxy_only_if_behind_proxy.
In your screenshot you are loding the site via localhost. As per IPPExceptionsManager [1], localhost is always excluded therefore the pref does not go into affect here. Given your localhost page was not loaded using the vpn, that is correct behavior imho.
Could you demonstrate loading this using a remote url, that is not excluded from the vpn? i.e loading your attachment: https://bugzilla.mozilla.org/attachment.cgi?id=9613900
| Reporter | ||
Comment 19•2 months ago
|
||
You're right, my bad.
I was testing the PoC from localhost, which is always excluded from the VPN (IPPExceptionsManager). That's why the real IP still appeared.
When I load the exact same page from a remote origin (e.g. the Cloudflare Worker version or the Bugzilla attachment itself) with the built-in VPN active, the srflx candidate no longer shows the real public IP. The media.peerconnection.ice.proxy_only_if_behind_proxy preference is correctly applied and the leak is blocked.
So the fix from bug 2026571 is working as intended for normal (non-localhost) pages. Sorry for the noise / reopening.
Thanks for the clarification, Sebastian.
| Reporter | ||
Comment 21•2 months ago
|
||
Always! so far best program to hunt! :)
| Reporter | ||
Comment 22•2 months ago
|
||
Btw, can someone remove the attachments, so wont dox my ip to world....
Comment 23•2 months ago
|
||
(In reply to Mihalis Haatainen from comment #22)
Btw, can someone remove the attachments, so wont dox my ip to world....
I don't know if they can be deleted but I've marked them private, this is typically how we handle sensitive attachments. Only a subset of Mozilla employees with security access will be able to see them now.
Updated•2 months ago
|
Updated•2 months ago
|
Updated•1 month ago
|
Description
•