All vulnerabilities
GHSA-h44v-39x6-9xvg

`freerdp_http_request()` completes TLS handshakes without peer or hostname verification

FreeRDP
CVSS
n/aMedium
ExploitationNo known exploitationNot in CISA KEV, which does not prove it is unexploitedCISA KEV
Exploit odds (EPSS)No scoreNo FIRST EPSS score in this snapshot yet
Fixed in3.32.0Fixed versionGitHub
Published28 Sep 2026Updated 28 Sep 2026GitHub

What fixes it

A fixed version on one release line is not the upgrade target for every line. Check your exact version and any vendor backports.

Description

This issue was found by Anthropic using Claude to study the security of open-source software, validated manually by me from Ada Logics.

Summary

freerdp_http_request() in libfreerdp/utils/http.c builds an SSL_CTX, loads the default trust store, sets SNI, and then completes the handshake without ever enforcing verification. Any server certificate is accepted. This helper is the only HTTPS client used by the Azure AD / Azure Virtual Desktop authentication flow, so an on-path attacker can terminate the connection to login.microsoftonline.com, read the OAuth authorization code the client POSTs to the token endpoint, and substitute the OpenID discovery document that selects those endpoints.

Affected: master at 7e0a52a12449fc8a6ca1b108c22ffec47cbeba28 (reproduced at current master, 2026-09-14) and the 3.x line; the code path is compiled in whenever WITH_AAD is enabled, which is the default when WinPR JSON support is present (CMakeLists.txt:542) and is set explicitly in packaging/deb/freerdp-nightly/cmake-preload.cmake.

CWE-295 (improper certificate validation), contributing CWE-297 (improper validation of host-specific certificate identity) and CWE-319 (transmission of sensitive information over a channel that provides no effective protection).

Root cause

At libfreerdp/utils/http.c:147 onwards:

ssl_ctx = SSL_CTX_new(TLS_client_method());
...
if (!SSL_CTX_set_default_verify_paths(ssl_ctx))   /* http.c:155 */
...
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
bio = BIO_new_ssl_connect(ssl_ctx);
...
if (!SSL_set_tlsext_host_name(ssl, hostname))     /* http.c:189 — SNI only */
...
if (BIO_write(bio, headers, (int)hlen) < 0)       /* http.c:196 — handshake happens here */

SSL_CTX_set_default_verify_paths() populates the trust store but leaves the verify mode at the OpenSSL default of SSL_VERIFY_NONE, under which a chain-verification failure does not abort the handshake and is only observable through SSL_get_verify_result(). SSL_set_tlsext_host_name() sets the name the client sends in the ClientHello; it has no bearing on certificate identity checking. The function never calls SSL_CTX_set_verify(SSL_VERIFY_PEER, …), never sets an expected hostname (SSL_set1_host / X509_VERIFY_PARAM_set1_host), and never reads SSL_get_verify_result().

A repo-wide grep at the pinned commit returns zero occurrences of SSL_VERIFY_PEER, SSL_CTX_set_verify, SSL_set1_host, SSL_get_verify_result or X509_VERIFY_PARAM_set1_host anywhere in the tree, so no caller compensates. (The RDP transport's own TLS handling in libfreerdp/crypto/tls.c is a separate, verified path and is not affected.)

Impact / primitive

freerdp_http_request() carries three requests, all part of the AAD/AVD flow:

  • libfreerdp/core/aad.c:1032 — GET .../v2.0/.well-known/openid-configuration; the JSON returned

supplies token_endpoint and authorization_endpoint for the requests below.

  • client/common/client.c:1289 — POST with

grant_type=authorization_code&code=<CODE>&client_id=<ID>&scope=…&redirect_uri=… (body built at client/common/client.c:2572 and :2630).

  • libfreerdp/core/aad.c:229 — POST grant_type=srv_challenge (nonce).

An attacker who can redirect the client's connection to the identity provider (hostile Wi-Fi, DNS/ARP spoofing, a malicious resolver or upstream) obtains full read/write control of all three exchanges. The authorization code is the primary asset: code_verifier / code_challenge do not appear anywhere in the codebase, so PKCE does not bind the code to the client, and the public client_id and redirect_uri needed to redeem it travel in the same intercepted body. The substituted discovery document additionally gives persistent control over which endpoints subsequent requests are sent to.

Scope caveats, stated plainly: this is not remotely triggerable on its own — it requires an attacker already positioned on the path to login.microsoftonline.com, and it only affects clients using the AAD/AVD authentication path in a WITH_AAD build. TLS is the control expected to defeat that attacker, and for this traffic it provides none.

Reproduction

ANT-2026-RPRG90Z1-e2e.zip

Validated end to end in a container at the pinned commit. The reproducer builds FreeRDP from source with WITH_AAD enabled (asserted at build time against the generated freerdp/config.h), points login.microsoftonline.com at a local listener presenting a self-signed certificate for that name which no CA in /etc/ssl/certs signed, and then drives the two public entry points the real AAD path uses — freerdp_utils_aad_get_wellknown() (include/freerdp/utils/aad.h:156) and freerdp_http_request() (include/freerdp/utils/http.h:75) — from a small consumer linked against the installed libfreerdp3/libwinpr3. curl against the same listener is included as a negative control. Nothing is mocked, stubbed or patched.

docker build -t ant-rprg90z1 docker-e2e/ && docker run --rm ant-rprg90z1

Observed output (excerpted; full log in the archive):

=== FreeRDP commit under test ===
7e0a52a12449fc8a6ca1b108c22ffec47cbeba28

=== repo-wide grep for peer/hostname verification enforcement ===
(no matches anywhere in the tree)

=== NEGATIVE CONTROL: curl (default verification) against the same listener ===
curl: (60) SSL certificate problem: self-signed certificate
curl exit=60

=== FreeRDP public AAD/HTTPS API against the same listener ===
[poc] step 1: freerdp_utils_aad_get_wellknown("login.microsoftonline.com", "common")
[poc] well-known document accepted by FreeRDP:
{"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/"}
[poc] step 2: freerdp_http_request(token_endpoint, <authorization_code body>)
[poc] token POST completed, HTTP status 200, response: {"access_token": "attacker-issued", "token_type": "Bearer"}

=== attacker-side log ===
[rogue] GET /common/v2.0/.well-known/openid-configuration (TLS handshake completed with self-signed cert)
[rogue] POST /common/oauth2/v2.0/token
[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

The reproducer drives the exported library functions rather than xfreerdp, because completing a full interactive AAD logon would additionally require a server that negotiates AAD authentication; the shipped call sites listed above are identical calls to the same exported functions.

Reproducer attached as ANT-2026-RPRG90Z1-e2e.zip. The Dockerfile is commit-parametrized (--build-arg TARGET_COMMIT=<sha>).

Status

Unpatched and reproduced at current master 7e0a52a12449fc8a6ca1b108c22ffec47cbeba28 (2026-09-14). git log -- libfreerdp/utils/http.c shows only refactoring commits since the file was added, and we found no published FreeRDP advisory covering this code path — the existing TLS-identity advisories (GHSA-43hh-p3vw-hfx3, GHSA-5wr6-8m8j-3h7f, GHSA-89c6-jjrw-96h4) concern the RDP transport's certificate identity checks, a different component and mechanism.

Credit

Found by Anthropic using Claude to study the security of open-source software, validated manually by Ada Logics. Handled under Anthropic's coordinated vulnerability disclosure policy (https://www.anthropic.com/coordinated-vulnerability-disclosure, 90 days). Please credit Claude and Ada Logics in any advisory.

Text from GitHub Advisory Database

Affected versions 1

  • FreeRDP, c<= 3.31.1GitHub