CVE-2021-26539
Description
Apostrophe Technologies sanitize-html before 2.3.1 does not properly handle internationalized domain name (IDN) which could allow an attacker to bypass hostname whitelist validation set by the "allowedIframeHostnames" option.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
sanitize-html prior to 2.3.1 fails to properly validate internationalized domain names, allowing attackers to bypass hostname whitelist checks in iframe src attributes.
Overview
[CVE-2021-26539] describes a vulnerability in the sanitize-html library (versions before 2.3.1) that allows an attacker to bypass the allowedIframeHostnames whitelist by using internationalized domain names (IDNs). The root cause is that the library used a deprecated URL parser (url.parse) which did not correctly handle IDN encoding, making it possible to craft iframe src values that appear to have a host not in the whitelist but are actually decoded to a different, attacker-controlled domain [1][3].
Exploitation
An attacker can supply a crafted iframe src URL containing an IDN (e.g., using Unicode characters that resemble ASCII). Because the parser did not normalize IDNs, the hostname extracted from the URL could be a whitelisted domain while the actual destination (after browser normalization) is a malicious site. This requires the application to allow iframe tags and to have configured allowedIframeHostnames. No authentication is needed beyond the ability to submit HTML content to the sanitizer [2][4].
Impact
Successful exploitation enables an attacker to inject arbitrary iframes pointing to attacker-controlled hosts, effectively bypassing the hostname whitelist protection. This could lead to UI redressing, credential harvesting, or distribution of malicious content within the context of the trusted application, amplifying the impact of other attacks like clickjacking [2][3].
Mitigation
The vulnerability is patched in sanitize-html version 2.3.1, which replaces the deprecated url.parse with the WHATWG URL parser that correctly normalizes IDNs [3][4]. Users should update to version 2.3.1 or later. There is no mention of a workaround for earlier versions. The CVE is not listed in the CISA Known Exploited Vulnerabilities catalog as of this writing.
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
sanitize-htmlnpm | < 2.3.1 | 2.3.1 |
Affected products
2- Apostrophe Technologies/sanitize-htmldescription
Patches
1bdf7836ef8f0Merge pull request #458 from apostrophecms/stop-idna-iframe-attacks
3 files changed · +79 −6
CHANGELOG.md+3 −0 modified@@ -1,5 +1,8 @@ # Changelog +## 2.3.1 (2021-01-22): +- Uses the standard WHATWG URL parser to stop IDNA (Internationalized Domain Name) attacks on the iframe hostname validator. Thanks to Ron Masas of Checkmarx for pointing out the issue and suggesting the use of the WHATWG parser. + ## 2.3.0 (2020-12-16): - Upgrades `htmlparser2` to new major version `^6.0.0`. Thanks to [Bogdan Chadkin](https://github.com/TrySound) for the contribution.
index.js+17 −6 modified@@ -5,7 +5,6 @@ const { isPlainObject } = require('is-plain-object'); const deepmerge = require('deepmerge'); const parseSrcset = require('parse-srcset'); const { parse: postcssParse } = require('postcss'); -const url = require('url'); // Tags that can conceivably represent stand-alone media. const mediaTags = [ 'img', 'audio', 'video', 'picture', 'svg', @@ -305,12 +304,24 @@ function sanitizeHtml(html, options, _recursing) { if (name === 'iframe' && a === 'src') { let allowed = true; try { + if (value.startsWith('relative:')) { + // An attempt to exploit our workaround for base URLs being + // mandatory for relative URL validation in the WHATWG + // URL parser, reject it + throw new Error('relative: exploit attempt'); + } // naughtyHref is in charge of whether protocol relative URLs - // are cool. We should just accept them - // TODO: Replace deprecated `url.parse` - // eslint-disable-next-line node/no-deprecated-api - parsed = url.parse(value, false, true); - const isRelativeUrl = parsed && parsed.host === null && parsed.protocol === null; + // are cool. Here we are concerned just with allowed hostnames and + // whether to allow relative URLs. + // + // Build a placeholder "base URL" against which any reasonable + // relative URL may be parsed successfully + let base = 'relative://relative-site'; + for (let i = 0; (i < 100); i++) { + base += `/${i}`; + } + const parsed = new URL(value, base); + const isRelativeUrl = parsed && parsed.hostname === 'relative-site' && parsed.protocol === 'relative:'; if (isRelativeUrl) { // default value of allowIframeRelativeUrls is true // unless allowedIframeHostnames or allowedIframeDomains specified
test/test.js+59 −0 modified@@ -1210,4 +1210,63 @@ describe('sanitizeHtml', function() { '<div><div><div><div><div><div>nested text</div></div></div></div></div></div>' ); }); + it('should not allow simple append attacks on iframe hostname validation', function() { + assert.equal( + sanitizeHtml('<iframe src=//www.youtube.comissocool>', { + allowedTags: [ 'iframe' ], + allowedAttributes: { + iframe: [ 'src' ] + }, + allowedIframeHostnames: [ 'www.youtube.com' ] + }), + '<iframe></iframe>' + ); + }); + it('should not allow IDNA (Internationalized Domain Name) iframe validation bypass attacks', function() { + assert.equal( + sanitizeHtml('<iframe src=//www.youtube.com%C3%9E.93.184.216.34.nip.io>', { + allowedTags: [ 'iframe' ], + allowedAttributes: { + iframe: [ 'src' ] + }, + allowedIframeHostnames: [ 'www.youtube.com' ] + }), + '<iframe></iframe>' + ); + }); + it('should parse path-rooted relative URLs sensibly', function() { + assert.equal( + sanitizeHtml('<a href="/foo"></a>'), + '<a href="/foo"></a>' + ); + }); + it('should parse bare relative URLs sensibly', function() { + assert.equal( + sanitizeHtml('<a href="foo"></a>'), + '<a href="foo"></a>' + ); + }); + it('should parse ../ relative URLs sensibly', function() { + assert.equal( + sanitizeHtml('<a href="../../foo"></a>'), + '<a href="../../foo"></a>' + ); + }); + it('should parse protocol relative URLs sensibly', function() { + assert.equal( + sanitizeHtml('<a href="//foo.com/foo"></a>'), + '<a href="//foo.com/foo"></a>' + ); + }); + it('should reject attempts to hack our use of a relative: protocol in our test base URL', function() { + assert.equal( + sanitizeHtml('<iframe src="relative://relative-test/aha">', { + allowedTags: [ 'iframe' ], + allowedAttributes: { + iframe: [ 'src' ] + } + }), + '<iframe></iframe>' + ); + }); });
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-rjqq-98f6-6j3rghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2021-26539ghsaADVISORY
- advisory.checkmarx.net/advisory/CX-2021-4308ghsax_refsource_MISCWEB
- github.com/apostrophecms/sanitize-html/blob/main/CHANGELOG.mdghsax_refsource_MISCWEB
- github.com/apostrophecms/sanitize-html/commit/bdf7836ef8f0e5b21f9a1aab0623ae8fcd09c1daghsaWEB
- github.com/apostrophecms/sanitize-html/pull/458ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.