Closed Bug 2026571 (CVE-2026-6782) Opened 7 months ago Closed 7 months ago

WebRTC can expose real IP address when IP Protection is active

Categories

(Firefox :: IP Protection, defect)

Firefox 149
defect

Tracking

()

VERIFIED FIXED
151 Branch
Tracking Status
firefox-esr115 --- unaffected
firefox-esr140 --- unaffected
firefox149 --- wontfix
firefox150 --- verified
firefox151 --- verified

People

(Reporter: yuki.detective, Assigned: sstreich)

References

Details

(4 keywords, Whiteboard: [adv-main150+][fx-vpn])

Attachments

(2 files, 2 external files, 1 obsolete file)

Attached file webrtc_ip_leak.md —

User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36

Steps to reproduce:

Note: IP Protection is an experimental feature with progressive rollout starting in Firefox 149, currently
available in the US, UK, Germany, and France.

  1. Enable IP Protection in Firefox 149 (supported region required)
  2. Verify the proxy is active (visit an IP-checking site — proxy IP should appear)
  3. Open the following PoC page:
  <!DOCTYPE html>
  <html><body><pre id="out"></pre><script>
  const out = document.getElementById("out");
  const pc = new RTCPeerConnection({
    iceServers: [{urls: "stun:stun.l.google.com:19302"}]
  });
  pc.createDataChannel("");
  pc.onicecandidate = e => {
    if (e.candidate) {
      const m = e.candidate.candidate.match(
        /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/
      );
      if (m) out.textContent += "IP: " + m[1] + " (" + e.candidate.type + ")\n";
    } else {
      out.textContent += "-- ICE gathering complete --\n";
    }
  };
  pc.createOffer().then(o => pc.setLocalDescription(o));
  </script></body></html>
  1. Observe the ICE candidates gathered

Actual results:

In my code analysis, IP Protection (toolkit/components/ipprotection/) does not set media.peerconnection.ice.proxy_only_if_behind_proxy or any other WebRTC-related preference when the proxy activates. I confirmed this preference remains at its default value of false in a Firefox 149.0 profile.

As a result, PeerConnectionImpl::ShouldForceProxy() (dom/media/webrtc/jsapi/PeerConnectionImpl.cpp, line 4150) returns false — it exits at line 4174 without ever reaching the GetIsProxyUsed() check at line 4184 that would detect IP Protection's proxy.

I observed host candidates exposing local IP addresses, and in test configurations, server-reflexive candidates exposing the public IP. Code inspection strongly indicates the issue; proxy-active runtime verification was not available in my region.

Expected results:

With IP Protection active, WebRTC ICE candidate gathering should be restricted to proxy-mediated paths only.
The user's real IP address should not be exposed to web content. IP Protection is described as masking the user's "IP address and location" for browsing within Firefox.
The fix infrastructure already exists: if proxy_only_if_behind_proxy were set to true by IP Protection, the existing code in ShouldForceProxy() would detect the proxy via GetIsProxyUsed() and enforce proxy-only ICE behavior.

Component: Untriaged → IP Protection

Yeah, I think setting that pref would be sufficient here.

(In reply to Byron Campen [:bwc] from comment #1)

Yeah, I think setting that pref would be sufficient here.

Can you help the frontend folks understand what other consequences setting this pref would have? That is, why is the default of the pref not true in general / what would the consequence be of keeping the pref set? How would they test for user impact, e.g. that webrtc-using websites are not broken by flipping this pref?

Flags: needinfo?(docfaraday)

Having this be true by default would disable UDP-based networking for webrtc when a web-proxy is in use. TCP is a poor transport protocol for real-time media, so we want to avoid the forced use of TCP unless it is absolutely necessary.

Flags: needinfo?(docfaraday)
Duplicate of this bug: 2028200
Assignee: nobody → sstreich
Status: UNCONFIRMED → ASSIGNED
Ever confirmed: true
Attached file (secure) —

Having this be true by default would disable UDP-based networking for webrtc when a web-proxy is in use. TCP is a poor transport protocol for real-time media, so we want to avoid the forced use of TCP unless it is absolutely necessary.
(In reply to Byron Campen [:bwc] from comment #3)

Please excuse me if I'm speaking out of turn or if I'm misunderstanding something specific about the behavior of firefox, but forcing webRTC traffic through a TURN server wouldn't specifically disable UDP based networking for webRTC. TURN supports both TCP and UDP. It would however disable direct peer2peer webRTC connections. My understanding is that every major videochat provider has TURN support, and in fact most end up forcing TURN connections after more than 2/3 people join a call in order to reduce the combinatorial send+receive stream effect.

(In reply to Max from comment #6)

Having this be true by default would disable UDP-based networking for webrtc when a web-proxy is in use. TCP is a poor transport protocol for real-time media, so we want to avoid the forced use of TCP unless it is absolutely necessary.
(In reply to Byron Campen [:bwc] from comment #3)

Please excuse me if I'm speaking out of turn or if I'm misunderstanding something specific about the behavior of firefox, but forcing webRTC traffic through a TURN server wouldn't specifically disable UDP based networking for webRTC. TURN supports both TCP and UDP. It would however disable direct peer2peer webRTC connections. My understanding is that every major videochat provider has TURN support, and in fact most end up forcing TURN connections after more than 2/3 people join a call in order to reduce the combinatorial send+receive stream effect.

I don't think we support RFC 9298 (routing UDP through a web proxy) yet, and even if we did, the web proxy needs to support it too and I'm not sure how common that actually is. So proxy or no, we're going to be contacting STUN/TURN servers directly for UDP unless that pref is set. As for host candidates, those are only ever exposed to content if the user has granted capture permissions to the origin, which is an RFC 8828 thing. The POC in the description does not do this, and I cannot reproduce the exposure of host candidates with it.

(In reply to Byron Campen [:bwc] from comment #7)

(In reply to Max from comment #6)

Having this be true by default would disable UDP-based networking for webrtc when a web-proxy is in use. TCP is a poor transport protocol for real-time media, so we want to avoid the forced use of TCP unless it is absolutely necessary.
(In reply to Byron Campen [:bwc] from comment #3)

Please excuse me if I'm speaking out of turn or if I'm misunderstanding something specific about the behavior of firefox, but forcing webRTC traffic through a TURN server wouldn't specifically disable UDP based networking for webRTC. TURN supports both TCP and UDP. It would however disable direct peer2peer webRTC connections. My understanding is that every major videochat provider has TURN support, and in fact most end up forcing TURN connections after more than 2/3 people join a call in order to reduce the combinatorial send+receive stream effect.

I don't think we support RFC 9298 (routing UDP through a web proxy) yet, and even if we did, the web proxy needs to support it too and I'm not sure how common that actually is.

Ah, it seems we might be coming to this from slightly opposite sites. I'm on the fastly team that Firefox VPN/IP protection has partnered with and we're supplying the proxy and we (the web proxy) do support CONNECT UDP. But if firefox doesn't support it or if you're considering more generalized solutions then that's a different question I suppose.

So proxy or no, we're going to be contacting STUN/TURN servers directly for UDP unless that pref is set. As for host candidates, those are only ever exposed to content if the user has granted capture permissions to the origin, which is an RFC 8828 thing. The POC in the description does not do this, and I cannot reproduce the exposure of host candidates with it.

I think that some amount of host candidate gathering must happen before the user grants capture permissions. With the firefox VPN enabled, if I visit dnscheck.tools, it shows my private IP address without any additional permissions granting.

dnscheck.tools does not show either the private IP address, or the real external-facing IP address, for me when using NordVPN. It does not show the internal IP address even without VPN, as I would expect.

Did you ever grant dnscheck.tools capture permissions? If you have ever done that in the past, it persists until you explicitly revoke.

I went looking, and it does appear that CONNECT UDP was implemented late last year! Let me see what it would take to get that working with webrtc.

(In reply to Byron Campen [:bwc] from comment #9)

dnscheck.tools does not show either the private IP address, or the real external-facing IP address, for me when using NordVPN. It does not show the internal IP address even without VPN, as I would expect.

Did you ever grant dnscheck.tools capture permissions? If you have ever done that in the past, it persists until you explicitly revoke.

Very strange, myself and several colleagues all experienced the same leak and none of us believe we've ever previously permitted dnscheck.tools capture permissions. I'm attaching a screenshot which isn't super helpful necessarily, but shows:

  1. dnscheck.tools displaying my private IP
  2. That I haven't clicked the "permission needed" link
  3. There doesn't seem to be anywhere that I could revoke permissions even if I had granted them...

(In reply to Byron Campen [:bwc] from comment #10)

I went looking, and it does appear that CONNECT UDP was implemented late last year! Let me see what it would take to get that working with webrtc.

Yay that's exciting. Fastly's proxy supports the RFC version, but we'd be happy to help talk through any specific implementation questions if you have any.

Based on that screenshot, it seems that this is about the external public facing IP address, not a local internal one. This is your primary issue I think. RFC 8828 says to use the interface that the routing table uses to reach the origin; web-proxy browser extensions do not (and cannot, I think) change the routing table, or create new VPN-like network interfaces. (see https://www.rfc-editor.org/rfc/rfc8828.html#section-5.2-2.4) So while the browser might load the origin via a web proxy, that does not alter the behavior of the ICE stack by itself, because that operates based on local interfaces; it will see what local address the routing table uses to reach the origin's IP address, and use that.

Right now, you could probably use proxy_only_if_behind_proxy, but since you are implementing the browser extension itself, it would probably make sense to use the more strict "media.peerconnection.ice.proxy_only" pref when the extension is active. That's what the NordVPN browser extension does.

(In reply to Byron Campen [:bwc] from comment #13)
Based on that screenshot, it seems that this is about the external public facing IP address, not a local internal one.

I'm not actually on the IP protection team at mozilla, but the purpose of the product is to hide the external public facing IP address, so this is the concern here.

This is your primary issue I think. RFC 8828 says to use the interface that the routing table uses to reach the origin; web-proxy browser extensions do not (and cannot, I think) change the routing table, or create new VPN-like network interfaces. (see https://www.rfc-editor.org/rfc/rfc8828.html#section-5.2-2.4) So while the browser might load the origin via a web proxy, that does not alter the behavior of the ICE stack by itself, because that operates based on local interfaces; it will see what local address the routing table uses to reach the origin's IP address, and use that.

A nit, but it's not actually my local internal interface address that's being shown here. I'm on a LAN behind a NAT so my local internal interface address is a RFC 1918 private address. I'm fairly certain the public facing IP that's being leaked by the webrtc stack is derived through the browser reaching out to a STUN server.

it would probably make sense to use the more strict "media.peerconnection.ice.proxy_only" pref when the extension is active. That's what the NordVPN browser extension does.

Agreed 100%

Adding some context from the Necko team on the MASQUE / connect-udp side of this.

Agree with Byron, setting media.peerconnection.ice.proxy_only when IP Protection is active is the right short-term fix.

Longer term: routing WebRTC UDP through the proxy via MASQUE connect-udp.

Byron noted in comment 10 that connect-udp (RFC 9298) was implemented in Firefox last year. That's correct, the implementation landed in mozilla-central and is tracked under Bug 1978893. However, connect-udp is not yet ready for production usage. We are currently working on closing the performance gap with HTTP/2 CONNECT (see QUICKER MASQUE).

Beyond production readiness, connect-udp alone is not sufficient for full WebRTC UDP proxying:

  • TURN case: connect-udp could tunnel UDP traffic to a known TURN server through the proxy, preserving UDP end-to-end. This would be a meaningful improvement over forcing TCP. However, it requires integration work to route TURN UDP through the connect-udp tunnel, which doesn't exist today.

  • Peer-to-peer case: Receiving UDP from multiple remote peers on a single proxy-allocated address requires connect-udp-listen, an extension still in IETF draft. Fastly has confirmed connect-udp-listen is not on their 2026 roadmap, and it would require significant work on both the Necko and WebRTC sides to implement.

Realistic path: ship the proxy_only fix now, pursue the TURN-via-connect-udp case as a follow-up to restore UDP transport quality.

(In reply to Max Inden from comment #15)

  • TURN case: connect-udp could tunnel UDP traffic to a known TURN server through the proxy, preserving UDP end-to-end. This would be a meaningful improvement over forcing TCP. However, it requires integration work to route TURN UDP through the connect-udp tunnel, which doesn't exist today.

I am still looking into what it will take to do this. We already use PUDPSocket when not running in socket process mode, and it would not be too difficult I think to do the same thing when using socket process. I am not 100% sure how far that gets us on its own.

Group: firefox-core-security → core-security-release
Status: ASSIGNED → RESOLVED
Closed: 7 months ago
Resolution: --- → FIXED
Target Milestone: --- → 151 Branch

The patch landed in nightly and beta is affected.
:sstreich, is this bug important enough to require an uplift?

For more information, please visit BugBot documentation.

Flags: needinfo?(sstreich)
Attached file (secure) (obsolete) —
Attachment #9568233 - Flags: approval-mozilla-beta?

firefox-beta Uplift Approval Request

  • User impact if declined/Reason for urgency: Any webpage can resolve the users real IP Address using WebRTC.
  • Code covered by automated testing?: yes
  • Fix verified in Nightly?: no
  • Needs manual QE testing?: yes
  • Steps to reproduce for manual QE testing: - Enable Firefox VPN
  • Check about:config

"media.peerconnection.ice.proxy_only_if_behind_proxy" should be set to "true"

  • Risk associated with taking this patch: low
  • Explanation of risk level: This patch only flips a pref for the time Firefox VPN is enabled: "media.peerconnection.ice.proxy_only_if_behind_proxy" - which is well tested.
  • String changes made/needed?: None
  • Is Android affected?: no
Flags: qe-verify+
Attachment #9568233 - Flags: approval-mozilla-beta? → approval-mozilla-beta+
Attachment #9568233 - Flags: approval-mozilla-beta+ → approval-mozilla-beta?
Attachment #9568314 - Flags: approval-mozilla-beta?

firefox-beta Uplift Approval Request

  • User impact if declined/Reason for urgency: Any webpage can resolve the users real IP Address using WebRTC.
  • Code covered by automated testing?: yes
  • Fix verified in Nightly?: no
  • Needs manual QE testing?: yes
  • Steps to reproduce for manual QE testing: - Enable Firefox VPN
  • Check about:config

"media.peerconnection.ice.proxy_only_if_behind_proxy" should be set to "true"

  • Risk associated with taking this patch: low
  • Explanation of risk level: This patch only flips a pref for the time Firefox VPN is enabled: "media.peerconnection.ice.proxy_only_if_behind_proxy" - which is well tested.
  • String changes made/needed?: None
  • Is Android affected?: no
Attachment #9568233 - Attachment is obsolete: true
Attachment #9568233 - Flags: approval-mozilla-beta?
Attachment #9568314 - Flags: approval-mozilla-beta? → approval-mozilla-beta+
QA Whiteboard: [qa-triage-done-c151/b150][sec]
QA Contact: vvirlics

Verified this following the steps provided for manual testing in the uplift request, on both Firefox Nightly 151.0a1 (14-04-2026) and Firefox Beta 150.0b9 using macOS 26.
Turning VPN ON changes the value of "media.peerconnection.ice.proxy_only_if_behind_proxy" to "true". Turning VPN OFF changes the value of "media.peerconnection.ice.proxy_only_if_behind_proxy" to "false", which is also its default value.
Is there anything else QA needs to test here, please let us know.

Status: RESOLVED → VERIFIED
Flags: qe-verify+
Whiteboard: [adv-main150+]
Flags: needinfo?(sstreich)
Alias: CVE-2026-6782
Whiteboard: [adv-main150+] → [adv-main150+][fx-vpn]
See Also: → 2056579
Duplicate of this bug: 2056579
No longer duplicate of this bug: 2056579
Duplicate of this bug: 2056579
Group: core-security-release
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: