The address returned by the PAC's `isInNet()` function may differ from the address that is actually connected, which may allow the PAC's access control to be bypassed.
Categories
(Core :: Networking: DNS, enhancement, P3)
Tracking
()
People
(Reporter: rintaroumelon, Unassigned)
References
(Blocks 1 open bug)
Details
(Keywords: reporter-external, Whiteboard: [client-bounty-form][necko-triaged])
Attachments
(1 file)
|
4.35 KB,
application/x-zip-compressed
|
Details |
Firefox PAC filtering can be bypassed due to DNS answer desynchronization between PAC decision time and connection time.
Specifically, isInNet(host, pattern, mask) (which resolves hostnames internally) evaluates one IP address, but Firefox may later connect to a different IP for the same hostname during connection fallback. This can cause PAC policy mismatch (e.g., PAC allows DIRECT based on first answer, actual request is delivered to second answer that should have gone through PROXY/deny path).
I found this during targeted auditing of PAC + DNS behavior, then validated it with CI prebuilt Firefox artifacts and xpcshell.
Environment:
- Firefox:
Mozilla Firefox 150.0a1(/tmp/ci-firefox/target/firefox/firefox --version) - OS:
Kali GNU/Linux Rolling 2025.4(kernel6.17.10+kali-amd64) - Tools: xpcshell,
nsINativeDNSResolverOverride, localHttpServer, PAC vianetwork.proxy.autoconfig_urldata URI
Reproduction steps:
- Set PAC mode (
network.proxy.type=2) and install PAC script that allows DIRECT only whenisInNet(host, "127.0.0.2", "255.255.255.255")is true; otherwise return deny proxy (PROXY no-proxy.invalid:9). - Set dual DNS answers for one hostname (same host): first
127.0.0.2, second127.0.0.1. - Start local HTTP server on
127.0.0.1and requesthttp://smuggle.test:<port>/. - Observe:
- PAC decision is made from first answer (
127.0.0.2). - Request succeeds, but actual connected remote IP is second answer (
127.0.0.1).
- PAC decision is made from first answer (
- Control check: request literal
http://127.0.0.1:<port>/and confirm deny branch is taken (request fails via unknown proxy host / no backend reach).
Condition:
- If the first DNS address connects successfully, transport does not move to the next address and this bypass does not occur for that request.
- The bypass requires fallback/retry path (connection/refused/timeout/unknown-host class errors) that advances DNS iterator.
- This issue is independent of
network.proxy.failover_direct; it is caused by PAC decision-time DNS selection vs socket connection-time DNS fallback for the destination host.
Evidence:
- PAC saw first answer:
xpc-raw-verbose.log:75 - Request succeeded:
xpc-raw-verbose.log:76 - Actual remoteAddress is different answer:
xpc-raw-verbose.log:77 - Repeated runs:
summary.txt:1(runs=20 pass=20 fail=0)
Code paths:
isInNet->dnsResolve:firefox/netwerk/base/ascii_pac_utils.js:43,firefox/netwerk/base/ascii_pac_utils.js:48- PAC DNS result consumption:
firefox/netwerk/base/ProxyAutoConfig.cpp:282 - Connection fallback to next DNS address:
firefox/netwerk/base/nsSocketTransport2.cpp:1778 - Fallback trigger conditions:
firefox/netwerk/base/nsSocketTransport2.cpp:1738 - DNS record iteration behavior:
firefox/netwerk/dns/nsDNSService2.cpp:166
Impact:
This vulnerability completely disables access control via PAC files.
For example, the examples in https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Proxy_servers_and_tunneling/Proxy_Auto-Configuration_PAC_file#isinnet can be bypassed using this vulnerability.
function FindProxyForURL(url, host) {
alert(isInNet(host, "192.0.2.172", "255.255.255.255"));
// "PAC-alert: true"
}
Log files and POC are included in attachment.zip
Updated•15 days ago
|
Comment 1•15 days ago
|
||
Thank you for this bug report. Have you checked whether other browsers are also affected by this issue (If so, could you link to the related issue trackers)
To me this seems like an issue with how the PAC functions are defined. It kinda assumes that every host resolves to only one IP address, and only uses that one.
Updated•15 days ago
|
Comment 2•15 days ago
|
||
I think the impact of this issue is somewhat limited. Users using a PAC script, that uses isInNet to filter domains. The domain must also resolve to multiple IP addresses, and some of those addresses must not match the subnet check.
At the same time fixing this issue might be tricky. I can immagine we could pin the DNS record for the host to only contain the address returned when isInNet/dnsResolve was called.
| Reporter | ||
Comment 3•15 days ago
|
||
Have you checked whether other browsers are also affected by this issue (If so, could you link to the related issue trackers)
I haven't checked other browsers yet, but I can check within a few days if necessary. Given the pattern of this vulnerability, it's quite possible that other browsers have similar vulnerabilities.
By other browsers, am I correct in saying Chromium and Safari?
To me this seems like an issue with how the PAC functions are defined. It kinda assumes that every host resolves to only one IP address, and only uses that one.
That's right. This bypass occurs because isInNet expects only one DNS response, but the actual connection expects multiple DNS responses.
I think the impact of this issue is somewhat limited. Users using a PAC script, that uses isInNet to filter domains. The domain must also resolve to multiple IP addresses, and some of those addresses must not match the subnet check.
This vulnerability is still practical.
This vulnerability can be exploited if any of the IP addresses allowed in the PAC is inaccessible.
Since this vulnerability can be used to request name resolution from any domain, it is possible for an attacker to simultaneously return allowed and disallowed IP addresses from a domain controlled by the attacker.
In this case, if the returned allowed IP address is inaccessible, a fallback will occur, and the attacker will connect to the disallowed IP address.
If the PAC is in whitelist format (comparing the IP addresses returned by isInNet with allowed IP addresses), the range of allowed IP addresses is limited, making fallback less likely to occur than with the blacklist format described below. However, if the allowed IP address range is /24, for example, there will likely be at least one IP address that is inaccessible.
The problem is even more serious if the PAC is in blacklist format (comparing the IP addresses returned by isInNet with disallowed IP addresses).
In this case, the allowed IP address range is almost infinite, so the conditions for this vulnerability (fallback within allowed IP addresses) can be met virtually unconditionally.
While not realistic, a partial exploit is possible even if the PAC allows only a single IP address or an extremely small number of IP addresses, allowing connections to all allowed IP addresses.
In this case, if there is a port that is not open on allowed IP addresses but is open on disallowed IP addresses, it is possible to bypass PAC access control and connect to that port.
As mentioned above, there are three main scenarios for this vulnerability, but it is unlikely that no fallback will occur within the allowed IP addresses.
In fact, the example shown at https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Proxy_servers_and_tunneling/Proxy_Auto-Configuration_PAC_file#isinnet uses the second blacklist format introduced above, which allows unconditional bypass.
At the same time fixing this issue might be tricky. I can immagine we could pin the DNS record for the host to only contain the address returned when isInNet/dnsResolve was called.
Probably the easiest way to fix this vulnerability is to make IsInNet support multiple responses.
Comment 4•13 days ago
|
||
By other browsers, am I correct in saying Chromium and Safari?
Yes.
| Reporter | ||
Comment 5•13 days ago
|
||
I have confirmed and reported a similar vulnerability in Chromium.
As for Safari, I am unable to verify it at this time because I do not have a testing environment and cannot view the source code for the network portion.
The Chromium vulnerability I reported is here:
https://issues.chromium.org/issues/490381622
Comment 6•10 days ago
|
||
Looks like Chromium opened up the bug and decided it isn't a security problem.
Comment 7•9 days ago
|
||
Updated•8 days ago
|
Description
•