# "Bad Protocols Validation" Bypass in "wp_kses_bad_protocol_once" using HTML-encoding without trailing semicolons

wp_kses_bad_protocol_once only splits on colon or HTML-encoded colon with trailing semicolon, so `javascript&#58alert()` without semicolon bypasses protocol filtering and yields XSS.

- Payout: hidden
- Severity: medium
- Program: WordPress
- Found by: irsdl
- Disclosed: 2019-11-16
- Write-up quality: 7/10
- Original: https://hackerone.com/reports/339483
- Library page: https://bugbountylibrary.com/r/h1%3A339483
- Tags: area:web domain:web impact:integrity kind:bounty platform:hackerone sev:medium tech:php technique:parser-differential technique:source-review vuln:xss

**Target:** WordPress Core wp_kses_bad_protocol_once sanitizer
**Root cause:** The regex splitting on `:|&#0*58;|&#x0*3a;` requires a trailing semicolon, but browsers accept HTML character references without semicolons, so the encoded colon is not recognized as a protocol delimiter and the bad protocol check is skipped.
**Steps:**
1. Craft attribute value using semicolon-less entity: `<a href="javascript&#58alert(document.domain)">`
2. Or hex variant: `<a href="javascript&#x3ax=1;alert(document.domain)">`
3. Pass through wp_kses_bad_protocol_once; preg_split fails to split on the entity, so no bad protocol detected
4. Browser decodes entity to `javascript:` and executes on click
**How found:** Reading the wp_kses_bad_protocol_once source, noticed the split regex only matched HTML-encoded colons with trailing semicolons; cross-referenced OWASP XSS filter evasion notes on semicolon-less numeric character references and tested the bypass.
**Exploitation:** Attacker submits content containing an anchor with `javascript&#58...` href that survives kses sanitization; victim clicks and script runs in site origin, enabling reflected or stored XSS in core or plugins relying solely on this function.
**Insight:** When auditing sanitizers that split on encoded delimiters, check whether the regex requires a trailing semicolon; browsers accept `&#NN` and `&#xNN` without it, so any parser that demands the semicolon can be bypassed with semicolon-less entities.

