Query PR
github/codeql#12991
Language
Python
CVE(s) ID list
CWE
CWE-176
Report
According to GoSecure's presentation [PDF] "Unicode vulnerabilities that could byte you", the recommendation states that "If you need to do normalization, normalized prior to a security validation". This is due to the fact that a Post-Unicode Normalization may cause serious security issues. Why ? A late Unicode normalization may introduce back an omitted or an escaped character.
When the Unicode normalization is applied to U+FF20 (@), the resulting character will be U+0040 (@). If ever a security check is performed against the latter U+0040 (@), before the normalization, that would bring it back using the other U+FF20 (@).
Breaking URL parser
Take for instance, the URL parser bases its splitting of the host part and user:password part on the identification of @ (U+0040).
The URL parser would deny-list this URL https://@evil.com
- host: evil.com
However, a malicious URL may include the Unicode character﹫ (U+FE6B) as https://@evil.com. No @ is found. The host is @evil.com different from evil.com which leads to the bypass of the deny-list.
Another vulnerability rXSS
Take for instance this useful Python Flask snippet:
import unicodedata
from flask import Flask, request, escape, render_template
app = Flask(__name__)
@app.route("/")
def escape_nd_normalize():
ui_escaped = escape(request.args.get('ui'))
norm_escaped_ui = unicodedata.normalize("NFKC", ui_escaped)
return render_template('result.html', norm_escaped_ui=norm_escaped_ui) # $result=BAD
with the result.html html template:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Results</title>
</head>
<body>
<h1>Results</h1>
<p>
{{ norm_escaped_ui | safe }}
</p>
</body>
</html>
Run the web server using:
FLASK_APP=snippet.py flask run --reload
Now, hit the server using the following payloads:
# 1. No XSS triggered
http://127.0.0.1:5000/?ui=%3Cimg%20src=x%20onerror=print()%3E
# 2. A Reflected XSS is triggered
http://127.0.0.1:5000/?ui=%EF%B9%A4img%20src=x%20onerror=print()%EF%B9%A5
Using the first payload, the flask.escape() function has successfully escaped the symbols < and > making the first payload benign. While the escape function is considered the symbol ﹤ (U+FE64) and ﹥ (U+FE65) as harmless. Thus, no escaping is being performed. But, when the late Unicode normalization with the algorithm NKFC is applied, it leads to the conversion of the symbol ﹤ (U+FE64) back into the regular <, resulting in an rXSS triggered.
Source-Sink flow representation
I modeled the identification of a Post-Unicode Normalization vulnerability the following way.
- The source is any remote user-controlled data.
- When the user-controlled data flows through either a string validation, regex verification, or an escaping function.
- The sink would be valid for a late post-Unicode normalization using one of the forms NKFC or NFC.

Pull Requests
I modeled the QL query for the following languages:
Impact
A post-Unicode normalization may lead to:
- Breaking the URL parser and Credentials and information leakage, for instance:
https://www.evil.c℀.ms.com would become https://www.evil.ca/c.ms.com.
- Bypass-escaping mechanisms lead to various vulnerabilities in Command injections, XSS, SQLi, etc.
- Account takeover due to character collision.
- Bypass deny lists.
References:
Are you planning to discuss this vulnerability submission publicly? (Blog Post, social networks, etc).
Blog post link
https://sim4n6.beehiiv.com
Query PR
github/codeql#12991
Language
Python
CVE(s) ID list
CWE
CWE-176
Report
According to GoSecure's presentation [PDF] "Unicode vulnerabilities that could byte you", the recommendation states that "If you need to do normalization, normalized prior to a security validation". This is due to the fact that a Post-Unicode Normalization may cause serious security issues. Why ? A late Unicode normalization may introduce back an omitted or an escaped character.
When the Unicode normalization is applied to U+FF20 (@), the resulting character will be U+0040 (@). If ever a security check is performed against the latter U+0040 (@), before the normalization, that would bring it back using the other U+FF20 (@).
Breaking URL parser
Take for instance, the URL parser bases its splitting of the host part and user:password part on the identification of @ (U+0040).
The URL parser would deny-list this URL https://@evil.com
However, a malicious URL may include the Unicode character﹫ (U+FE6B) as https://@evil.com. No @ is found. The host is @evil.com different from evil.com which leads to the bypass of the deny-list.
Another vulnerability rXSS
Take for instance this useful Python Flask snippet:
with the result.html html template:
Run the web server using:
Now, hit the server using the following payloads:
Using the first payload, the flask.escape() function has successfully escaped the symbols < and > making the first payload benign. While the escape function is considered the symbol ﹤ (U+FE64) and ﹥ (U+FE65) as harmless. Thus, no escaping is being performed. But, when the late Unicode normalization with the algorithm NKFC is applied, it leads to the conversion of the symbol ﹤ (U+FE64) back into the regular <, resulting in an rXSS triggered.
Source-Sink flow representation
I modeled the identification of a Post-Unicode Normalization vulnerability the following way.
Pull Requests
I modeled the QL query for the following languages:
Impact
A post-Unicode normalization may lead to:
https://www.evil.c℀.ms.com would become https://www.evil.ca/c.ms.com.
References:
Are you planning to discuss this vulnerability submission publicly? (Blog Post, social networks, etc).
Blog post link
https://sim4n6.beehiiv.com