Stack-based buffer overflow in Western Digital My Cloud allows for remote code execution

Abstract

It was discovered that the Western Digital My Cloud is vulnerable to a stack-based buffer overflow in the authentication mechanism. By exploiting this vulnerability it is possible for an unauthenticated attacker to run arbitrary code with root privileges.

Tested versions

This vulnerability was successfully verified on a Western Digital My Cloud model WDBCTL0020HWT running firmware version 2.21.126. This issue isn't limited to the model that was used to find this vulnerability since most of the products in the My Cloud series share the same (vulnerable) code.

Fix

There is currently no fix available.

Introduction

Western Digital My Cloud is a low-cost entry-level network-attached storage device. It was discovered that the Western Digital My Cloud is vulnerable to a stack-based buffer overflow in the authentication mechanism. By exploiting this vulnerability it is possible for an unauthenticated attacker to run arbitrary code with root privileges.

Details

The previous My Cloud firmware version 2.21.119 was susceptible to an authentication bypass. To solve this an additional check was added that verifies if an admin user is actually logged in. The authentication check is performed in the login_check() function found in the /web/lib/login_checker.php file.

/* ret: 0: no login, 1: login, admin, 2: login, normal user */
	
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
	
		if (**wto_check($_COOKIE['username']) === 0**) //wto check fail
		$ret = 0;
	}
	
	return $ret;
}

To prevent the authentication bypass from occuring another check is performed by calling the wto_check() function with the value of the username cookie to check if a user is actually logged in. The wto_check() function calls the wto binary as shown in the following code fragment.

/*
  return value: 1: Login, 0: No login
*/
function wto_check($username)
{
	if (empty($username))
		return 0;
	
	exec(sprintf("wto -n \"%s\" -i '%s' -c", escapeshellcmd($username), $_SERVER["REMOTE_ADDR"]), $login_status);
	if ($login_status[0] === "WTO CHECK OK")
		return 1;
	else
		[...]
}

The username is escaped with the escapeshellcmd argument to prevent command injection (which is not sufficient as pointed out by Zenofex). Analysis of the wto binary shows that it is susceptible to a buffer overflow when it copies the provided username to the stack using the strcpy() function without performing any input checking.

To parse the provided parameters the wto binary uses the getopt() function (provided by libc). The follow listing shows the case called when the optstring equals n. The optarg contains a pointer to our provided input and this is directly passed to the strcpy() function as the source parameter via R1. The destination pointer is provided via R0 and this is a pointer to the stack.

.text:00010FC0 get_username                          
.text:00010FC0                                        
.text:00010FC0                 LDR             R3, =(optarg_ptr - _GLOBAL_OFFSET_TABLE_) ; jumptable 00010F48 case 13
.text:00010FC4                 LDR             R3, [R7,R3]
.text:00010FC8                 ADD             R0, SP, #0xE0+var_A0
.text:00010FCC                 LDR             R1, [R3] 
.text:00010FD0                 **BL              strcpy**
.text:00010FD4                 B               parse_args
.text:00010FD4                                   

Proof of concept

Sending a username consisting of 160 characters is enough to overwrite the saved LR register on the stack and to get control of the program counter (PC). The following core dump shows that besides controlling the value of LR (and therefore PC) we can set the values of R4-R8 and R10:

Core was generated by `wto -n AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x41414140 in ?? ()
(gdb) info reg
r0             0x0  0
r1             0x0  0
r2             0x0  0
r3             0xb68b526c   3062583916
r4             0x41414141   1094795585
r5             0x41414141   1094795585
r6             0x41414141   1094795585
r7             0x41414141   1094795585
r8             0x41414141   1094795585
r9             0x0  0
r10            0x41414141   1094795585
r11            0x0  0
r12            0x0  0
sp             0xbef6fbd8   0xbef6fbd8
lr             0x41414141   1094795585
pc             0x41414140   0x41414140
cpsr           0x60000030   1610612784
(gdb)

Vragen of feedback?