FreeRDP RDP Man-in-the-Middle, Fixed in 3.32.2 (CVE-2026-121392)
FreeRDP before 3.32.2 accepts a server certificate on two hand-rolled identity checks, so a trusted-CA man-in-the-middle can intercept RDP sessions; update now.
TL;DR
- FreeRDP's client-side TLS identity check accepts a matching Common Name even when the Subject Alternative Names do not match, and matches IP-literal targets against DNS names. A man-in-the-middle holding a certificate from a client-trusted CA can intercept RDP sessions.
- Every FreeRDP release before 3.32.2 is affected, according to the CVE record. The July 2026 advisory that described these cases claimed a fix in 3.29.0 that never landed for two of its three cases.
- VulnCheck's CVE record scores the flaw 9.1 Critical on CVSS v4.0 and 7.4 High on CVSS v3.1. No exploitation is known, and the CVE is not in CISA's Known Exploited Vulnerabilities (KEV) catalog as of November 4, 2026.
- Update to FreeRDP 3.32.2 or later now. No configuration workaround is documented; see "How do I fix it?" for products that embed FreeRDP.
Am I affected?
You are exposed when all of these hold:
- You run a FreeRDP-based client, or a product embedding FreeRDP, at any version before 3.32.2. The CVE record marks every release from the first one as affected.
- An attacker can intercept or redirect the RDP TCP connection, and can present a certificate chaining to a CA the client already trusts. The advisory names the scenarios: a misissuing public CA, a compromised or rogue private CA, or another certificate-trust-chain anomaly.
- The client verifies server certificates. The original disclosure reproduced the flaws with
AutoAcceptCertificate = FALSE,AutoDenyCertificate = TRUEandAuthenticationLevel = 2.
An attacker who cannot obtain a trusted certificate is not helped by this flaw. The bug weakens server identity verification in trusted-CA scenarios; it does not create the trust.
| Version | Affected | Fixed version |
|---|---|---|
| 3.32.1 and every earlier release | Yes, in the default identity check | 3.32.2, released 2026-10-12 |
| 3.32.2 and later, including 3.33.0 | No | Keep updating |
3.32.2 and 3.33.0 carry one known defect in the new IP check, and it fails closed: it rejects valid matches instead of accepting bad ones (see "Why does the fix work?").
Check what you run:
# The FreeRDP 3 client front-ends all print the version
xfreerdp3 --version
# Debian-family package installs
dpkg -l | grep freerdp
# Library copies, including those bundled inside other products
ldconfig -p | grep libfreerdp
A product that bundles FreeRDP needs its own update; the version the product reports is the one to check against the advisory.
How do I fix it?
- Update to FreeRDP 3.32.2 or later. The fix commits landed in 3.32.2, released October 12, 2026, and every later release carries them.
- Update products that embed FreeRDP when their vendor ships the fixed library. The CVE record's affected range starts at the first release, so older embedded copies are in scope.
- Treat a session as compromised if it may have crossed an interceptor holding a client-trusted certificate. Such an attacker sees screen content, keystrokes and clipboard and can inject input, so rotate every credential used in that session and review what was done in it. The flaw does not give the attacker that network position; it lets an attacker who already holds one act inside the session.
No configuration workaround is documented. The follow-up advisory that found the incomplete fix proposes no mitigation. Its suggested fix is what 3.32.2 implements: the Common Name counts only when no DNS SAN exists, and IP targets match only iPAddress SAN entries. FreeRDP wrote that logic itself instead of calling OpenSSL's X509_check_host() and X509_check_ip_asc(), which the advisory names as applying the same rules.
What is the root cause?
FreeRDP performs its own Common Name and DNS SAN string matching instead of using OpenSSL's length-aware identity validation APIs. The vulnerable block, libfreerdp/crypto/tls.c lines 1859 to 1879 at the 3.32.1 tag:
/* compare against common name */
if (common_name)
{
if (tls_match_hostname(common_name, common_name_length, hostname))
hostname_match = TRUE;
}
/* compare against alternative names */
if (dns_names)
{
for (size_t index = 0; index < dns_names_count; index++)
{
if (tls_match_hostname(dns_names[index], dns_names_lengths[index], hostname))
{
hostname_match = TRUE;
break;
}
}
}
Two properties of this block break server identity verification:
- The Common Name check and the DNS SAN check are an unconditional OR. RFC 6125 section 6.4.4 requires the opposite: when a certificate carries any dNSName SAN entry, the Common Name is irrelevant. A certificate with
CN=rdp01.corp.exampleand a non-matching SAN list such asDNS:files.corp.examplestill passes a connection tordp01.corp.example. x509_utils_get_dns_names()requests only GEN_DNS entries. The code never reads iPAddress SAN entries anywhere. When the RDP target is an IP literal, FreeRDP therefore matches the IP as a plain string against the CN and DNS SAN entries.
A July 2026 advisory, GHSA-43hh-p3vw-hfx3, documented three identity-check weaknesses and stated version 3.29.0 fixed them. Its fix set only closed the first case, an embedded-NUL truncation. The Common Name and IP-literal cases stayed byte-identical from the 3.29.0 tag onward, and GHSA-jq25-3374-4262 documented that residual gap in October 2026.
How is it exploited?
- The attacker obtains a position to intercept or redirect the victim's RDP connection. The attacker also needs a certificate chaining to a CA the client trusts, whose Common Name is the target hostname or whose DNS SAN holds the target IP as text.
- The victim's FreeRDP client connects and runs
tls_verify_certificate()on the attacker's certificate. The chain and validity checks pass, because the certificate is genuinely issued by a trusted CA. - The Common Name branch sets
hostname_match = TRUEeven though the SAN entries do not match, or the IP-literal target matches a DNS SAN string. The client accepts the certificate as identifying the server. - The attacker's endpoint now carries the RDP session: screen content, keystrokes, clipboard and input injection, in the follow-up advisory's impact description.
No public proof of concept exists beyond the reproduction notes in the two advisories. The original advisory reproduced all three cases in FreeRDP's TLS handshake path, with a local test CA installed into an isolated FreeRDP certificate store. The follow-up called the unmodified tls_verify_certificate() directly on chain-verified certificates and did not run a full network handshake.
How does the attack flow?
A man-in-the-middle with a trusted certificate serves the RDP session, and the hand-rolled identity check accepts the mismatched certificate.
trusted-CA certificate"] --> B["Victim connects to
the RDP server"] B --> C["tls_verify_certificate runs
on the attacker's certificate"] C --> D["CN matches, SAN entries
do not: unconditional OR"] D --> E["No iPAddress SAN is
ever read for IP targets"] E --> F["Client accepts the
attacker's identity"] F --> G["Session traffic: screen,
keystrokes, clipboard"]
What can an attacker do?
The attacker intercepts the RDP session in full: screen content, keystrokes, clipboard and input injection, per the follow-up advisory's impact statement. Neither advisory claims code execution or direct credential extraction, and the original advisory also claims no memory corruption.
The attack needs the network position and the trusted certificate first. A certificate whose issuer the client does not trust fails the chain check before the identity matching runs. A client that pins or manually verifies certificates independently of this path is outside the described scenario.
Why does the fix work?
Two commits in 3.32.2 replace the hand-rolled matching for the two cases. Commit 1a23b21c37, "do not check common name if there are DNS SAN", moves the Common Name comparison behind an else if:
common_name = freerdp_certificate_get_common_name(cert, &common_name_length);
dns_names = freerdp_certificate_get_dns_names(cert, &dns_names_count, &dns_names_lengths);
-
- /* compare against common name */
-
- if (common_name)
- {
- if (tls_match_hostname(common_name, common_name_length, hostname))
- hostname_match = TRUE;
- }
-
/* compare against alternative names */
-
if (dns_names)
{
for (size_t index = 0; index < dns_names_count; index++)
@@
}
}
+ /* compare against common name */
+ else if (common_name)
+ {
+ if (tls_match_hostname(common_name, common_name_length, hostname))
+ hostname_match = TRUE;
+ }
The Common Name is now consulted only when the certificate has no DNS SAN entries at all, which is the RFC 6125 section 6.4.4 rule.
Commit 350539ec9a, "add IP SAN checks", adds an iPAddress extraction path. When the connection target parses as an IP, the loop matches it against the certificate's iPAddress SAN entries through a binary-aware comparison, and never falls back to DNS string matching.
The fix is complete for the two reported cases. The block that both commits rewrite holds the only calls to tls_match_hostname() in the project. The original advisory's third case, the embedded-NUL truncation, was already fixed in 3.29.0.
One defect remains in the new IP path, and it fails closed. In 3.32.2 and 3.33.0, the helper utils_compare_ip_strings() rejects every valid address because it tests the wrong inet_pton() return value. An IP-literal target therefore never matches an iPAddress SAN entry, and the client falls back to its known-hosts check or prompt. Commit f5e8a54fb2 corrected that check on November 3, 2026, after the 3.33.0 release.
How do I check if I was compromised?
No indicators are published, and the advisories report no exploitation. A successful interception looks like a normal TLS session to the client.
The client keeps no record of the attack. When the chain is trusted and the name matches, tls_verify_certificate() accepts the certificate without reading or updating FreeRDP's known-hosts store, so the attacker's certificate leaves no entry there. RDP connection logs on your servers are the better lead: a session from a source address you do not expect is worth checking, but it is not proof of interception.
Timeline
- 2026-07-15: GHSA-43hh-p3vw-hfx3 is published, documenting three identity-check weaknesses and stating 3.29.0 fixed them.
- 2026-10-07: The two fix commits for the residual cases are authored by Armin Novak.
- 2026-10-12: FreeRDP 3.32.2 is released with the fixes, as part of a broader hardening release.
- 2026-10-19: GHSA-jq25-3374-4262 is published, documenting that 2 of the 3 cases were never fixed in 3.29.0.
- 2026-11-03: CVE-2026-121392 is published by VulnCheck with the 9.1 Critical CVSS v4.0 score.
The advisory said 3.29.0 closed these cases. The fix commits came 84 days after that advisory, and the release that shipped them came 89 days after it.
FAQ
Why is the flaw rated both Critical and High?
The two ratings come from two CVSS versions applied to the same facts. VulnCheck, the CVE Numbering Authority (CNA) that published the record, scores it 9.1 Critical on CVSS v4.0, CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N. VulnCheck and FreeRDP's own advisory both score it 7.4 High on CVSS v3.1, CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N.
Does this affect the FreeRDP server or proxy?
The flaw is in the client's server-identity verification, in tls_verify_certificate(). The 3.32.2 release notes list 44 other advisories in the same batch, which harden the code against malicious client and server messages.
Was the original advisory wrong?
Its patched-versions field said 3.29.0. The follow-up showed the 3.29.0 fix set closed only the embedded-NUL case, and that the other two cases were byte-identical from that tag onward. The field was inaccurate for 2 of the 3 documented cases.
Who reported these flaws?
The original three-case advisory was reported by Bin Luo of the University of Electronic Science and Technology of China (UESTC), GitHub user 1121984919, per the advisory. The follow-up report documenting the incomplete fix does not name a reporter. It discloses that Claude was used as a tool under the reporter's direction, with the reporter re-verifying every cited commit and line before submission.
Does updating to 3.33.0 also fix it?
Yes. The fixes landed in 3.32.2, and 3.33.0, released November 2, 2026, carries them. The 3.32.2 release is the minimum.
Find the FreeRDP copies in your estate before an interceptor does
Every FreeRDP build before 3.32.2 is in the affected range, including copies bundled inside products. Ostorlab's SCA and SBOM scanning inventories the dependencies in your repositories and images, so you know which components and products carry an affected FreeRDP.
References
- CVE-2026-121392, VulnCheck CVE record with both CVSS vectors: https://www.cve.org/CVERecord?id=CVE-2026-121392 (checked November 4, 2026)
- VulnCheck advisory, "FreeRDP before 3.32.2 Certificate Identity Validation Bypass via tls_verify_certificate": https://www.vulncheck.com/advisories/freerdp-before-3.32.2-certificate-identity-validation-bypass-via-tls-verify-certificate (checked November 4, 2026)
- GHSA-jq25-3374-4262, FreeRDP advisory, "Incomplete fix for GHSA-43hh-p3vw-hfx3": https://github.com/FreeRDP/FreeRDP/security/advisories/GHSA-jq25-3374-4262 (checked November 4, 2026)
- GHSA-43hh-p3vw-hfx3, FreeRDP advisory, "TLS certificate identity validation accepts malformed and non-authoritative identities": https://github.com/FreeRDP/FreeRDP/security/advisories/GHSA-43hh-p3vw-hfx3 (checked November 4, 2026)
- Fix commit 1a23b21c37, "[crypto,tls] do not check common name if there are DNS SAN": https://github.com/FreeRDP/FreeRDP/commit/1a23b21c37 (checked November 4, 2026)
- Fix commit 350539ec9a, "[crypto,tls] add IP SAN checks": https://github.com/FreeRDP/FreeRDP/commit/350539ec9a (checked November 4, 2026)
- Follow-up commit f5e8a54fb2, "[core,utils] fix return check for inet_pton": https://github.com/FreeRDP/FreeRDP/commit/f5e8a54fb2 (checked November 4, 2026)
- FreeRDP 3.32.2 release notes: https://github.com/FreeRDP/FreeRDP/releases/tag/3.32.2 (checked November 4, 2026)
- Vulnerable source, libfreerdp/crypto/tls.c at the 3.32.1 tag: https://raw.githubusercontent.com/FreeRDP/FreeRDP/3.32.1/libfreerdp/crypto/tls.c (checked November 4, 2026)
- CISA Known Exploited Vulnerabilities catalog, JSON feed, searched for CVE-2026-121392: https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json (checked November 4, 2026)