Files

228 lines
8.2 KiB
Markdown
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.
---
name: network-reconnaissance
description: "Class-level skill for network reconnaissance from containerized and general environments. Covers internal subnet scanning (TCP sweep, port scan, service detection, TLS/SSH analysis), external domain/service reconnaissance (DNS, SSL, HTTP headers, API discovery), and automated recurring scan setups."
version: 1.0.0
tags: [network, reconnaissance, scanning, security, dns, ssl, ports, containers]
---
## Overview
This umbrella skill consolidates all network scanning and reconnaissance capabilities:
- **Internal Subnet Scanning** — TCP sweep, port scanning, banner grabbing, SSH/TLS analysis from containers or Python
- **External Domain Reconnaissance** — DNS, SSL certificates, HTTP headers, API/service discovery
- **Automated Recurring Scans** — Weekly cronjob-based scanning with change detection
No nmap, no root privileges required. Pure Python stdlib (`socket`, `ssl`, `concurrent.futures`) for container environments.
---
## Section 1: Internal Subnet Scanning
### 1.1 Network Layout Discovery
**Always discover the actual IP format first.** Users may describe segments as "50.x" but the real network may be `10.0.X.Y`.
```bash
ip addr show eth0 | grep inet
ip route show
ping -c 1 -W 2 10.0.50.1
ping -c 1 -W 2 50.0.50.1
```
Common formats:
- `10.0.X.Y` where X = segment index (e.g., 10.0.20.10 = Hypervisor)
- Container usually sits in one segment (e.g., 10.0.30.230)
### 1.2 Two-Phase Scan Workflow
**Phase 1 — Host Discovery (fast TCP sweep)**
```python
import socket, concurrent.futures
PROBE_PORTS = [443, 80, 22, 2222, 3306, 8080, 8443]
def host_alive(ip, ports=PROBE_PORTS, timeout=0.3):
for p in ports:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
if sock.connect_ex((ip, p)) == 0:
sock.close(); return True
sock.close()
return False
with concurrent.futures.ThreadPoolExecutor(100) as pool:
alive = [ip for ip, ok in zip(segment_ips, pool.map(host_alive, segment_ips)) if ok]
```
**Phase 2 — Deep Scan (per host)**
```python
IMPORTANT_PORTS = {
22: "SSH", 23: "Telnet", 25: "SMTP", 53: "DNS", 80: "HTTP",
443: "HTTPS/TLS", 1883: "MQTT", 2222: "SSH-alt",
2375: "Docker", 2376: "Docker-TLS", 3306: "MySQL",
5432: "PostgreSQL", 6379: "Redis", 8080: "HTTP-alt",
8443: "HTTPS-alt", 8883: "MQTT-TLS", 9090: "Prometheus/Grafana",
9200: "Elasticsearch", 11211: "Memcached", 27017: "MongoDB",
}
def deep_scan(ip):
hostname = socket.gethostbyaddr(ip)[0] # may be None
ports = scan_host_ports(ip, IMPORTANT_PORTS, workers=30)
tls = analyze_tls(ip) if any(p[0]==443 for p in ports) else None
ssh = get_ssh_banner(ip, 22) or get_ssh_banner(ip, 2222)
web = get_web_header(ip, 80)
return {"ip": ip, "hostname": hostname, "ports": ports, "tls": tls, "ssh": ssh, "web_server": web}
```
### 1.3 TLS Analysis
```python
def analyze_tls(ip, port=443, timeout=5.0):
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
try:
with socket.create_connection((ip, port), timeout=timeout) as sock:
with ctx.wrap_socket(sock, server_hostname=ip) as s:
return {
"protocol": s.version(), "cipher": s.cipher()[0],
"cert_cn": next((f[0][1] for f in s.getpeercert().get("subject",[]) if f[0][0]=="commonName"), None),
"cert_issuer": next((f[0][1] for f in s.getpeercert().get("issuer",[]) if f[0][0]=="commonName"), None),
"valid_from": s.getpeercert().get("notBefore"),
"valid_until": s.getpeercert().get("notAfter"),
}
except Exception as e:
return {"error": str(e)[:80]}
```
### 1.4 SSH Banner Grab
```python
def get_ssh_banner(ip, port=22, timeout=5.0):
sock = socket.create_connection((ip, port), timeout=timeout)
banner = sock.recv(256).decode("utf-8", errors="ignore").strip()
sock.close(); return banner
```
### 1.5 Output Format
```json
{
"scan_date": "2026-05-17T08:03:11.184435+00:00",
"total_hosts": 60,
"scan_duration_seconds": 94.4,
"segments": {
"Hypervisor": {"subnet": "10.0.20.0/24", "alive_count": 10, "hosts": [...]},
"Container": {"subnet": "10.0.30.0/24", "alive_count": 26, "hosts": [...]},
"Smart Home": {"subnet": "10.0.50.0/24", "alive_count": 24, "hosts": [...]}
}
}
```
- `hostname` is usually `null` (no reverse-DNS in internal networks)
- `vendor` and `mac` are NOT included (no MAC access from container)
---
## Section 2: External Domain & Service Reconnaissance
### 2.1 DNS Resolution
```bash
getent hosts cloud.familie-schoen.com
host cloud.familie-schoen.com
```
### 2.2 Port Scanning (Bash /dev/tcp)
```bash
bash -c 'echo >/dev/tcp/HOST/PORT 2>&1 && echo open || echo closed'
# Common ports: 22, 25, 53, 80, 443, 587, 993, 995, 3306, 5432, 8080, 8443, 9090
```
### 2.3 HTTP/HTTPS Analysis
```bash
curl -sI http://DOMAIN
curl -ksI https://DOMAIN
curl -kI -L https/DOMAIN
curl -ks https://DOMAIN/path
# Security headers:
curl -ksI https://DOMAIN | grep -iE 'strict-transport|content-security|x-frame|x-xss|cache-control'
```
### 2.4 SSL Certificate Extraction
```bash
openssl s_client -connect HOST:443 -servername HOST 2>&1 </dev/null
openssl s_client -connect HOST:443 -servername HOST 2>&1 </dev/null | \
openssl x509 -noout -subject -issuer -dates -ext subjectAltName
```
### 2.5 API/Service Discovery
```bash
for endpoint in /api/v1/version /api/health /healthz /version /status /robots.txt; do
r=$(curl -ks https://HOST$endpoint)
[ -n "$r" ] && echo "$endpoint: $r"
done
```
Common services to fingerprint:
- Seafile: `/api2/server-info/`, `/seafhttp/`
- GitLab: `/api/v4/version`
- Nextcloud: `/.well-known/caldav`
- Grafana: `/api/health`
- Prometheus: `/metrics`
### 2.6 HTML Content Analysis
```bash
curl -ks https://DOMAIN | grep -ioE '<title>[^<]+'
curl -ks https://DOMAIN | grep -ioE 'wordpress|django|flask|react|angular|nextcloud|seafile|gitea|jenkins|grafana|prometheus'
```
---
## Section 3: Automated Recurring Scanning
For SRE-style weekly network scans with change detection, set up a `no_agent` cronjob:
```bash
# 1. Write the scan script to ~/.hermes/scripts/network-scan.py
# 2. Create the cronjob:
cronjob action=create \
name="weekly-network-scan" \
script="network-scan.py" \
schedule="0 8 * * 0" \
no_agent=true \
deliver="origin"
```
**Critical rules:**
- `script` parameter accepts **filename only** (relative to `~/.hermes/scripts/`), NOT an absolute path.
- Prefer `--no-agent` for pure script execution — faster, avoids model format issues.
- After any Hermes update, verify cron job model format (see `references/cron-model-fix.md`).
### Change Detection
Load the previous scan file and diff:
- New IPs → new hosts
- Missing IPs → gone hosts
- New ports on existing hosts → new services
- Different SSH banners → version changes
---
## Pitfalls
| Pitfall | Symptom | Solution |
|---------|---------|----------|
| Using nmap | Permission denied | Use Python stdlib only |
| Using arping | Hangs forever | NEVER use arping in containers |
| Using `ip neigh` for MAC | No MACs returned | OUI vendor detection is useless from container |
| Sequential TCP scan | 14+ min for /24 | Use ThreadPoolExecutor (100 workers sweep, 30 deep scan) |
| Wrong IP format | Can't reach targets | Verify with `ip route` and `ping` first |
| `script` path absolute | Cronjob fails | Use filename only in `script` param |
| Cron model null after update | `Error code: 400` | Update with `provider/model` format |
| ShareGPT first-run delay | 25 min wait | Pre-download once before benchmark suite |
| NumPy X86_V2 crash | `RuntimeError: NumPy...X86_V2` | `pip install "numpy<2.0" --force-reinstall` |
| Prefill throughput bias | First request inflates latency | Pre-warm with throwaway run, or filter `turn_index=0` from JSONL |
---
## References
- `references/container-network-recon-session-2026-04.md` — Session notes from initial network discovery
- `references/domain-service-recon-familie-schoen.md` — External domain scan example
- `references/cron-model-fix.md` — Post-Hermes-update cron job model fix
- `scripts/network-scan.py` — Full internal scan script (copy to `~/.hermes/scripts/`)