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:#fff

Objective

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

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 verification

Objective

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

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:#fff

Objective

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

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

Pass Criteria

All 4 timeline elements in pcap. Injected packets visible with rogue source IP. SVG generated. Full attack reconstructable from pcap alone.