{"schema":"threatline.record/2","record":{"id":"GHSA-h44v-39x6-9xvg","aliases":["GHSA-h44v-39x6-9xvg"],"summary":"`freerdp_http_request()` completes TLS handshakes without peer or hostname verification","details":"This issue was found by Anthropic using Claude to study the security of open-source software, validated manually\r\nby me from Ada Logics.\r\n\r\n## Summary\r\n\r\n`freerdp_http_request()` in `libfreerdp/utils/http.c` builds an `SSL_CTX`, loads the default trust\r\nstore, sets SNI, and then completes the handshake without ever enforcing verification. Any server\r\ncertificate is accepted. This helper is the only HTTPS client used by the Azure AD / Azure Virtual\r\nDesktop authentication flow, so an on-path attacker can terminate the connection to\r\n`login.microsoftonline.com`, read the OAuth authorization code the client POSTs to the token\r\nendpoint, and substitute the OpenID discovery document that selects those endpoints.\r\n\r\nAffected: `master` at `7e0a52a12449fc8a6ca1b108c22ffec47cbeba28` (reproduced at current master, 2026-09-14) and\r\nthe 3.x line; the code path is compiled in whenever `WITH_AAD` is enabled, which is the default\r\nwhen WinPR JSON support is present (`CMakeLists.txt:542`) and is set explicitly in\r\n`packaging/deb/freerdp-nightly/cmake-preload.cmake`.\r\n\r\nCWE-295 (improper certificate validation), contributing CWE-297 (improper validation of\r\nhost-specific certificate identity) and CWE-319 (transmission of sensitive information over a\r\nchannel that provides no effective protection).\r\n\r\n## Root cause\r\n\r\nAt `libfreerdp/utils/http.c:147` onwards:\r\n\r\n```c\r\nssl_ctx = SSL_CTX_new(TLS_client_method());\r\n...\r\nif (!SSL_CTX_set_default_verify_paths(ssl_ctx))   /* http.c:155 */\r\n...\r\nSSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);\r\nbio = BIO_new_ssl_connect(ssl_ctx);\r\n...\r\nif (!SSL_set_tlsext_host_name(ssl, hostname))     /* http.c:189 — SNI only */\r\n...\r\nif (BIO_write(bio, headers, (int)hlen) < 0)       /* http.c:196 — handshake happens here */\r\n```\r\n\r\n`SSL_CTX_set_default_verify_paths()` populates the trust store but leaves the verify mode at the\r\nOpenSSL default of `SSL_VERIFY_NONE`, under which a chain-verification failure does not abort the\r\nhandshake and is only observable through `SSL_get_verify_result()`.\r\n`SSL_set_tlsext_host_name()` sets the name the client sends in the ClientHello; it has no bearing\r\non certificate identity checking. The function never calls\r\n`SSL_CTX_set_verify(SSL_VERIFY_PEER, …)`, never sets an expected hostname\r\n(`SSL_set1_host` / `X509_VERIFY_PARAM_set1_host`), and never reads `SSL_get_verify_result()`.\r\n\r\nA repo-wide grep at the pinned commit returns zero occurrences of `SSL_VERIFY_PEER`,\r\n`SSL_CTX_set_verify`, `SSL_set1_host`, `SSL_get_verify_result` or `X509_VERIFY_PARAM_set1_host`\r\nanywhere in the tree, so no caller compensates. (The RDP transport's own TLS handling in\r\n`libfreerdp/crypto/tls.c` is a separate, verified path and is not affected.)\r\n\r\n## Impact / primitive\r\n\r\n`freerdp_http_request()` carries three requests, all part of the AAD/AVD flow:\r\n\r\n- `libfreerdp/core/aad.c:1032` — `GET .../v2.0/.well-known/openid-configuration`; the JSON returned\r\n  supplies `token_endpoint` and `authorization_endpoint` for the requests below.\r\n- `client/common/client.c:1289` — `POST` with\r\n  `grant_type=authorization_code&code=<CODE>&client_id=<ID>&scope=…&redirect_uri=…`\r\n  (body built at `client/common/client.c:2572` and `:2630`).\r\n- `libfreerdp/core/aad.c:229` — `POST grant_type=srv_challenge` (nonce).\r\n\r\nAn attacker who can redirect the client's connection to the identity provider (hostile Wi-Fi,\r\nDNS/ARP spoofing, a malicious resolver or upstream) obtains full read/write control of all three\r\nexchanges. The authorization code is the primary asset: `code_verifier` / `code_challenge` do not\r\nappear anywhere in the codebase, so PKCE does not bind the code to the client, and the public\r\n`client_id` and `redirect_uri` needed to redeem it travel in the same intercepted body. The\r\nsubstituted discovery document additionally gives persistent control over which endpoints\r\nsubsequent requests are sent to.\r\n\r\nScope caveats, stated plainly: this is not remotely triggerable on its own — it requires an\r\nattacker already positioned on the path to `login.microsoftonline.com`, and it only affects\r\nclients using the AAD/AVD authentication path in a `WITH_AAD` build. TLS is the control expected to\r\ndefeat that attacker, and for this traffic it provides none.\r\n\r\n## Reproduction\r\n\r\n[ANT-2026-RPRG90Z1-e2e.zip](https://github.com/user-attachments/files/32213453/ANT-2026-RPRG90Z1-e2e.zip)\r\n\r\n\r\nValidated end to end in a container at the pinned commit. The reproducer builds FreeRDP from source\r\nwith `WITH_AAD` enabled (asserted at build time against the generated `freerdp/config.h`), points\r\n`login.microsoftonline.com` at a local listener presenting a self-signed certificate for that name\r\nwhich no CA in `/etc/ssl/certs` signed, and then drives the two public entry points the real AAD\r\npath uses — `freerdp_utils_aad_get_wellknown()` (`include/freerdp/utils/aad.h:156`) and\r\n`freerdp_http_request()` (`include/freerdp/utils/http.h:75`) — from a small consumer linked against\r\nthe installed `libfreerdp3`/`libwinpr3`. `curl` against the same listener is included as a negative\r\ncontrol. Nothing is mocked, stubbed or patched.\r\n\r\n```\r\ndocker build -t ant-rprg90z1 docker-e2e/ && docker run --rm ant-rprg90z1\r\n```\r\n\r\nObserved output (excerpted; full log in the archive):\r\n\r\n```\r\n=== FreeRDP commit under test ===\r\n7e0a52a12449fc8a6ca1b108c22ffec47cbeba28\r\n\r\n=== repo-wide grep for peer/hostname verification enforcement ===\r\n(no matches anywhere in the tree)\r\n\r\n=== NEGATIVE CONTROL: curl (default verification) against the same listener ===\r\ncurl: (60) SSL certificate problem: self-signed certificate\r\ncurl exit=60\r\n\r\n=== FreeRDP public AAD/HTTPS API against the same listener ===\r\n[poc] step 1: freerdp_utils_aad_get_wellknown(\"login.microsoftonline.com\", \"common\")\r\n[poc] well-known document accepted by FreeRDP:\r\n{\"token_endpoint\":\"https://login.microsoftonline.com/common/oauth2/v2.0/token\",\"authorization_endpoint\":\"https://login.microsoftonline.com/common/oauth2/v2.0/authorize\",\"issuer\":\"https://attacker.example/\"}\r\n[poc] step 2: freerdp_http_request(token_endpoint, <authorization_code body>)\r\n[poc] token POST completed, HTTP status 200, response: {\"access_token\": \"attacker-issued\", \"token_type\": \"Bearer\"}\r\n\r\n=== attacker-side log ===\r\n[rogue] GET /common/v2.0/.well-known/openid-configuration (TLS handshake completed with self-signed cert)\r\n[rogue] POST /common/oauth2/v2.0/token\r\n[rogue] CAPTURED REQUEST BODY: grant_type=authorization_code&code=SECRET-AUTH-CODE-0.AXoA&client_id=a85cf173-4192-42f8-81fa-777a763e6e2c&redirect_uri=https%3A%2F%2Flogin.microsoftonline.com%2Fcommon%2Foauth2%2Fnativeclient\r\n```\r\n\r\nThe reproducer drives the exported library functions rather than `xfreerdp`, because completing a\r\nfull interactive AAD logon would additionally require a server that negotiates AAD authentication;\r\nthe shipped call sites listed above are identical calls to the same exported functions.\r\n\r\nReproducer attached as `ANT-2026-RPRG90Z1-e2e.zip`. The Dockerfile is commit-parametrized\r\n(`--build-arg TARGET_COMMIT=<sha>`).\r\n\r\n## Status\r\n\r\nUnpatched and reproduced at current master `7e0a52a12449fc8a6ca1b108c22ffec47cbeba28` (2026-09-14).\r\n`git log -- libfreerdp/utils/http.c` shows only refactoring commits since the file was added, and\r\nwe found no published FreeRDP advisory covering this code path — the existing TLS-identity\r\nadvisories (GHSA-43hh-p3vw-hfx3, GHSA-5wr6-8m8j-3h7f, GHSA-89c6-jjrw-96h4) concern the RDP\r\ntransport's certificate identity checks, a different component and mechanism.\r\n\r\n## Credit\r\n\r\nFound by Anthropic using Claude to study the security of open-source software, validated manually\r\nby Ada Logics. Handled under Anthropic's coordinated vulnerability disclosure policy\r\n(https://www.anthropic.com/coordinated-vulnerability-disclosure, 90 days). Please credit Claude and\r\nAda Logics in any advisory.\r\n","categories":["Unclassified"],"product":"FreeRDP","vendor":"","ecosystem":null,"affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/FreeRDP/FreeRDP/security/advisories/GHSA-h44v-39x6-9xvg","source_modified_at":"2026-09-28T08:33:17Z","fetched_at":"2026-10-23T20:00:35.122048Z","package":{"ecosystem":"c","name":"FreeRDP"},"range":"<= 3.31.1"}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/FreeRDP/FreeRDP/security/advisories/GHSA-h44v-39x6-9xvg","source_modified_at":"2026-09-28T08:33:17Z","fetched_at":"2026-10-23T20:00:35.122048Z","package":{"ecosystem":"c","name":"FreeRDP"},"version":"3.32.0"}],"cvss":null,"severity":"Medium","kev":{"listed":false},"epss":{"probability":null,"percentile":null,"score_date":null},"published_at":"2026-09-28T08:33:16Z","modified_at":"2026-09-28T08:33:17Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/FreeRDP/FreeRDP/security/advisories/GHSA-h44v-39x6-9xvg","fetched_at":"2026-10-23T20:00:35.122048Z","source_modified_at":"2026-09-28T08:33:17Z"}],"sources":["GitHub Advisory Database"],"references":[],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":null,"confidence":"unknown","reviewed":false},"change_timestamp":"2026-09-28T08:33:17Z","content_hash":"cae819c182933bec02d7d68c440a60785e446c239d424150f445cf0cd3442d72"}}
