TP-10_monitoring_detection
TP-10 — Monitoring, Detection & Logging Tests
Domain: Observability, Audit, and Forensics
Standards: 3GPP TS 33.117 §4.2.5 · GSMA FS.40 v3.0 §7 · NIST SP 800-86 · 3GPP TS 28.552
Prerequisites: TP-00 Step 9 complete (Prometheus + Grafana running); baseline registrations from TP-01 complete
TC-MON-01: Prometheus Metrics — AMF Registration Counter
Threat Model
graph TD
subgraph MONITORING["Monitoring Stack"]
PROM["Prometheus\n:9090"]
GRAFANA["Grafana\n:3000"]
ALERTMGR["Alertmanager"]
end
subgraph AMF_METRICS["AMF Metrics Exposure"]
AMF["AMF\n:9090/metrics"]
UE_COUNT["amf_registered_ue_count\n(gauge)"]
REG_RATE["amf_registration_rate\n(counter)"]
AUTH_FAIL["amf_authentication_failures_total\n(counter)"]
end
subgraph THREAT["Threat: No Monitoring = Blind Operation"]
T1["Attack in progress\n(DoS, auth storm)"]
T2["No metrics\n→ No alert\n→ No detection"]
T3["Operator discovers\nafter customer complaints"]
end
UE1["UE-1"] & UE2["UE-2"] & UE3["UE-3"] -->|Register| AMF
AMF --> UE_COUNT & REG_RATE & AUTH_FAIL
UE_COUNT & REG_RATE & AUTH_FAIL -->|Scrape 15s| PROM
PROM -->|Visualize| GRAFANA
PROM -->|Alert rules| ALERTMGR
T1 -->|Spike in metrics| UE_COUNT
UE_COUNT -->|Threshold breach| ALERTMGR
ALERTMGR -->|PagerDuty/Slack| OPS["✅ Operator alerted\n< 1 minute"]
style T3 fill:#c0392b,color:#fff
style OPS fill:#27ae60,color:#fffObjective
Verify Prometheus correctly scrapes AMF registration metrics and counters accurately reflect UE state in real time.
Steps
# 1. Check Prometheus targets are UP
echo "=== Prometheus Scrape Targets ==="
curl -s "http://localhost:9090/api/v1/targets" | \
jq '.data.activeTargets[] | {job: .labels.job, instance: .labels.instance, health: .health, lastScrape: .lastScrape}'
# 2. Check current registration count (baseline)
echo ""
echo "=== Baseline UE count ==="
curl -s "http://localhost:9090/api/v1/query?query=amf_registered_ue_count" | \
jq '.data.result'
BASELINE=$(curl -s "http://localhost:9090/api/v1/query?query=amf_registered_ue_count" | \
jq -r '.data.result[0].value[1] // "0"')
echo "Baseline count: ${BASELINE}"
# 3. Register 3 new UEs
for i in $(seq 1 3); do
docker run -d --name prom-test-ue-${i} \
--cap-add=NET_ADMIN --network open5gs-lab_ran \
-v ~/open5gs-lab/5g/config/ue-${i}.yaml:/etc/ueransim/ue.yaml \
louisroyer/ueransim-ue:latest
sleep 1
done
sleep 20
# 4. Check counter increased
echo "=== Post-registration count ==="
AFTER=$(curl -s "http://localhost:9090/api/v1/query?query=amf_registered_ue_count" | \
jq -r '.data.result[0].value[1] // "0"')
echo "Count after 3 registrations: ${AFTER}"
EXPECTED=$((BASELINE + 3))
echo "Expected: ${EXPECTED}"
[ "$AFTER" -eq "$EXPECTED" ] && echo "✅ PASS" || echo "⚠️ MISMATCH"
# 5. Deregister UEs
for i in $(seq 1 3); do docker rm -f prom-test-ue-${i}; done
sleep 20
# 6. Verify count decrements
AFTER_DEREG=$(curl -s "http://localhost:9090/api/v1/query?query=amf_registered_ue_count" | \
jq -r '.data.result[0].value[1] // "0"')
echo "Count after deregistration: ${AFTER_DEREG}"
echo "Expected: ${BASELINE}"
[ "$AFTER_DEREG" -eq "$BASELINE" ] && echo "✅ PASS — counter decremented" || \
echo "⚠️ Counter did not decrement (check AMF metric export)"
# 7. Check scrape interval
curl -s "http://localhost:9090/api/v1/query_range?query=amf_registered_ue_count&start=$(date -d '5 minutes ago' +%s)&end=$(date +%s)&step=15" | \
jq '.data.result[0].values | length'
echo "Data points in last 5 minutes (expect ~20 at 15s interval)"
Expected Results
- Prometheus target
open5gs-amfhealth:up - Counter increments by exactly 3 after 3 registrations
- Counter decrements after deregistration
- Scrape interval: ≤ 30 seconds
Pass Criteria
Metric accuracy ±0 over 5 registration/deregistration cycles. Scrape confirmed at 15s interval.
TC-MON-02: Anomaly Detection — Rapid Deregistration (SIM Swap Signal)
Threat Model
sequenceDiagram
participant LEGIT as Legitimate Subscriber\n(normal usage)
participant ATTACKER as SIM Swap Attacker
participant CARRIER as Carrier (Social Eng.)
participant AMF as AMF
participant MONITOR as Monitoring System
Note over LEGIT,MONITOR: BASELINE: Normal UE registration pattern
LEGIT->>AMF: Register (morning)
LEGIT->>AMF: Deregister (evening)
Note over MONITOR: 1 registration/day — normal
Note over ATTACKER,MONITOR: ATTACK: SIM swap fraud indicators
ATTACKER->>CARRIER: Social engineer customer service\n"Lost my SIM, please port IMSI to new SIM"
CARRIER->>AMF: Deregister old SIM (explicit deregistration)
ATTACKER->>AMF: Register with new SIM (same IMSI)
Note over MONITOR: ⚠️ SIGNAL DETECTED:\n2+ registrations from\ndifferent device (IMEI changed)
loop Attacker tests connectivity
ATTACKER->>AMF: Register
ATTACKER->>AMF: Deregister
ATTACKER->>AMF: Register (retry)
end
Note over MONITOR: 5+ deregistrations in 60s\nfor same SUPI\n→ Alert threshold breached
MONITOR->>MONITOR: Fire alert:\nsim_swap_indicator{supi=...}
MONITOR-->>CARRIER: ✅ Fraud flag raised\nAccount frozen pending verificationObjective
Verify log and metric systems can detect rapid registration/deregistration cycles for the same IMSI (SIM swap indicator).
Steps
# 1. Create alert rule for SIM swap detection
cat << 'EOF' | kubectl apply -f - 2>/dev/null || \
cat << 'EOF2' > /tmp/amf-alert-rules.yml
# For Kubernetes:
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: open5gs-fraud-detection
namespace: open5gs
spec:
groups:
- name: sim-swap-detection
rules:
- alert: SuspiciousRegistrationPattern
expr: |
increase(amf_deregistration_total[1m]) > 5
for: 0m
labels:
severity: high
annotations:
summary: "High deregistration rate — possible SIM swap"
description: "{{ $value }} deregistrations in 1 minute"
EOF
# For Docker Compose — add alert rule to prometheus config:
EOF2
mkdir -p /tmp/prometheus-rules
cat > /tmp/prometheus-rules/fraud.yml << 'EOF'
groups:
- name: sim-swap
rules:
- alert: RapidDeregistration
expr: increase(amf_deregistration_total[1m]) > 5
for: 0m
labels:
severity: high
annotations:
summary: "Possible SIM swap — high deregistration rate"
EOF
# 2. Simulate rapid registration/deregistration (SIM swap pattern)
echo "Simulating SIM swap pattern: 10 register/deregister cycles..."
for i in $(seq 1 10); do
CONTAINER="sim-swap-sim-${i}"
docker run -d --name ${CONTAINER} \
--cap-add=NET_ADMIN --network open5gs-lab_ran \
-v ~/open5gs-lab/5g/config/ue.yaml:/etc/ueransim/ue.yaml \
louisroyer/ueransim-ue:latest
sleep 2
docker rm -f ${CONTAINER}
sleep 1
done
# 3. Check AMF logs for repeated deregistration of same SUPI
echo ""
echo "=== Checking AMF logs for rapid deregistration pattern ==="
docker logs open5gs-amf 2>&1 | \
grep "001010000000001" | \
grep -E "registered|deregistered|removed" | \
awk '{print NR": "$0}' | tail -20
DEREG_COUNT=$(docker logs open5gs-amf 2>&1 | \
grep "001010000000001" | \
grep -c "deregistered\|removed\|detached" 2>/dev/null || echo "0")
echo ""
echo "Deregistration events for IMSI 001010000000001: ${DEREG_COUNT}"
[ "${DEREG_COUNT}" -gt 5 ] && echo "⚠️ ALERT THRESHOLD BREACHED" || echo "Below threshold"
# 4. Check Prometheus for metric spike
curl -s "http://localhost:9090/api/v1/query?query=increase(amf_deregistration_total[5m])" | \
jq '.data.result'
# 5. Check IMEI change (stronger SIM swap indicator)
echo ""
echo "=== IMEI change detection ==="
docker logs open5gs-amf 2>&1 | grep -E "IMEI|PEI" | tail -10
Expected Results
- AMF logs show > 5 deregistration events for same IMSI within 1 minute
- Prometheus metric
increase(amf_deregistration_total[1m]) > 5evaluates to true - Alert fires (if Alertmanager configured)
- Log timestamp chain allows reconstruction of registration pattern
Pass Criteria
Deregistration pattern detectable from logs and metrics. Alert threshold documented. Forensic chain reconstructable.
TC-MON-03: Audit Log Completeness — SBI Call Chain
Threat Model
graph LR
subgraph AUDIT_TRAIL["Complete Audit Trail Required"]
E1["AMF: Registration Request received\nSUPI + timestamp + gNB-ID"]
E2["AUSF: AKA challenge issued\nSUPI + RAND + timestamp"]
E3["UDM: Subscriber data queried\nSUPI + requestor NF-ID + data type"]
E4["SMF: PDU Session established\nSUPI + DNN + slice + IP + timestamp"]
E5["NRF: NF discovery\nrequester + target NF type"]
end
E1 --> E2 --> E3 --> E4
E1 --> E5
subgraph GAP["Audit Gap — Information Disclosure Risk"]
G1["If UDM has no SUPI in log:\n→ Cannot identify which subscriber\n was queried"]
G2["If timestamps absent:\n→ Cannot establish timeline\n for forensic investigation"]
G3["If requestor NF absent:\n→ Cannot detect rogue NF\n accessing subscriber data"]
end
subgraph COMPLIANCE["Compliance Requirements"]
C1["GDPR Art.30: Record of processing\nactivities with SUPI"]
C2["3GPP TS 33.117 §4.2.5:\nAccess log for personal data"]
C3["GSMA FS.40 §7.4:\nSecurity logging requirements"]
end
style G1 fill:#c0392b,color:#fff
style G2 fill:#c0392b,color:#fff
style G3 fill:#c0392b,color:#fffObjective
Verify all SBI call events are logged with SUPI, timestamp, requestor NF ID, and result code.
Steps
# 1. Perform a complete registration to generate log events
docker compose -f ~/open5gs-lab/5g/docker-compose.yml restart ue
sleep 20
IMSI="001010000000001"
TIMESTAMP=$(date +%Y-%m-%d)
# 2. Collect logs from all NFs
echo "=== Collecting NF logs for IMSI ${IMSI} ==="
for NF in amf smf nrf scp ausf udm udr pcf nssf; do
echo ""
echo "--- ${NF^^} ---"
docker logs open5gs-${NF} 2>&1 | grep "${IMSI}\|imsi-${IMSI}" | tail -10
done
# 3. Check AMF: Registration Request + Accept logged
echo ""
echo "=== AMF: Registration events ==="
docker logs open5gs-amf 2>&1 | \
grep -E "(${IMSI}|Registered|Registration)" | \
head -20
# 4. Check AUSF: AKA events
echo "=== AUSF: Authentication events ==="
docker logs open5gs-ausf 2>&1 | \
grep -E "(${IMSI}|AKA|auth)" | head -10
# 5. Check UDM: Data query events (GDPR-relevant)
echo "=== UDM: Subscriber data access ==="
docker logs open5gs-udm 2>&1 | \
grep -E "(${IMSI}|nudm-sdm|nudm-uecm)" | head -10
# 6. Check SMF: PDU Session events
echo "=== SMF: PDU Session events ==="
docker logs open5gs-smf 2>&1 | \
grep -E "(${IMSI}|PDU Session|nsmf)" | head -10
# 7. Verify log chain completeness
echo ""
echo "=== Log Chain Completeness Check ==="
declare -A EVENTS
EVENTS["AMF-Registration"]=$(docker logs open5gs-amf 2>&1 | grep -c "${IMSI}" 2>/dev/null || echo "0")
EVENTS["AUSF-AKA"]=$(docker logs open5gs-ausf 2>&1 | grep -c "${IMSI}" 2>/dev/null || echo "0")
EVENTS["UDM-Query"]=$(docker logs open5gs-udm 2>&1 | grep -c "${IMSI}" 2>/dev/null || echo "0")
EVENTS["SMF-Session"]=$(docker logs open5gs-smf 2>&1 | grep -c "${IMSI}" 2>/dev/null || echo "0")
for EVENT in "${!EVENTS[@]}"; do
COUNT="${EVENTS[$EVENT]}"
STATUS=$( [ "$COUNT" -gt "0" ] && echo "✅ Present (${COUNT} events)" || echo "⚠️ MISSING — audit gap!" )
echo " ${EVENT}: ${STATUS}"
done
Expected Results
- AMF: Registration Request + Accept logged with SUPI
- AUSF: AKA challenge and result logged with SUPI
- UDM:
nudm-sdmsubscription data query logged with SUPI + requestor NF ID - SMF: PDU Session Establishment logged with SUPI + DNN + IP assigned
- Each log entry has timestamp
Finding
Missing SUPI in any NF log: HIGH FINDING (GDPR Art.30 / TS 33.117 §4.2.5 compliance gap).
Pass Criteria
All 5 NF event types logged with SUPI. Full call chain reconstructable from logs alone.
TC-MON-04: Forensic pcap — Attack Reconstruction via 5g-trace-visualizer
Threat Model
sequenceDiagram
participant FORENSIC as Forensic Analyst
participant PCAP as pcap File\n(captured during TC-GTP-01)
participant WIRESHARK as Wireshark 4.4+
participant VIZ as 5g-trace-visualizer
participant REPORT as Incident Report
Note over FORENSIC,REPORT: OBJECTIVE: Reconstruct GTP-U injection attack\nfrom network capture evidence
FORENSIC->>PCAP: Load tc-gtp-01.pcap
PCAP-->>WIRESHARK: Parse packets
WIRESHARK->>WIRESHARK: Dissect:\n- Outer IP (rogue src 172.23.1.99)\n- GTP-U header (TEID=0xABCD1234)\n- Inner IP (spoofed UE 10.45.0.99)
FORENSIC->>WIRESHARK: Filter: gtp && ip.src == 172.23.1.99
WIRESHARK-->>FORENSIC: Injected GTP-U packets\n+ timestamps + payload
FORENSIC->>VIZ: 5g_trace_visualizer.py -i tc-gtp-01.pcap -o report.svg
VIZ->>VIZ: Parse NGAP/NAS/GTP/HTTP2\nGenerate sequence diagram
VIZ-->>REPORT: SVG: Attack timeline\n1. gNB setup\n2. UE register\n3. PDU session est.\n4. ⚠️ GTP-U INJECTION from 172.23.1.99\n5. UPF drop (or forward)
FORENSIC->>REPORT: Findings:\n- Attack source: 172.23.1.99\n- TEID exploited: 0xABCD1234\n- Attack time: [timestamp]\n- Impact: [dropped/forwarded]Objective
Verify that a pcap captured during TC-GTP-01 (GTP-U injection) contains sufficient detail to fully reconstruct the attack.
Steps
# Prerequisites: TC-GTP-01 was run with packet capture active
# pcap at: ~/open5gs-lab/captures/tc-gtp-01.pcap
# 1. Verify capture file exists and has content
ls -lh ~/open5gs-lab/captures/
tshark -r ~/open5gs-lab/captures/tc-gtp-01.pcap -c 1 2>/dev/null | head -3
# 2. Filter for injected GTP-U from rogue source
echo "=== Injected GTP-U packets from rogue source ==="
tshark -r ~/open5gs-lab/captures/tc-gtp-01.pcap \
-Y "gtp && ip.src == 172.23.1.99" \
-T fields \
-e frame.time \
-e ip.src \
-e ip.dst \
-e gtp.teid \
-e ip.inner.src \
-e ip.inner.dst 2>/dev/null
# 3. Check GTP-U dissection in Wireshark (tshark)
echo ""
echo "=== GTP-U packet details ==="
tshark -r ~/open5gs-lab/captures/tc-gtp-01.pcap \
-Y "gtp" \
-V 2>/dev/null | head -50
# 4. Generate sequence diagram with 5g-trace-visualizer
echo ""
echo "=== Generating sequence diagram ==="
cd ~/open5gs-lab/tools/5g-trace-visualizer
python3 5g_trace_visualizer.py \
-i ~/open5gs-lab/captures/tc-gtp-01.pcap \
-o ~/open5gs-lab/captures/tc-gtp-01-attack-timeline.svg \
2>/dev/null && echo "✅ SVG generated: tc-gtp-01-attack-timeline.svg" || \
echo "⚠️ 5g-trace-visualizer not configured — manual Wireshark analysis required"
# 5. Verify attack timeline elements in pcap
echo ""
echo "=== Attack Timeline Verification ==="
# Check gNB setup present
GNB_SETUP=$(tshark -r ~/open5gs-lab/captures/tc-gtp-01.pcap \
-Y "ngap.procedureCode == 21" -c 1 2>/dev/null | wc -l)
echo "gNB NGSetup: $([ $GNB_SETUP -gt 0 ] && echo '✅ Present' || echo '⚠️ Missing')"
# Check UE registration present
UE_REG=$(tshark -r ~/open5gs-lab/captures/tc-gtp-01.pcap \
-Y "nas-5gs.mm.message_type == 0x41" -c 1 2>/dev/null | wc -l)
echo "UE Registration: $([ $UE_REG -gt 0 ] && echo '✅ Present' || echo '⚠️ Missing')"
# Check GTP-U session
GTP_SESS=$(tshark -r ~/open5gs-lab/captures/tc-gtp-01.pcap \
-Y "gtp" -c 1 2>/dev/null | wc -l)
echo "GTP-U traffic: $([ $GTP_SESS -gt 0 ] && echo '✅ Present' || echo '⚠️ Missing')"
# Check injection packets (rogue source)
INJECTED=$(tshark -r ~/open5gs-lab/captures/tc-gtp-01.pcap \
-Y "gtp && ip.src == 172.23.1.99" 2>/dev/null | wc -l)
echo "Injected GTP-U packets: ${INJECTED}"
[ $INJECTED -gt 0 ] && echo "✅ Attack evidence captured" || \
echo "⚠️ No injection packets in capture — re-run TC-GTP-01 with capture active"
# 6. Export attack evidence summary
echo ""
echo "=== Attack Evidence Summary ==="
cat << EOF
Incident: GTP-U Tunnel Injection
Capture file: tc-gtp-01.pcap
Attack source: 172.23.1.99 (unauthorized)
Target: 172.23.0.30 (UPF N3 interface)
Method: GTP-U packet with spoofed source IP and valid TEID
Injected packets: ${INJECTED}
Evidence quality: $([ $INJECTED -gt 0 ] && echo "Sufficient for forensic report" || echo "Capture re-run required")
Wireshark filter: gtp && ip.src == 172.23.1.99
EOF
Expected Results
- pcap contains: gNB setup, UE registration, PDU session, AND injected GTP-U packets
- tshark filter
gtp && ip.src == 172.23.1.99returns > 0 packets - GTP-U TEID value extracted and matches expected injection TEID
- 5g-trace-visualizer produces SVG with attack timeline
- Attack reconstructable without access to NF logs (pcap-only forensics)
Pass Criteria
All 4 timeline elements in pcap. Injected packets visible with rogue source IP. SVG generated. Full attack reconstructable from pcap alone.