← Back to CVE List
CVE-2026-102612NVD
Vulnerability Summary
Summary `bws_srv_start()` in `ports/linux/websocket-srv.c` builds the libwebsockets server context with a CA certificate loaded but never sets `LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT`, so the hub accepts TLS connections from clients presenting no certificate at all. Any network-reachable host can join the BACnet/SC mesh without credentials. Reachable via remote-network (network access to port 50050 over wss://; no credentials required). Scope and class This is a certificate-validation defect (CWE-295) in the reference BACnet/SC datalink shipped under `ports/`, not in the protocol core (`src/`). It is not an operator misconfiguration: the verify option is absent unconditionally and the `bws_srv_start()` API exposes no parameter to request peer verification, so a deployer cannot enable the mutual TLS the spec mandates without patching source. The reference port is what the `bacschub`/`bacserv` apps and the documented BACnet/SC build use; the project ships no alternative cert-validating transport. Exploitation needs network reachability to the hub, not a memory-corruption primitive. Affected version - Target: bacnet-stack 1.6.0-rc1 - Commit read: `1068250f197c0cc78b6357374d8be5df7d94b3cc` - Bug is in `ports/linux/websocket-srv.c`, the reference port datalink (the only BACnet/SC transport the project ships), not a demo. `bws_srv_start()` is invoked from `src/bacnet/datalink/bsc/bsc-socket.c:1574` on the `BSC_SOCKET_CTX_ACCEPTOR` path. Environment - OS: Ubuntu noble (container `icsloop-analyzer:latest`) - libwebsockets-dev 4.3.3, OpenSSL 3.x - Build: `make BACDL=bsc BACNET_PORT=linux sc-hub` (stock release flags, no sanitizer needed; this is a behavioral bypass, not a memory-safety crash) - Commit: `1068250f197c0cc78b6357374d8be5df7d94b3cc` Affected path ``` bsc_init_ctx() acceptor path -- src/bacnet/datalink/bsc/bsc-socket.c:1574 (cfg->type == BSC_SOCKET_CTX_ACCEPTOR) bws_srv_start() -- ports/linux/websocket-srv.c:748-769 lws_create_context(&info) -- info.options missing LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT lws leaves SSL_CTX at SSL_VERIFY_NONE (no client cert requested) rogue client completes TLS handshake without presenting any certificate bsc_process_srv_awaiting_request() -- src/bacnet/datalink/bsc/bsc-socket.c:819 bvlc_sc_encode_connect_accept() sends Connect-Accept (BVLC_SC_CONNECT_ACCEPT = 0x07) c->state = BSC_SOCK_STATE_CONNECTED -- bsc-socket.c:928 ``` Severity Pre-auth, network-reachable, no user interaction. The authenticity guarantee of the only authenticated BACnet transport is defeated, so integrity is High; confidentiality and availability are Low because the demonstrated primitive is admission, not data exfiltration or service loss. Root cause `bws_srv_start()` populates `struct lws_context_creation_info` and calls `lws_create_context()`. The CA cert is correctly loaded: `ports/linux/websocket-srv.c:755` ```c info.server_ssl_ca_mem = ca_cert; ``` `ports/linux/websocket-srv.c:756` ```c info.server_ssl_ca_mem_len = ca_cert_size; ``` But the options set on the context are only: `ports/linux/websocket-srv.c:759` ```c info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; ``` `ports/linux/websocket-srv.c:760` ```c info.options |= LWS_SERVER_OPTION_FAIL_UPON_UNABLE_TO_BIND; ``` `LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT` is absent from the entire function. Without it, libwebsockets leaves the `SSL_CTX` at `SSL_VERIFY_NONE`: no client certificate is requested and none is validated. The CA cert loaded at lines 755-756 is never used for client verification. The same defect is present in: - `ports/win32/websocket-srv.c:742-743` - `ports/bsd/websocket-srv.c:757-758` ASHRAE 135 Annex AB mandates mutual TLS for all BACnet/SC connections. Relationship No CVE assigned. The libwebsockets and OpenSSL layers behave as documented; the defect is the missing require-cert option in bacnet-stack's own port code. The win32 and bsd ports carry the identical omission (same `bws_srv_start()` pattern), so a single conceptual fix covers all three. This is the first BACnet/SC auth finding in this audit set; it does not depend on and is not chained with any memory-safety finding here. Reproduction This is a behavioral auth bypass, not a sanitizer crash. The proof is that a rogue client presenting no certificate is admitted to the real hub and receives a Connect-Accept. 1. Build the SC hub: `make BACDL=bsc BACNET_PORT=linux sc-hub` (produces `bin/bacschub`, 1.6.0-rc1). 2. `cd reports/needs-triage/ICS-189-bacnet-stack-sc-server-no-client-cert-verify/poc` 3. Generate the hub's CA and operational cert: `bash gen_certs.sh`. 4. Run the hub: `BACNET_SC_HUB_FUNCTION_BINDING=50050 ... bacschub &` (listens on `wss://127.0.0.1:50050`). 5. Fire the rogue client: `python3 rogue_client.py` (uses `ssl.CERT_NONE`, presents no client cert). 6. Observe the hub accept the TLS handshake and reply with a BVLC-SC Connect-Accept. Reproduction status: yes-rebuilt-and-ran. <details> <summary>Live output (poc/evidence.txt)</summary> ``` [] Connecting to wss://127.0.0.1:50050 (subprotocol='hub.bsc.bacnet.org') [] No client certificate presented (ssl.CERT_NONE) [+] WebSocket TLS handshake succeeded -- hub accepted connection without client cert [+] Server replied: [BVLC-SC CONNECT-ACCEPT] fn=0x07 ctrl=0x00 msg_id=0x0001 hub_vmac=e2:2a:cc:c7:af:e2 ... AUTH BYPASS CONFIRMED ``` </details> The hub returned a BVLC-SC Connect-Accept (`fn=0x07`) to a client that presented no certificate and set its socket to `BSC_SOCK_STATE_CONNECTED`, admitting an unauthenticated peer. Impact Pre-authentication bypass of the only authenticated BACnet transport. An attacker with network access to port 50050 can join the BACnet/SC hub as a trusted node without any certificate. The PoC demonstrates the admission step directly: a no-cert client reaches `BSC_SOCK_STATE_CONNECTED` and receives a BVLC-SC Connect-Accept. Property-write and command injection across the mesh are not individually demonstrated by the PoC. They are inferred from the hub's role: it forwards BVLC-SC messages between admitted peers, so a peer admitted without a certificate can address other enrolled devices on the network as a legitimate node. The demonstrated primitive is unauthenticated admission; the broader read/write/command reach follows from message forwarding, not from a separate exploit step shown here. This defeats the BACnet/SC security model. Not RCE without a chained memory-corruption vulnerability. Suggested fix Set the missing option in `bws_srv_start()` in all three ports: ```diff --- a/ports/linux/websocket-srv.c +++ b/ports/linux/websocket-srv.c @@ -760,6 +760,7 @@ info.options |= LWS_SERVER_OPTION_FAIL_UPON_UNABLE_TO_BIND; + info.options |= LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT; ``` Apply the same one line in `ports/win32/websocket-srv.c` (after line 743) and `ports/bsd/websocket-srv.c` (after line 758). This causes libwebsockets to call `SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, ...)`, rejecting any client that does not present a certificate signed by the loaded CA. Remediation Add the SSL option as suggested in PR #1436.
CVSS v3.1 Base Metrics — Score 8.1
Attack VectorNetwork
Attack ComplexityHigh
Privileges RequiredNone
User InteractionNone
ScopeUnchanged
ConfidentialityHigh
IntegrityHigh
AvailabilityHigh
Affected & Patched Versions
- bacnet-stack <= 1.5.0
- bacnet-stack 1.4.6, 1.5.2, 1.6.1, 1.7.0