You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
System.Web.HttpRequest.get_RawUrl (the C# property Request.RawUrl) is modeled as a barrierModel for url-redirection in csharp/ql/lib/ext/System.Web.model.yml (i.e. treated as a sanitizer). But RawUrl is not a URL-redirect sanitizer — it is the attacker-controlled, un-normalized HTTP request-line path (including the query string), which can be a protocol-relative URL such as //evil.com that browsers resolve to {current-scheme}://evil.com and navigate to an external site. So Response.Redirect(Request.RawUrl) is an open-redirect vulnerability, but the barrierModel suppresses the alert, causing CodeQL to miss it.
CodeQL's own test suite marks ctx.Response.Redirect(ctx.Request.RawUrl) as GOOD (non-vulnerable), with the comment "Redirecting to the RawUrl only reloads the current Url" — but that assumption is wrong, because RawUrl is the attacker-controlled request line, not a fixed current-page URL. I confirmed the false negative by removing the barrierModel row and re-running the test — the suppressed alert appears.
The model
csharp/ql/lib/ext/System.Web.model.yml (the barrierModel block, with CodeQL's own comment):
extensions:
- addsTo:
pack: codeql/csharp-allextensible: barrierModeldata:
# The RawUrl property is considered to be safe for URL redirects
- ["System.Web", "HttpRequest", False, "get_RawUrl", "()", "", "ReturnValue", "url-redirection", "manual"]
The barrierModel means CodeQL considers the return value of RawUrl to be safe from url-redirection (taint of kind url-redirection is stopped at the barrier).
Why Request.RawUrl is not a URL-redirect sanitizer
RawUrl is not a sanitizer — it is the attacker-controlled, un-normalized request-line path. The evidence chain is as follows:
(1) RawUrl is the attacker-controlled request-line path, un-normalized. Per the Microsoft docs, "The raw URL is defined as the part of the URL following the domain information" — the part of the request line after the domain (path + query string). The attacker fully controls the HTTP request line; sending GET //evil.com?x=1 HTTP/1.1 makes Request.RawUrl = //evil.com?x=1. The SimpleWorkerRequest.GetRawUrl() docs explicitly warn: "The returned URL is not normalized. Using the URL for access control or security-sensitive decisions can expose your application to canonicalization security vulnerabilities." In the ASP.NET reference source (HttpRequest.cs), RawUrl is taken directly from the worker request: _rawUrl = _wr.GetRawUrl(), with the comment "RAW Url (as supplied by worker request)" — supplied by the client, un-sanitized.
(2) //evil.com is a protocol-relative URL that browsers navigate to an external site. Per RFC 3986 §4.2, a URL beginning with // is a scheme-relative URL; the browser resolves it using the current page's scheme, so //evil.com is resolved as {current-scheme}://evil.com and navigates to evil.com. The OWASP Open Redirect Cheat Sheet lists "Protocol-relative URL //evil.com" as a standard open-redirect bypass technique.
(3) System.Web.HttpResponse.Redirect writes //evil.com to the Location header unchanged. This is the sink used by the test code ctx.Response.Redirect (System.Web, ASP.NET Framework, not Core). I traced the ASP.NET Framework reference source HttpResponse.csRedirect(string url, bool endResponse, bool permanent) method; it processes the url in four steps, and //evil.com passes through each unchanged:
ApplyAppPathModifier("//evil.com"): the source explicitly handles URLs beginning with // — if (!UrlPath.IsRooted(virtualPath) || virtualPath.StartsWith("//", ...)) { return virtualPath; }, with the comment "ignore paths with http://server/... or //". //evil.com begins with //, so it is returned unchanged.
ConvertToFullyQualifiedRedirectUrlIfRequired("//evil.com"): under the default configuration (UseFullyQualifiedRedirectUrl=false, the vast majority of apps) it does return url, unchanged. (Even with that config enabled, new Uri(Request.Url, "//evil.com").AbsoluteUri resolves //evil.com as a protocol-relative URL per RFC 3986, yielding http://evil.com, which still navigates externally.)
UrlEncodeRedirect("//evil.com"): TrySplitUriForPathEncode splits //evil.com into schemeAndAuthority="//evil.com" + path="", and UrlEncodeNonAscii only encodes non-ASCII characters; //evil.com is all-ASCII, so it is unchanged.
RedirectLocation = url → headers.Add(new HttpResponseHeader(HttpWorkerRequest.HeaderLocation, _redirectLocation)): //evil.com is written to the Location header unchanged.
Result: HTTP 302 Location: //evil.com, which the browser resolves per RFC 3986 §4.2 as {current-scheme}://evil.com and navigates to evil.com.
Supplementary: dotnet/aspnetcore#66486 (the same class of issue in ASP.NET Core) confirms that //host-style URLs bypass the scheme-delimiter check and flow into the Location header; that issue also notes that ASP.NET's own SharedUrlHelper.IsLocalUrlalready rejects URLs beginning with // or /\ — indicating ASP.NET itself considers //-prefixed URLs non-local and dangerous (IsLocalUrl treats //evil.com as non-local, but Response.Redirect performs no such check).
(4) CodeQL's own threat model also treats "attacker can control the location" as dangerous. In csharp/ql/lib/semmle/code/csharp/security/dataflow/UrlRedirectQuery.qll, the ConcatenationSanitizer (which marks "foo.asp?param=" + url as GOOD) has the comment: "the attacker can then only control the query string parameters, rather than the location itself. In the majority of cases, this will only allow the attacker to redirect the user to a link they could have already redirected them to." So CodeQL's model is: controlling the location = dangerous, controlling only the query = safe. RawUrl includes the path (i.e. the location), not just the query, so by CodeQL's own model RawUrl is dangerous.
In summary, Response.Redirect(Request.RawUrl) is a real open-redirect vulnerability when RawUrl = "//evil.com", and the barrierModel suppresses the alert.
// GOOD: Redirecting to the RawUrl only reloads the current Urlctx.Response.Redirect(ctx.Request.RawUrl);
ctx.Request.RawUrl is attacker-controlled (the request-line path, un-normalized)
ctx.Response.Redirect is a url-redirection sink
UrlRedirect.expected reports url-redirection alerts for UrlRedirect.cs lines 13, 23, 38, 39, 48, 64, 70, 76, but not line 42 (Redirect(Request.RawUrl))
The comment "Redirecting to the RawUrl only reloads the current Url" is incorrect — RawUrl is not a fixed current-page URL; it is the attacker-controlled request line and can be //evil.com.
Ablation confirmation: I removed the get_RawUrlbarrierModel block from csharp/ql/lib/ext/System.Web.model.yml and re-ran the test. The .actual output now includes a line-42 alert:
| UrlRedirect.cs:42:31:42:48 | access to property RawUrl | UrlRedirect.cs:42:31:42:48 | access to property RawUrl | UrlRedirect.cs:42:31:42:48 | access to property RawUrl | Untrusted URL redirection due to $@. | UrlRedirect.cs:42:31:42:48 | access to property RawUrl | user-provided value |
This confirms the barrierModel is suppressing a legitimate url-redirection alert. The taint flows Request.RawUrl (attacker-controlled request line) → Response.Redirect, and RawUrl can be //evil.com (a protocol-relative URL that navigates externally).
Re-adding the barrierModel block restores the baseline (line 42 is no longer flagged, test passes) — the alert appears and disappears exactly with the barrier, confirming it is the cause of the false negative.
Steps to reproduce
Run the UrlRedirect query on the existing test:
codeql test run "csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect/UrlRedirect.qlref"
Observe that line 42 (ctx.Response.Redirect(ctx.Request.RawUrl)) is not in the alerts (UrlRedirect.cs flags only lines 13, 23, 38, 39, 48, 64, 70, 76). The test passes (.actual matches .expected).
Remove the entire barrierModel block (the get_RawUrl row) from csharp/ql/lib/ext/System.Web.model.yml and re-run. The test now fails with an unexpected alert on line 42 (Unexpected result: Alert on access to property RawUrl, "Untrusted URL redirection").
Re-add the barrierModel block and re-run; the alert disappears and the test passes again (baseline restored).
Context
Tested with CodeQL CLI 2.25.6, repo HEAD a24d222d96 (codeql-cli/latest-577-ga24d222d96).
The barrierModel for get_RawUrl was added in commit 130f8f148b ("Convert barrier to MaD").
Every link in the attack chain has independent evidence: MS docs (RawUrl is un-normalized; not for security-sensitive decisions), RFC 3986 §4.2 (// is a protocol-relative URL), OWASP (protocol-relative URL is a standard open-redirect bypass), ASP.NET Framework HttpResponse.Redirect source verified function-by-function (ApplyAppPathModifier comment "ignore //", StartsWith("//") returns unchanged; all four steps pass //evil.com through unchanged into the Location header), URL Rewrite middleware does not detect scheme-relative URLs (//host) in redirect targets dotnet/aspnetcore#66486 (the Core equivalent + IsLocalUrl rejecting //), and CodeQL's own UrlRedirectQuery.qll threat model (controlling the location is dangerous).
Notes
The test comment "Redirecting to the RawUrl only reloads the current Url" reflects a common misconception: that RawUrl is always the current page's path. But RawUrl is the request line's path+query, and the attacker can send any request line (e.g. GET //evil.com HTTP/1.1), making RawUrl = //evil.com.
The safe approach is to validate with UrlHelper.IsLocalUrl() before redirecting (CodeQL's UrlRedirectQuery.qll already models IsLocalUrl as a sanitizer), rather than assuming RawUrl itself is safe.
Description of the issue
System.Web.HttpRequest.get_RawUrl(the C# propertyRequest.RawUrl) is modeled as abarrierModelforurl-redirectionincsharp/ql/lib/ext/System.Web.model.yml(i.e. treated as a sanitizer). ButRawUrlis not a URL-redirect sanitizer — it is the attacker-controlled, un-normalized HTTP request-line path (including the query string), which can be a protocol-relative URL such as//evil.comthat browsers resolve to{current-scheme}://evil.comand navigate to an external site. SoResponse.Redirect(Request.RawUrl)is an open-redirect vulnerability, but thebarrierModelsuppresses the alert, causing CodeQL to miss it.CodeQL's own test suite marks
ctx.Response.Redirect(ctx.Request.RawUrl)asGOOD(non-vulnerable), with the comment "Redirecting to the RawUrl only reloads the current Url" — but that assumption is wrong, becauseRawUrlis the attacker-controlled request line, not a fixed current-page URL. I confirmed the false negative by removing thebarrierModelrow and re-running the test — the suppressed alert appears.The model
csharp/ql/lib/ext/System.Web.model.yml(thebarrierModelblock, with CodeQL's own comment):The
barrierModelmeans CodeQL considers the return value ofRawUrlto be safe fromurl-redirection(taint of kindurl-redirectionis stopped at the barrier).Why
Request.RawUrlis not a URL-redirect sanitizerRawUrlis not a sanitizer — it is the attacker-controlled, un-normalized request-line path. The evidence chain is as follows:(1)
RawUrlis the attacker-controlled request-line path, un-normalized. Per the Microsoft docs, "The raw URL is defined as the part of the URL following the domain information" — the part of the request line after the domain (path + query string). The attacker fully controls the HTTP request line; sendingGET //evil.com?x=1 HTTP/1.1makesRequest.RawUrl=//evil.com?x=1. TheSimpleWorkerRequest.GetRawUrl()docs explicitly warn: "The returned URL is not normalized. Using the URL for access control or security-sensitive decisions can expose your application to canonicalization security vulnerabilities." In the ASP.NET reference source (HttpRequest.cs),RawUrlis taken directly from the worker request:_rawUrl = _wr.GetRawUrl(), with the comment "RAW Url (as supplied by worker request)" — supplied by the client, un-sanitized.(2)
//evil.comis a protocol-relative URL that browsers navigate to an external site. Per RFC 3986 §4.2, a URL beginning with//is a scheme-relative URL; the browser resolves it using the current page's scheme, so//evil.comis resolved as{current-scheme}://evil.comand navigates toevil.com. The OWASP Open Redirect Cheat Sheet lists "Protocol-relative URL//evil.com" as a standard open-redirect bypass technique.(3)
System.Web.HttpResponse.Redirectwrites//evil.comto the Location header unchanged. This is the sink used by the test codectx.Response.Redirect(System.Web, ASP.NET Framework, not Core). I traced the ASP.NET Framework reference sourceHttpResponse.csRedirect(string url, bool endResponse, bool permanent)method; it processes the url in four steps, and//evil.compasses through each unchanged:ApplyAppPathModifier("//evil.com"): the source explicitly handles URLs beginning with//—if (!UrlPath.IsRooted(virtualPath) || virtualPath.StartsWith("//", ...)) { return virtualPath; }, with the comment "ignore paths with http://server/... or //".//evil.combegins with//, so it is returned unchanged.ConvertToFullyQualifiedRedirectUrlIfRequired("//evil.com"): under the default configuration (UseFullyQualifiedRedirectUrl=false, the vast majority of apps) it doesreturn url, unchanged. (Even with that config enabled,new Uri(Request.Url, "//evil.com").AbsoluteUriresolves//evil.comas a protocol-relative URL per RFC 3986, yieldinghttp://evil.com, which still navigates externally.)UrlEncodeRedirect("//evil.com"):TrySplitUriForPathEncodesplits//evil.comintoschemeAndAuthority="//evil.com"+path="", andUrlEncodeNonAsciionly encodes non-ASCII characters;//evil.comis all-ASCII, so it is unchanged.RedirectLocation = url→headers.Add(new HttpResponseHeader(HttpWorkerRequest.HeaderLocation, _redirectLocation))://evil.comis written to the Location header unchanged.Result:
HTTP 302 Location: //evil.com, which the browser resolves per RFC 3986 §4.2 as{current-scheme}://evil.comand navigates toevil.com.Supplementary: dotnet/aspnetcore#66486 (the same class of issue in ASP.NET Core) confirms that
//host-style URLs bypass the scheme-delimiter check and flow into the Location header; that issue also notes that ASP.NET's ownSharedUrlHelper.IsLocalUrlalready rejects URLs beginning with//or/\— indicating ASP.NET itself considers//-prefixed URLs non-local and dangerous (IsLocalUrltreats//evil.comas non-local, butResponse.Redirectperforms no such check).(4) CodeQL's own threat model also treats "attacker can control the location" as dangerous. In
csharp/ql/lib/semmle/code/csharp/security/dataflow/UrlRedirectQuery.qll, theConcatenationSanitizer(which marks"foo.asp?param=" + urlas GOOD) has the comment: "the attacker can then only control the query string parameters, rather than the location itself. In the majority of cases, this will only allow the attacker to redirect the user to a link they could have already redirected them to." So CodeQL's model is: controlling the location = dangerous, controlling only the query = safe.RawUrlincludes the path (i.e. the location), not just the query, so by CodeQL's own modelRawUrlis dangerous.In summary,
Response.Redirect(Request.RawUrl)is a real open-redirect vulnerability whenRawUrl = "//evil.com", and thebarrierModelsuppresses the alert.Confirmed false negative (with ablation)
csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect/UrlRedirect.cslines 41-42:ctx.Request.RawUrlis attacker-controlled (the request-line path, un-normalized)ctx.Response.Redirectis a url-redirection sinkUrlRedirect.expectedreports url-redirection alerts forUrlRedirect.cslines 13, 23, 38, 39, 48, 64, 70, 76, but not line 42 (Redirect(Request.RawUrl))The comment "Redirecting to the RawUrl only reloads the current Url" is incorrect —
RawUrlis not a fixed current-page URL; it is the attacker-controlled request line and can be//evil.com.Ablation confirmation: I removed the
get_RawUrlbarrierModelblock fromcsharp/ql/lib/ext/System.Web.model.ymland re-ran the test. The.actualoutput now includes a line-42 alert:This confirms the
barrierModelis suppressing a legitimate url-redirection alert. The taint flowsRequest.RawUrl (attacker-controlled request line) → Response.Redirect, andRawUrlcan be//evil.com(a protocol-relative URL that navigates externally).Re-adding the
barrierModelblock restores the baseline (line 42 is no longer flagged, test passes) — the alert appears and disappears exactly with the barrier, confirming it is the cause of the false negative.Steps to reproduce
Run the
UrlRedirectquery on the existing test:Observe that line 42 (
ctx.Response.Redirect(ctx.Request.RawUrl)) is not in the alerts (UrlRedirect.csflags only lines 13, 23, 38, 39, 48, 64, 70, 76). The test passes (.actualmatches.expected).Remove the entire
barrierModelblock (theget_RawUrlrow) fromcsharp/ql/lib/ext/System.Web.model.ymland re-run. The test now fails with an unexpected alert on line 42 (Unexpected result: Alertonaccess to property RawUrl, "Untrusted URL redirection").Re-add the
barrierModelblock and re-run; the alert disappears and the test passes again (baseline restored).Context
a24d222d96(codeql-cli/latest-577-ga24d222d96).barrierModelforget_RawUrlwas added in commit130f8f148b("Convert barrier to MaD").RawUrlis un-normalized; not for security-sensitive decisions), RFC 3986 §4.2 (//is a protocol-relative URL), OWASP (protocol-relative URL is a standard open-redirect bypass), ASP.NET FrameworkHttpResponse.Redirectsource verified function-by-function (ApplyAppPathModifiercomment "ignore //",StartsWith("//")returns unchanged; all four steps pass//evil.comthrough unchanged into the Location header), URL Rewrite middleware does not detect scheme-relative URLs (//host) in redirect targets dotnet/aspnetcore#66486 (the Core equivalent +IsLocalUrlrejecting//), and CodeQL's ownUrlRedirectQuery.qllthreat model (controlling the location is dangerous).Notes
RawUrlis always the current page's path. ButRawUrlis the request line's path+query, and the attacker can send any request line (e.g.GET //evil.com HTTP/1.1), makingRawUrl = //evil.com.UrlHelper.IsLocalUrl()before redirecting (CodeQL'sUrlRedirectQuery.qllalready modelsIsLocalUrlas a sanitizer), rather than assumingRawUrlitself is safe.