Monthly Archives: February 2014

OS X Found me Within 23 Feet

Same topic as my last post, only this time a OS X implementation. Which is a little more troubling because now I'm next door to iOS and therefore iPhones. And iPads. And iPod touches...

int main(int argc, const char * argv[])
{
 @autoreleasepool {

  //Get the WiFi
  NSMutableArray* wifiAccessPoints = [[NSMutableArray alloc] init];
  for(NSString *name in [CWInterface interfaceNames])
  {
   CWInterface* interface = [CWInterface interfaceWithName:name];
   //enumerate over all visible networks.
   for(CWNetwork *network in [interface scanForNetworksWithName:nil error:nil])
   {
    NSDictionary* dict = @{@"macAddress" : [network bssid],
                       @"signalStrength" : [NSNumber numberWithInteger: 
                                                   [network rssiValue]]};
    [wifiAccessPoints addObject:dict];
   }
  }

  //Create the request object;
  NSDictionary* dictRequest = @{@"wifiAccessPoints" : wifiAccessPoints};
  NSError* err = nil;
  NSData* jsonData = [NSJSONSerialization dataWithJSONObject:dictRequest
                                      options:NSJSONWritingPrettyPrinted
                                        error:&err];

  //Transmit the request.
  NSURL *url = [NSURL URLWithString:(Still Elided)];
  NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
  [request addValue:(Still Elided) forHTTPHeaderField:@"User-Agent"];
  [request setHTTPMethod:@"POST"];
  [request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
  [request addValue:[NSString stringWithFormat:@"%ld", jsonData.length] 
                            forHTTPHeaderField:@"Content-Length"];
  [request setHTTPBody:jsonData];

  err = nil;
  NSHTTPURLResponse *res = nil;
  NSData *locationData = [NSURLConnection sendSynchronousRequest:request 
                                               returningResponse:&res 
                                                           error:&err];
  if(!err)
  {
   //There I am!
   NSString* locationString = [[NSString alloc] initWithData:locationData 
                                            encoding:NSUTF8StringEncoding];
   NSLog(@"%@", locationString);
  }
 }
 return 0;
}

I'm also a little shocked by how short it is.

Exploiting HTML5 Geolocation is Too Easy

Titling this post was hard. I wanted something attention getting but yet not hyperbolic. Hopefully what I've selected works. I do think it's a pretty no nonsense way to sum up what I'm about to go into here because Computer Security boils down to answering the question of, "Secure against whom?" Well, I think I should have been driven to a point where I gave up and started to work on something else instead of being here now, able to write this post with the findings I have.

What got this started was when I took Introduction to Computer Security in Grad School my professor's stated goal was to make the students in the class think like an attacker. Today, I think the "attacker" mentality is a good one for all software engineers to have. It gets us thinking off the happy path, in the weeds and muck, perhaps finding a path to somewhere that you as the author didn't even know existed.

In this post, that path metaphor may be too literal.

After I was done with the class I remember thinking to myself, "Gee, I know how to do some Internet Explorer automation, I wonder if I can use IE to get my physical location on Earth?" So, I spent some time figuring out how to do just that and those results results are here. I was going to make this post a retelling of that find but I got to thinking, "You know Paige? Over the years a lot of people have picked on Internet Explorer. Don't cha think there's enough fodder out there for that?" Well, I happened to agree with me so I thought, what else could I go after? Well, what about the service that makes HTML5's Geolocation work itself?

Using the HTML5 Geolocation service via the familiar way is done in browser, through JavaScript, with code that has these basic parts:

navigator.geolocation.getCurrentPosition(function(p) {
 console.log(p.coords.latitude);
 console.log(p.coords.longitude);
});

This is pretty much what my Internet Explorer solution relied on. Back then, I found that my IE instance was "hung" and that turned out to be because there was a, "Do you want to allow [X] website access to your location?" prompt up. I didn't want to look at a flashing IE window as automation ran so I added some registry keys to grant permanent permission to IE for it to be able to use Geolocaiton on a website I browsed to, automatically. When I was done I realized I'd in effect created something that could get my location on Earth without my permission.

Now, back to the task at hand.

What happens at the lower levels when you call getCurrentPosition in JavaScript? Well, in my case where the only wireless access I have is WiFi, all the wireless access points are scanned for their MAC addresses and signal strength because HTML5 Geolocation works by triangulating your device with the Access Points that it can see.

A lot of code is about to follow but all this C++/CLI code is doing is collecting the aforementioned information.

List<AccessPoint^>^ AccessPoint::QueryAll()
{
 DWORD dwCurrentVersion = 0;
 HANDLE hClient = INVALID_HANDLE_VALUE;
 DWORD dwResult = WlanOpenHandle(2, nullptr, &dwCurrentVersion, &hClient);     
 auto results = gcnew List<AccessPoint^>();

 if(dwResult != ERROR_SUCCESS)
  return results;

 PWLAN_INTERFACE_INFO_LIST pIfList = nullptr;

 dwResult = WlanEnumInterfaces(hClient, nullptr, &pIfList);
 for(int i = 0; i < pIfList->dwNumberOfItems; i++)
 {
  PWLAN_BSS_LIST pBSSList = nullptr;  
  dwResult = WlanGetNetworkBssList(hClient, 
                                   &pIfList->InterfaceInfo[i].InterfaceGuid, 
                                   nullptr, dot11_BSS_type_any, false, 
                                   nullptr, &pBSSList);
  if(dwResult == ERROR_SUCCESS)
  {
   PWLAN_AVAILABLE_NETWORK_LIST pAvailableList = nullptr;
   dwResult = WlanGetAvailableNetworkList(hClient, 
             &pIfList->InterfaceInfo[i].InterfaceGuid, 
             WLAN_AVAILABLE_NETWORK_INCLUDE_ALL_ADHOC_PROFILES | 
               WLAN_AVAILABLE_NETWORK_INCLUDE_ALL_MANUAL_HIDDEN_PROFILES, 
             nullptr, &pAvailableList);

   if(dwResult == ERROR_SUCCESS)
   {
    for (int availableIndex = 0; availableIndex < pAvailableList->dwNumberOfItems;
                                                           availableIndex++)
    {
     for(int bssIndex = 0; bssIndex < pBSSList->dwNumberOfItems; bssIndex++)
     {
      if(!memcmp(&pAvailableList->Network[availableIndex].dot11Ssid, 
                 &pBSSList->wlanBssEntries[bssIndex].dot11Ssid,
                 sizeof(DOT11_SSID)))
      {
       auto name = String::Empty;
       auto address = String::Empty;       

       for(int c = 0; 
           c < pBSSList->wlanBssEntries[bssIndex].dot11Ssid.uSSIDLength; 
           c++)
        name += (Char)pBSSList->wlanBssEntries[bssIndex].dot11Ssid.ucSSID[c];

       for(int c = 0; c < 6; c++)
       {
        if(c > 0)
         address += ":";

        address += pBSSList->wlanBssEntries[bssIndex].dot11Bssid[c]
                  .ToString("X02");
       }

       int quality = -(pAvailableList->Network[availableIndex].wlanSignalQuality 
                       / 2);
       results->Add(gcnew AccessPoint(name, address, quality));
       break;
      }
     }
    }

    if(pAvailableList != nullptr)
     WlanFreeMemory(pAvailableList);
   }

   if(pBSSList != nullptr)
    WlanFreeMemory(pBSSList);
  } 
 }

 if(pIfList != nullptr)
  WlanFreeMemory(pIfList);

 if(hClient != INVALID_HANDLE_VALUE)
  WlanCloseHandle(hClient, nullptr);

 return results;
}

Then I just had to send off the collected information to the right Web Service which I did in C#:

class Program
 {
  static void Main(string[] args)
  {
   var list = AccessPoint.QueryAll().Select(ap => new WiFiAccessPoint() 
   {
    macAddress = ap.MACAddress, 
    signalStrength = ap.SignalStrength
   });
   var data = new Request() 
   {
    cellTowers = new object[0], 
    wifiAccessPoints = list.ToArray() 
   };
   var serializer = new JavaScriptSerializer();
   var message = serializer.Serialize(data);

   byte[] bytes = System.Text.Encoding.ASCII.GetBytes(message);

   var request = WebRequest.Create([Elided but easy to get what is needed]) 
                                  as HttpWebRequest;
   request.UserAgent = [Elided but also very easy to figure out, if even needed];
   request.Method = "POST";
   request.ContentType = "application/json";
   request.ContentLength = bytes.Length;
   using (var stream = request.GetRequestStream())
    stream.Write(bytes, 0, bytes.Length);

   string result = string.Empty;
   using (var response = request.GetResponse())
   {
    if (response == null)
     throw new Exception("Yeah! I think this could be a good thing!");

    using (var responseStream = response.GetResponseStream())
    {
     using (var reader = new StreamReader(responseStream))
     {
      result = reader.ReadToEnd().Trim();
      Console.WriteLine(result);
     }
    }
   }

   using (var file = new StreamWriter("result.txt", false))
    file.Write(result);
  }
 }

Regarding the elided bits, I'm not going to reveal what I did to fill them in cause that gets into information I'd rather you discover on your own. A little bit of critical thinking is all that's needed to find it just like only a little bit of critical thinking was needed to carry this out end to end. Anyway and as for WiFiAccessPoint and Request, they are simply:

public class WiFiAccessPoint
{
 public string macAddress { get; set; }
 public int signalStrength { get; set; }
}

public class Request
{
 public object cellTowers { get; set; }
 public WiFiAccessPoint[] wifiAccessPoints { get; set; }
}

When I ran my project, my position was returned to me with an error of 33 feet. I was given no prompt and if I hid my console window I would have had no indicator that my process was running unless I was staring at process monitor.

The only thing that prevents an attacker from developing software that gets your device location is a requirement from the W3C (The group that dictates how HTML5 Geolocation shall work) that mandates apps ask for permission. If it would always have to ask instead of leaving it to merely trusting developers, or if it took someone with more knowledge than I to pull this off I wouldn't be bothered. As much. I like Geolocation for navigation, but I also like being asked my permission to opt apps in to using the Geolocation services on my devices. Google Maps, ok. Others, it depends.