原文始发于Octagon :CVE-2022-24990: TerraMaster TOS unauthenticated remote command execution via PHP Object Instantiation
CVE-2022-24990: TerraMaster TOS unauthenticated remote command execution via PHP Object Instantiation
Introduction
This report explains how researchers at Octagon Networks were able to chain two interesting vulnerabilities to achieve unauthenticated remote command execution as root on TerraMaster NAS devices running TOS version 4.2.29. Patches are distributed for 4.2.31 now.
Analyzing the PHP files
After downloading the installation package of the latest TOS package from the official download site and extracting it using binwalk, we can see the platform uses nginx server with PHP scripts. The PHP scripts are encrypted on disk. After decrypting them and analyzing their content, there is an interesting php script – /usr/www/module/api.php.
The script starts parsing the uri components in the following manner.
The
function parses the get parameters and assigns them in such a way that if the request is http://target/module/api.php?XXXX/YYYY, then
will be XXXX, and
is YYYY.
It then checks if the function is in an array of
, and if it’s not, it sets
to 1. This will be important detail for the exploit later.
It then instantiates the class stated by and calls the method stated by .$class
$function
One thing to notice here is that the line . If our function is not in an array by the name of , then a check will be made. It takes the TIMESTAMP http header and passes it to a function called , and compares the output of the function to the value in SIGNATURE http header. It then checks if the timestamp is not older than 5 minutes. The is a custom hashing function, which we will come to analyze later to figure out how SIGNATURE is created.if (!in_array($function, $class::$notHeader)) {
$notHeadeer
tos_encrypt_str
tos_encrypt_str
Looking for php scripts which have classes which can be invoked this way, I came across . To start off, it has two arrays of method name, and ./usr/www/include/class/mobile.class.php
$notCheck
$notHeader
Then it makes three check in its constructor.
The first check ensures that if the method name invoked is not in array, it tests whether the user-agent http header is ‘TNAS’ and AUTHORIZATION header is equal to . Otherwise it exits with an error message. The AUTHORIZATION header will be important later on. For now, let’s proceed to the second check.$notHeader
$this->REQUESTCODE
if is set, then it checks whether the user is logged in. Otherwise it will exit. And the final check:REQUEST_MODE
It checks whether the method name is in , and exits with an error if it’s not.$notCheck
Severe Information Leak: CVE-2022-24990
So, with the knowledge of the two php script chains, we know that the seven functions “webNasIPS”, “getDiskList”, “createRaid”, “getInstallStat”, “getIsConfigAdmin”, “setAdminConfig”, “isConnected” are in array in , and will set to 0, passing one of the checks in . And upon further checking these functions, is the only function which is both in and arrays of ‘s constructor, which effectively can pass two remaining checks. Let’s call webNasIPS and see what it returns.NO_LOGIN_CHECK
api.php
REQUEST_MODE
mobile.class.php
webNasIPS
$notCheck
$notHeader
mobile.class.php
It returns a lot of interesting data. Let’s look at the function webNasIPS.
Finding an OS command injection: CVE-2022-24989
Since gives us REQUESTCODE without authentication, we can now call from the seven functions we listed which are in and in array, but NOT in arrary. is one of the functions which fullfills this.webNasIPS
NOT_LOGIN_CHECK
$notCheck
$notHeader
createRaid
createRaid
takes two POST parameters by and and calls with the value of as the first parameter. Let’s have a closer look at defined in volume.class.php.raidtype
diskstring
$vol->volume_make_from_disks
raidtype
volume_make_from_disks
It takes the first parameter and inserts it into a string to call another function . is a function defined in .$this->fun->_backexec
_backexec
func.class.php
_backexec function passes the parameters it receives to without any sanitization. Therefore, it’s vulnerable to OS command injection.popen
Bypassing Timstamp header checks
We have now chained the information leak (admin password hash) with an OS injection vulnerability. But we have to return to timestamp header check that we said we will look at later.api.php
api.php ensures that if the method name is not in the class’s array, it will check that the TIMESTAMP header is not older than 300 seconds (5 minutes), and the SIGNATURE header has to be equal to the output of function call on the TIMESTAMP value. Now, we have three problems.$notHeader
tos_encrypt_str
- We have to get the time of the machine
- We have to know how is invoked, and call it with arbitrary value
tos_encrypt_str
- We have to calculate the right TIMESTAMP in epoch time from the machine’s time regardless of time difference
Let’s start with tos_encrypt_str.
Figuring out the custom hash function
Upon searching , we realize that it’s not defined in the PHP scripts. And after a google search, we realize that it’s not also in the default and common list of php extentions. This leads us to the conclusion that it’s a function in one of TerraMaster’s custom PHP extenstions. Listing the loaded PHP extensions:tos_encrypt_str
The extension sticks out. It exports one function, .php_terra_master.so
tos_encrypt_str
Calling will tell us that it returns a hash.tos_encrypt_str
Opening the shared object in IDA, and searching for the string , we find out that the hashing function is .tos_encrypt_str
sub_3738
It takes our string to be hashed from , and passes it to (using variable ). Before the php_sprintf call, there is a function call , whose return value is given to the php_sprintf call as parameter, with our string. Let’s take a look at it.zend_parse_parameters
php_sprintf
v8
sub_3694
__int64 __fastcall sub_3694(__int64 a1) { int v2; // w21 const char *v3; // x0 char v5[40]; // [xsp+38h] [xbp+38h] BYREF v2 = socket(2, 1, 0); if ( (v2 & 0x80000000) != 0 ) { v3 = “socket”; LABEL_5: perror(v3); return 0LL; } strcpy(v5, “eth0”); if ( (ioctl(v2, 0x8927uLL, v5) & 0x80000000) != 0 ) { v3 = “ioctl”; goto LABEL_5; } php_sprintf(a1, “%02x%02x%02x”, (unsigned __int8)v5[21], (unsigned __int8)v5[22], (unsigned __int8)v5[23]); return a1; }
This function gets the mac address of the interface and returns the last 3 bytes (device specific part) in hex. Then the in sub_3738 call formats it with our string to a final string to give it to . Therefore, if the mac address of eth0 for a device is 55:44:33:12:34:56 and our string to be hashed is XXXX, then the final string given to the actuall hash function (sub_2348) will be 123456XXXX.eth0
php_sprintf
sub_2348
Getting the later half of the mac
tos_encrypt_str
using the later half of the mac address of eth0 allows each TerraMaster device to derive different hashes for the same string. This prevents us from calling the function with arbitrary stirng, since we need the mac address.
Lucky for us, , which leaks the admin password hash, also gives us the mac address of the default gateway interface, which is often eth0. Armed with the later half of the mac address, we can write a small hook to call tos_encrypt_str with any string value we want to generate the hash.webNasIPS
Another information leak: time of the machine
Now that we can generate the right hash for any arbitrary string for any TerraMaster device, the only remaining piece is the timestamp value. On some TerraMaster TOS devices, there is a header that tells us what timezone the target machine is synched to, so we can sync our time with the victim and have an accurate timestamp. However, on TerraMaster devices, it is hardened and the header is stripped so it is not easy for us to figure out what timezone the machine is synched to.Date
Date
Upon replaying with the request, we realize that sending a request to any of these functions in a way that the result is a failure will yield the time of the machine being leaked. Here is a simple request to call with no AUTHORIZATION, TIMESTAMP and SIGNATURE headers.createRaid
In the request, there is a value of , which contains the date of the machine with the 24 hour format.ctime
Now, with all the pieces on our hand, the only remaining task is to calculate the timestamp in epoch regardless of the machine’s timezone.
Calculating the timestamp
To do this, we can use any unix timestamp calculator. These are the steps to do that.
- We take the time of the machine from the and convert it to epoch time
ctime
- We calculate our own time (both formal and epoch) (for example: using PHP’s functionality:
date
php -r 'echo time() . " " . date("Y-m-d H:i:s") . PHP_EOL; '
) - We subtract the epoch time of our machine from the target machine
- We convert the subtraction result of the above calculation into relative time
- We add/subtract the relative time to our machine’s formal time
- We convert the result from the above calculation to epoch time
- We now have the right epoch time. We calculate the hash of this epoch time using the machine’s later half of the mac
- We invoke with our payload in
createRaid
raidtype
Exploitation
The final payload will look something like this.
Successful exploitation will contain a ‘createRaid successful’ or a ‘createRaid failed’ message.
On 4.1.x versions of TOS, there’s is no need for the timestamp and hash check, so the exploit becomes even simpler:
Conclusion
All in all, this was a very interesting project. we have used multiple components of an information leak, along with another information leak of the machine’s time, and chained it with an authenticated OS command injection to achieve an unauthenticated remote command execution as root.
The exploitation works on all TOS 4.2.x versions < 4.2.30, and on all 4.1.x versions.
Vendor Response
All vulnerabilities in this post have been patched by the vendor in February 2022.
转载请注明:CVE-2022-24990: TerraMaster TOS unauthenticated remote command execution via PHP Object Instantiation | CTF导航