TP-08_stride_threat_emulation

TP-08 — STRIDE Threat Emulation Tests

Domain: Active Threat Emulation (STRIDE Framework)
Standards: GSMA FS.40 v3.0 · 3GPP TS 33.501 · NIST SP 800-187
Prerequisites: TP-01 baseline established; both 4G and 5G labs running; Scapy/pycrate installed


TC-STRIDE-S01: Spoofing — Rogue gNB / IMSI Catcher Simulation

Threat Model — Spoofing

graph TD
    subgraph SCENARIO_4G["4G — IMSI Exposure (Stingray)"]
        UE4G["srsUE (4G UE)\n(real device equivalent)"]
        REAL_ENB["Legitimate eNB\n(srsENB)"]
        ROGUE_ENB["Rogue eNB\n(Stingray simulator)\nStronger signal"]

        UE4G -->|Selects strongest signal| ROGUE_ENB
        ROGUE_ENB -->|Identity Request\n(before security)| UE4G
        UE4G -->|Identity Response\nIMSI in CLEARTEXT| ROGUE_ENB
        ROGUE_ENB -.->|Relay or drop| REAL_ENB
    end

    subgraph SCENARIO_5G["5G — SUCI Protection (Mitigated)"]
        UE5G["UERANSIM UE (5G UE)"]
        ROGUE_GNB["Rogue gNB\n(fake UERANSIM gNB)"]

        UE5G -->|Registration Request| ROGUE_GNB
        Note1["SUCI encrypted\nNo IMSI in request"]
        ROGUE_GNB -.->|Cannot decode SUCI\nNo Home Network Private Key| BLOCKED["❌ SUPI not revealed\nIMSI Catcher FAILS in 5G"]
    end

    subgraph COMPARE["4G vs 5G Comparison"]
        C1["4G: IMSI in cleartext\nbefore AKA setup\n☠️ Stingray-exploitable"]
        C2["5G: SUCI encryption\nECIES scheme\n✅ IMSI protected"]
    end

    style ROGUE_ENB fill:#7b2d00,color:#fff
    style ROGUE_GNB fill:#7b2d00,color:#fff
    style BLOCKED fill:#27ae60,color:#fff
    style C1 fill:#c0392b,color:#fff
    style C2 fill:#27ae60,color:#fff

Objective

Simulate rogue gNB/eNB; demonstrate 4G IMSI exposure vs 5G SUCI protection.

Steps

# === 4G: Demonstrate IMSI Exposure ===

# 1. Start Open5GS 4G EPC and srsENB (TAC=1, PLMN=00101)
# 2. Start a "rogue" srsENB with higher signal — same PLMN, same TAC
#    (In ZMQ/software radio, both are equally strong — note the concept)

# Monitor S1AP traffic for Identity Response (IMSI in cleartext)
sudo tshark -i lo \
  -Y "s1ap && s1ap.IdentityResponse" \
  -T fields -e s1ap.id_NAS_PDU 2>/dev/null &

# Start srsUE — note: 4G will send IMSI in Identity Response if no GUTI cached
sudo srsue \
  --usim.imsi=001010000000001 \
  --usim.k=465B5CE8B199B49FAA5F0A2EE238A6BC \
  --usim.opc=E8ED289DEBA952E4283B54E88E6183CA \
  --usim.imei=356938035643809 \
  --log.all_level=info 2>&1 | grep -E "IMSI|Identity|attach"

# === 5G: Verify SUCI Protection ===

# 3. Capture Registration Request on N2
sudo tshark -i br-$(docker network ls --filter name=sbi -q) \
  -Y "ngap && nas-5gs.mm.message_type == 0x41" \
  -T fields \
  -e nas-5gs.mm.mobile_identity_type \
  -e nas-5gs.mm.suci.protection_scheme_id \
  2>/dev/null &

# Restart UE to trigger fresh registration
docker compose -f ~/open5gs-lab/5g/docker-compose.yml restart ue
sleep 15

# Check: Mobile Identity Type should be SUCI (1), not IMSI (2)
echo "=== 5G NAS Registration Request Mobile Identity ==="
tshark -r ~/open5gs-lab/captures/tc-reg-03.pcap 2>/dev/null \
  -Y "nas-5gs.mm.message_type == 0x41" \
  -T fields -e nas-5gs.mm.mobile_identity_type | head -5

Expected Results

Pass Criteria (5G)

Zero IMSI disclosures to gNB. SUCI type confirmed. 4G IMSI exposure documented as architectural limitation.


TC-STRIDE-T01: Tampering — User Plane MitM (GTP-U Content Modification)

Threat Model — Tampering

sequenceDiagram
    participant UE as UE
    participant GNB as gNB
    participant MITM as MitM Node\n(lab VM / iptables)
    participant UPF as UPF
    participant DN as Internet

    Note over UE,DN: THREAT VECTOR: S1-U/N3 GTP-U carries unencrypted\nuser traffic in 4G (and 5G without UP integrity)

    UE->>GNB: User data (HTTP GET request)
    GNB->>MITM: GTP-U encapsulated\n(TEID=0x1234, inner IP: HTTP GET)
    Note over MITM: ⚠️ MitM modifies inner IP payload\nChanges HTTP response content
    MITM->>UPF: GTP-U (same TEID, modified inner payload)
    UPF->>DN: Decapsulated modified packet

    Note over UE,DN: 4G RESULT: Modification transparent\nUser receives modified content\n(DNS poisoning, HTTP injection possible)

    rect rgb(26, 60, 100)
        Note over UE,DN: 5G WITH UP INTEGRITY (NIA2)
        UE->>GNB: PDCP PDU with integrity MAC
        GNB->>MITM: GTP-U with PDCP integrity protection
        MITM->>MITM: Modify inner payload\nbut cannot forge PDCP MAC
        MITM->>UPF: Modified GTP-U
        UPF->>UPF: PDCP integrity check FAILS
        UPF-->>GNB: PDCP integrity failure
        Note over UE,DN: ✅ 5G: Modification DETECTED
    end

Objective

Demonstrate user-plane tampering in 4G (expected gap) and verify 5G UP integrity detection.

Steps

# === 4G: Demonstrate GTP-U MitM ===

# 1. Enable IP forwarding on host (acts as transparent bridge)
sudo sysctl -w net.ipv4.ip_forward=1

# 2. Use iptables to intercept and modify GTP-U traffic on S1-U path
# (Lab: modify DNS response inside GTP-U tunnel)
# This uses Scapy for transparent MitM

python3 << 'PYEOF'
from scapy.all import *
from scapy.contrib.gtp import GTP_U_Header

# Conceptual MitM: intercept GTP-U, modify inner DNS response
# In a real lab, you'd run this on a VM bridging eNB to SGW-U

def gtp_mitm(pkt):
    """Intercept GTP-U and modify inner IP payload"""
    if GTP_U_Header in pkt and DNS in pkt:
        print(f"[INTERCEPT] GTP-U TEID={hex(pkt[GTP_U_Header].teid)}")
        print(f"[INTERCEPT] Inner DNS: {pkt[DNS].summary()}")
        # In real attack: modify DNS response to point to attacker's server
        # This demonstrates the lack of integrity protection on S1-U
        print("[WARNING] 4G S1-U has NO integrity protection!")
        print("[WARNING] Inner payload can be modified transparently")
        return pkt  # pass through unmodified (demonstration only)

# Listen for GTP-U traffic (demonstration without actual modification)
print("Starting GTP-U monitor (demonstration mode)...")
print("4G S1-U interface has no encryption or integrity protection.")
print("Any host on the S1-U segment can transparently modify user traffic.")
print("")
print("Mitigation: 5G UP Integrity (NIA2) + GTP-U IPsec tunnel")
PYEOF

# === 5G: Check UP Integrity Configuration ===

# 3. Check if SMF has UP integrity configured
docker exec open5gs-smf cat /etc/open5gs/smf.yaml 2>/dev/null | \
  grep -A5 "integrity\|security"

# 4. Check pcap for PDCP protection indicator
sudo tshark -i br-$(docker network ls --filter name=ran -q) \
  -Y "pdcp-lte || pdcp-nr" \
  -c 20 2>/dev/null

Expected Results

Pass Criteria

4G gap documented. 5G UP integrity config verified in smf.yaml. Document as architectural comparison.


TC-STRIDE-I01: Information Disclosure — MongoDB Direct Access

Threat Model — Information Disclosure

graph TD
    subgraph ATTACK_PATH["Attack Path: Lateral Movement to MongoDB"]
        ATK["Attacker gains foothold\n(e.g., via compromised NF container\nor rogue container on Docker network)"]

        ATK -->|1. Network scan| DISCOVERY["Discovers MongoDB\n172.22.0.2:27017"]
        DISCOVERY -->|2. Connect| AUTH_CHECK{"MongoDB\nauthentication?"}

        AUTH_CHECK -->|No auth (default)| MONGO_OPEN["mongosh mongodb://172.22.0.2/open5gs"]
        AUTH_CHECK -->|Auth required| BLOCKED_AUTH["✅ Access denied\nCredentials required"]

        MONGO_OPEN -->|3. Query subscribers| DATA["db.subscribers.find()\n→ ALL subscriber records:\n- IMSI\n- Authentication Key K\n- OPc\n- SQN\n- Subscriber profile"]

        DATA -->|Impact| I1["❌ K extracted\n→ Full AKA compromise\nClone any SIM"]
        DATA -->|Impact| I2["❌ OPc extracted\n→ Operator secret exposed"]
        DATA -->|Impact| I3["❌ SQN extracted\n→ Auth replay possible"]
        DATA -->|Impact| I4["❌ Mass subscriber PII\n→ GDPR violation\n→ SIM swap enablement"]
    end

    subgraph ISOLATION["Expected Controls"]
        CTL1["MongoDB only on SBI/control plane network\n(not accessible from RAN network)"]
        CTL2["SCRAM-SHA-256 authentication required"]
        CTL3["Bind to 127.0.0.1 only (production)"]
    end

    style MONGO_OPEN fill:#c0392b,color:#fff
    style I1 fill:#c0392b,color:#fff
    style I2 fill:#c0392b,color:#fff
    style I3 fill:#c0392b,color:#fff
    style I4 fill:#c0392b,color:#fff
    style BLOCKED_AUTH fill:#27ae60,color:#fff

Objective

Verify MongoDB subscriber database is isolated and requires authentication; lateral movement from RAN-side containers is blocked.

Steps

# 1. Attempt MongoDB access from UERANSIM (RAN-side) container
echo "=== Test 1: RAN container cannot reach MongoDB ==="
docker exec ueransim-ue bash -c \
  "curl -s --connect-timeout 3 http://172.22.0.2:27017 && echo 'REACHABLE' || echo 'BLOCKED'"
# Expected: BLOCKED (different network)

# 2. Check if MongoDB is on RAN network
echo "=== Test 2: MongoDB network attachment ==="
docker inspect open5gs-mongodb 2>/dev/null | jq '.[].NetworkSettings.Networks | keys'
# Should show ONLY SBI/control plane network, NOT RAN network

# 3. Attempt connection without authentication
echo "=== Test 3: MongoDB auth check ==="
docker exec open5gs-mongodb mongosh --eval \
  'db.runCommand({connectionStatus:1})' --quiet 2>&1 | head -5

# Try unauthenticated query
docker exec open5gs-mongodb mongosh open5gs --eval \
  'db.subscribers.countDocuments()' --quiet 2>&1

# 4. Check from a freshly launched container (simulating attacker foothold)
echo "=== Test 4: Attacker container on SBI network ==="
docker run --rm \
  --network open5gs-lab_sbi \
  curlimages/curl:latest \
  curl -s --connect-timeout 3 http://172.22.0.2:27017
# Should be accessible from SBI network — check if auth is needed

# 5. Verify MongoDB bind address
docker exec open5gs-mongodb cat /etc/mongod.conf 2>/dev/null | grep "bindIp"

# 6. Check what data is exposed (run as authorized user only)
docker exec open5gs-mongodb mongosh open5gs --eval \
  'db.subscribers.findOne({},{imsi:1, "security.k":1})' --quiet
echo "WARNING: Auth key K exposed above — must be encrypted at rest in production"

Expected Results

Finding

MongoDB accessible without auth: HIGH FINDING — all subscriber authentication keys exposed.
MongoDB accessible from RAN network: CRITICAL FINDING — RAN compromise leads to full subscriber database access.

Pass Criteria

MongoDB not reachable from RAN containers. Auth required for any query.


TC-STRIDE-E01: Elevation of Privilege — Cross-UE Session Modification

Threat Model — Elevation of Privilege

sequenceDiagram
    participant UE_A as UE-A (IMSI ...001)
    participant UE_B as UE-B (IMSI ...002)
    participant GNB as gNB
    participant AMF as AMF
    participant SMF as SMF

    Note over UE_A,SMF: THREAT: UE-A attempts to modify UE-B's PDU session\nto gain elevated QoS or disrupt service

    Note over UE_A,SMF: Legitimate state
    UE_A->>AMF: Registered (AMF-UE-NGAP-ID=100, SUPI=...001)
    UE_B->>AMF: Registered (AMF-UE-NGAP-ID=101, SUPI=...002)
    UE_A->>SMF: PDU Session 5 (DNN=internet)
    UE_B->>SMF: PDU Session 5 (DNN=iot)

    Note over UE_A: Attacker constructs NAS PDU Session Modification\nusing UE-B's PDU Session ID (5) but in UE-A's NAS context
    UE_A->>GNB: NAS: PDU Session Modification Request\n{PDU Session ID: 5, RequestedQoS: GBR 1Gbps}
    GNB->>AMF: NGAP UplinkNASTransport\n{AMF-UE-NGAP-ID: 100 — UE-A's ID!\n NAS: PDU Session Mod for session 5}

    AMF->>AMF: Bind PDU Session ID 5 to UE-A's NAS context\n(SUPI ...001, PDU session ID 5)
    AMF->>SMF: POST /nsmf-pdusession/v1/sm-contexts/ue-a-session5/modify
    Note over SMF: SMF looks up session by SUPI+PDU-Session-ID\nSUPI=...001, PDU-Session-ID=5 = UE-A's session
    SMF->>SMF: Apply modification to UE-A's session\n(not UE-B's!)

    Note over UE_B: UE-B's session unchanged\n✅ Context binding prevents cross-UE elevation

    Note over UE_A: If SMF used ONLY PDU-Session-ID without SUPI binding:
    Note over SMF: ❌ Could modify UE-B's session\n(privilege escalation)

Objective

Verify AMF and SMF bind PDU Session IDs to subscriber context (SUPI); cross-UE session modification is impossible.

Steps

# 1. Register two UEs
docker compose -f ~/open5gs-lab/5g/docker-compose.yml up -d ue
sleep 10
UE_A_IP=$(docker exec ueransim-ue ip addr show uesimtun0 | grep "inet " | awk '{print $2}' | cut -d/ -f1)

# Register UE-B
docker exec open5gs-mongodb mongosh open5gs --eval '
db.subscribers.insertOne({
  imsi: "001010000000002",
  security: {k: "465B5CE8B199B49FAA5F0A2EE238A6BC",
    opc: "E8ED289DEBA952E4283B54E88E6183CA", amf: "8000", sqn: NumberLong(0)},
  slice: [{sst: 1, default_indicator: true,
    session: [{name: "internet", type: 3,
      qos: {index: 9, arp: {priority_level: 8,
        pre_emption_capability: 1, pre_emption_vulnerability: 1}}}]}]
})'

docker run -d --name ueransim-ue-b \
  --cap-add=NET_ADMIN --network open5gs-lab_ran \
  -v ~/open5gs-lab/5g/config/ue-002.yaml:/etc/ueransim/ue.yaml \
  louisroyer/ueransim-ue:latest
sleep 15

# 2. Check SMF session contexts
docker logs open5gs-smf 2>&1 | grep "SUPI\|PDU Session" | tail -20

# 3. Attempt to modify UE-B's session from UE-A via UERANSIM CLI
# Use nr-cli to send PDU session modification request
docker exec ueransim-ue nr-cli imsi-001010000000001 \
  -e "ps-modify 5 --ambr-downlink 1000000000" 2>&1
# This requests QoS modification on PDU Session 5

# 4. Verify modification affected only UE-A's session
docker logs open5gs-smf 2>&1 | grep -E "modify|001010000000001|001010000000002" | tail -20
# UE-A session: modified (if SMF applied it)
# UE-B session: UNCHANGED

# 5. Cross-session via pycrate (conceptual demonstration)
python3 << 'PYEOF'
print("Cross-UE session attack mitigation analysis:")
print("")
print("AMF binds: AMF-UE-NGAP-ID → SUPI → NAS context → PDU Session IDs")
print("Any NAS PDU Session Modification from UE-A is bound to UE-A's SUPI")
print("SMF session lookup: /nsmf-pdusession/v1/sm-contexts/{UE-A-context-ID}")
print("UE-B's context ID is never exposed to UE-A")
print("")
print("Conclusion: Cross-UE session modification requires knowledge of")
print("UE-B's AMF-assigned context ID, which is never shared with other UEs")
PYEOF

# Cleanup
docker rm -f ueransim-ue-b

Expected Results

Pass Criteria

Cross-UE session modification impossible. Session context binding verified in AMF/SMF logs.