Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions csharp/ql/lib/semmle/code/csharp/frameworks/Microsoft.qll
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,16 @@ class MicrosoftNamespace extends Namespace {
this.hasName("Microsoft")
}
}

/** The `Microsoft.Net.Http.Headers.HeaderNames` class. */
class MicrosoftNetHttpHeadersHeaderNames extends Class {
MicrosoftNetHttpHeadersHeaderNames() {
this.hasFullyQualifiedName("Microsoft.Net.Http.Headers", "HeaderNames")
}

/** Gets the `XFrameOptions` field. */
Field getXFrameOptionsField() { result = this.getField("XFrameOptions") }

/** Gets the `ContentSecurityPolicy` field. */
Field getContentSecurityPolicyField() { result = this.getField("ContentSecurityPolicy") }
}
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,19 @@ class MicrosoftAspNetCoreHttpHeaderDictionaryExtensions extends RefType {
Method getSetCommaSeparatedValuesMethod() { result = this.getAMethod("SetCommaSeparatedValues") }
}

/** The `Microsoft.AspNetCore.Http.IHeaderDictionary` interface. */
class MicrosoftAspNetCoreHttpIHeaderDictionary extends RefType {
MicrosoftAspNetCoreHttpIHeaderDictionary() {
this.hasFullyQualifiedName("Microsoft.AspNetCore.Http", "IHeaderDictionary")
}

/** Gets the `XFrameOptions` property. */
Property getXFrameOptionsProperty() { result = this.getProperty("XFrameOptions") }

/** Gets the `ContentSecurityPolicy` property. */
Property getContentSecurityPolicyProperty() { result = this.getProperty("ContentSecurityPolicy") }
}

/** The `Microsoft.AspNetCore.Http.CookieOptions` class. */
class MicrosoftAspNetCoreHttpCookieOptions extends RefType {
MicrosoftAspNetCoreHttpCookieOptions() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/** Provides predicates for recognizing clickjacking-related response-header configuration. */

import csharp
import semmle.code.csharp.dataflow.DataFlow
import semmle.code.csharp.frameworks.microsoft.AspNetCore
import semmle.code.csharp.frameworks.system.Web

/** Holds if `name` is the `X-Frame-Options` header name, ignoring case. */
bindingset[name]
predicate isXFrameOptionsText(string name) { name.toLowerCase() = "x-frame-options" }

/** Holds if `name` is the enforced `Content-Security-Policy` header name, ignoring case. */
bindingset[name]
predicate isContentSecurityPolicyText(string name) {
name.toLowerCase() = "content-security-policy"
}

/**
* Holds if `value` contains a `frame-ancestors` directive at the start of a CSP policy or
* after a directive or policy separator.
*/
bindingset[value]
predicate containsFrameAncestorsDirective(string value) {
value.regexpMatch("(?is)(^|.*[;,])\\s*frame-ancestors(\\s|;|$).*")
}

private predicate isXFrameOptionsHeaderNameExpr(Expr name) {
isXFrameOptionsText(name.stripImplicit().getValue())
or
name.stripImplicit().(FieldAccess).getTarget() =
any(MicrosoftNetHttpHeadersHeaderNames f).getXFrameOptionsField()
}

private predicate isContentSecurityPolicyHeaderNameExpr(Expr name) {
isContentSecurityPolicyText(name.stripImplicit().getValue()) or
name.stripImplicit().(FieldAccess).getTarget() =
any(MicrosoftNetHttpHeadersHeaderNames f).getContentSecurityPolicyField()
}

private predicate containsFrameAncestorsDirectiveExpr(Expr value) {
containsFrameAncestorsDirective(value.stripImplicit().getValue())
}

private predicate isDirectResponseHeadersAccess(Expr expr) {
exists(PropertyAccess headers, MicrosoftAspNetCoreHttpHttpResponse response |
expr.stripImplicit() = headers and headers.getProperty() = response.getHeadersProperty()
)
}

private predicate isCallOnResponseHeadersAccess(Call call) {
exists(Expr qualifier |
call.(MethodCall).getQualifier() = qualifier or
call.(ExtensionMethodCall).getArgument(0) = qualifier or
call.(AccessorCall).getQualifier() = qualifier
|
exists(Expr directAccess |
isDirectResponseHeadersAccess(directAccess) and
DataFlow::localExprFlow(directAccess, qualifier.stripImplicit())
)
)
}

private predicate isClickjackingHeaderCall(MethodCall call) {
(
call.getTarget() = any(SystemWebHttpResponseClass r).getAppendHeaderMethod() or
call.getTarget() = any(SystemWebHttpResponseClass r).getAddHeaderMethod()
) and
(
isXFrameOptionsHeaderNameExpr(call.getArgumentForName("name"))
or
isContentSecurityPolicyHeaderNameExpr(call.getArgumentForName("name")) and
containsFrameAncestorsDirectiveExpr(call.getArgumentForName("value"))
)
}

private predicate isClickjackingHeaderDictionaryLikeWrite(Call call) {
(
call.getTarget().hasUndecoratedName(["Append", "Add", "TryAdd"])
or
call.(IndexerCall).getTarget() instanceof Setter
) and
(
isXFrameOptionsHeaderNameExpr(call.getArgumentForName("key"))
or
isContentSecurityPolicyHeaderNameExpr(call.getArgumentForName("key")) and
containsFrameAncestorsDirectiveExpr(call.getArgumentForName("value"))
)
}

private predicate isClickjackingPropertyWrite(Call c) {
c.getTarget() instanceof Setter and
(
c.(PropertyCall).getProperty() =
any(MicrosoftAspNetCoreHttpIHeaderDictionary dic).getXFrameOptionsProperty()
or
c.(PropertyCall).getProperty() =
any(MicrosoftAspNetCoreHttpIHeaderDictionary dic).getContentSecurityPolicyProperty() and
containsFrameAncestorsDirectiveExpr(c.getArgumentForName("value"))
)
}

/** Gets an expression that configures a clickjacking-related response header. */
Call getAClickjackingHeaderWrite() {
isClickjackingHeaderCall(result)
or
isCallOnResponseHeadersAccess(result) and
(
isClickjackingHeaderDictionaryLikeWrite(result)
or
isClickjackingPropertyWrite(result)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@

<overview>
<p>
Web sites that do not specify the <code>X-Frame-Options</code> HTTP header may be vulnerable to UI
redress attacks ("clickjacking"). In these attacks, the vulnerable site is loaded in a frame on
an attacker-controlled site which uses opaque or transparent layers to trick the user into
Web sites that do not restrict framing using the <code>X-Frame-Options</code> HTTP header or the
<code>frame-ancestors</code> Content Security Policy directive may be vulnerable to UI redress
attacks ("clickjacking"). In these attacks, the vulnerable site is loaded in a frame on an
attacker-controlled site which uses opaque or transparent layers to trick the user into
unintentionally clicking a button or link on the vulnerable site.
</p>

Expand All @@ -17,16 +18,24 @@ unintentionally clicking a button or link on the vulnerable site.
<p>
Set the <code>X-Frame-Options</code> HTTP header to <code>DENY</code>, to instruct web browsers to
block attempts to load the site in a frame. Alternatively, if framing is needed in certain
circumstances, specify <code>SAMEORIGIN</code> or <code>ALLOW FROM: ...</code> to limit the ability
to frame the site to pages from the same origin, or from an allowed whitelist of trusted domains.
circumstances, specify <code>SAMEORIGIN</code> to permit framing by the same origin. The
<code>frame-ancestors</code> directive in an enforced <code>Content-Security-Policy</code> header
provides a more flexible alternative. For example, use <code>frame-ancestors 'none'</code> to
prevent all framing, or use its source list to specify which origins may embed the application.
</p>
<p>
For ASP.NET web applications, the header may be specified either in the <code>Web.config</code>
file, using the <code>&lt;customHeaders&gt;</code> tag, or within the source code of the
application using the <code>HttpResponse.AddHeader</code> method. In general, prefer specifying the
header in the <code>Web.config</code> file to ensure it is added to all requests. If adding it
to the source code, ensure that it is added unconditionally to all requests. For example, add the
header in the <code>Application_BeginRequest</code> method in the <code>global.asax</code> file.
For ASP.NET Framework applications, the header may be specified either in the
<code>Web.config</code> file, using the <code>&lt;customHeaders&gt;</code> tag, or within the source
code of the application using the <code>HttpResponse.AddHeader</code> method. In general, prefer
specifying the header in the <code>Web.config</code> file to ensure it is added to all requests. If
adding it to the source code, ensure that it is added unconditionally to all requests. For example,
add the header in the <code>Application_BeginRequest</code> method in the
<code>global.asax</code> file.
</p>
<p>
For ASP.NET Core applications, set the header on <code>HttpResponse.Headers</code>. This can be
done using the header dictionary's indexer or its <code>Append</code>, <code>Add</code>, or
<code>TryAdd</code> methods.
</p>

</recommendation>
Expand All @@ -41,11 +50,17 @@ The following example shows how to specify the <code>X-Frame-Options</code> head

<p>
This next example shows how to specify the <code>X-Frame-Options</code> header within the
<code>global.asax</code> file for ASP.NET application:
<code>global.asax</code> file for an ASP.NET application:
</p>

<sample src="MissingXFrameOptions.cs" />

<p>
The following ASP.NET Core example uses an enforced Content Security Policy to disallow framing:
</p>

<sample src="MissingXFrameOptionsAspNetCore.cs" />

</example>
<references>

Expand All @@ -57,6 +72,10 @@ OWASP:
Mozilla:
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options">X-Frame-Options</a>
</li>
<li>
Mozilla:
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors">Content-Security-Policy: frame-ancestors</a>
</li>

</references>
</qhelp>
53 changes: 28 additions & 25 deletions csharp/ql/src/Security Features/CWE-451/MissingXFrameOptions.ql
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/**
* @name Missing X-Frame-Options HTTP header
* @description If the 'X-Frame-Options' setting is not provided, a malicious user may be able to
* overlay their own UI on top of the site by using an iframe.
* @name Missing clickjacking protection
* @description If neither the 'X-Frame-Options' header nor a Content Security Policy
* 'frame-ancestors' directive is provided, a malicious user may be able to overlay
* their own UI on top of the site by using an iframe.
* @kind problem
* @problem.severity error
* @security-severity 7.5
Expand All @@ -14,7 +15,7 @@

import csharp
import semmle.code.asp.WebConfig
import semmle.code.csharp.frameworks.system.Web
import semmle.code.csharp.security.MissingXFrameOptionsQuery

XmlElement getAWebConfigRoot(WebConfigXml webConfig) {
result = webConfig.getARootElement()
Expand All @@ -28,9 +29,10 @@ XmlElement getAWebConfigRoot(WebConfigXml webConfig) {
}

/**
* Holds if the `Web.config` file `webConfig` adds an `X-Frame-Options` header.
* Holds if the `Web.config` file `webConfig` adds an `X-Frame-Options` header or a
* `Content-Security-Policy` header containing a `frame-ancestors` directive.
*/
predicate hasWebConfigXFrameOptions(WebConfigXml webConfig) {
predicate hasWebConfigClickjackingProtection(WebConfigXml webConfig) {
// Looking for an entry in `webConfig` that looks like this:
// ```xml
// <system.webServer>
Expand All @@ -42,29 +44,30 @@ predicate hasWebConfigXFrameOptions(WebConfigXml webConfig) {
// </system.webServer>
// ```
// This can also be in a `location`
getAWebConfigRoot(webConfig)
.getAChild("system.webServer")
.getAChild("httpProtocol")
.getAChild("customHeaders")
.getAChild("add")
.getAttributeValue("name") = "X-Frame-Options"
exists(XmlElement add, string name |
add =
getAWebConfigRoot(webConfig)
.getAChild("system.webServer")
.getAChild("httpProtocol")
.getAChild("customHeaders")
.getAChild("add") and
name = add.getAttributeValue("name") and
(
isXFrameOptionsText(name)
or
isContentSecurityPolicyText(name) and
containsFrameAncestorsDirective(add.getAttributeValue("value"))
)
)
}

/**
* Holds if there exists a call to `AddHeader` or `AppendHeader` adding the `X-Frame-Options`
* header.
* Holds if code configures a clickjacking protection response header.
*/
predicate hasCodeXFrameOptions() {
exists(MethodCall call |
call.getTarget() = any(SystemWebHttpResponseClass r).getAppendHeaderMethod() or
call.getTarget() = any(SystemWebHttpResponseClass r).getAddHeaderMethod()
|
call.getArgumentForName("name").getValue() = "X-Frame-Options"
)
}
predicate hasCodeClickjackingProtection() { exists(getAClickjackingHeaderWrite()) }

from WebConfigXml webConfig
where
not hasWebConfigXFrameOptions(webConfig) and
not hasCodeXFrameOptions()
select webConfig, "Configuration file is missing the X-Frame-Options setting."
not hasWebConfigClickjackingProtection(webConfig) and
not hasCodeClickjackingProtection()
select webConfig, "Configuration file is missing clickjacking protection."
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
context.Response.Headers["Content-Security-Policy"] = "frame-ancestors 'none'";
await next();
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
category: minorAnalysis
---
* The `cs/web/missing-x-frame-options` query now recognizes clickjacking protection configured
through ASP.NET Core response headers and enforced Content Security Policy `frame-ancestors`
directives.
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
query: Security Features/CWE-451/MissingXFrameOptions.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql
Security Features/CWE-451/MissingXFrameOptions.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.Net.Http.Headers;
using AspNetCoreHttpContext = Microsoft.AspNetCore.Http.HttpContext;

public class HeaderWrites
{
public void AspNetCoreResponseHeaders(AspNetCoreHttpContext context)
{
context.Response.Headers.Append(HeaderNames.XFrameOptions, "DENY"); // $ Alert
context.Response.Headers.Add("x-frame-options", "SAMEORIGIN"); // $ Alert
context.Response.Headers.TryAdd(
HeaderNames.ContentSecurityPolicy,
"default-src 'self'; FrAmE-AnCeStOrS 'none'"); // $ Alert

context.Response.Headers["X-Frame-Options"] = "DENY"; // $ Alert
context.Response.Headers["Content-Security-Policy"] = // $ Alert
"default-src 'self'; frame-ancestors 'none'";
context.Response.Headers.XFrameOptions = "DENY"; // $ Alert
context.Response.Headers.ContentSecurityPolicy = // $ Alert
"default-src 'self'; frame-ancestors 'self'";
context.Response.Headers["Content-Security-Policy"] = // $ Alert
"default-src 'self', frame-ancestors 'none'";

IHeaderDictionary responseHeaders = context.Response.Headers;
responseHeaders.Append("X-Frame-Options", "DENY"); // $ Alert
}

public void IgnoredHeaderWrites(AspNetCoreHttpContext context)
{
context.Request.Headers["X-Frame-Options"] = "DENY";

IHeaderDictionary reassignedHeaders = context.Response.Headers;
reassignedHeaders = context.Request.Headers;
reassignedHeaders["X-Frame-Options"] = "DENY";

var standaloneHeaders = new HeaderDictionary();
standaloneHeaders.Append("X-Frame-Options", "DENY");
standaloneHeaders["Content-Security-Policy"] = "frame-ancestors 'none'";

context.Response.Headers["Content-Security-Policy-Report-Only"] =
"frame-ancestors 'none'";
context.Response.Headers.ContentSecurityPolicyReportOnly = "frame-ancestors 'none'";
context.Response.Headers["X-Content-Security-Policy"] = "frame-ancestors 'none'";
context.Response.Headers["Content-Security-Policy"] = "default-src 'self'";
context.Response.Headers["Content-Security-Policy"] =
"report-uri https://example.test/frame-ancestors";
context.Response.Headers["Content-Security-Policy"] =
"default-src 'self'; not-frame-ancestors 'none'";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
| HeaderWrites.cs:10:9:10:74 | call to method Append | A clickjacking-related response header is configured here. |
| HeaderWrites.cs:11:9:11:69 | call to method Add | A clickjacking-related response header is configured here. |
| HeaderWrites.cs:12:9:14:57 | call to method TryAdd<String,StringValues> | A clickjacking-related response header is configured here. |
| HeaderWrites.cs:16:9:16:51 | access to indexer | A clickjacking-related response header is configured here. |
| HeaderWrites.cs:17:9:17:59 | access to indexer | A clickjacking-related response header is configured here. |
| HeaderWrites.cs:19:9:19:46 | access to property XFrameOptions | A clickjacking-related response header is configured here. |
| HeaderWrites.cs:20:9:20:54 | access to property ContentSecurityPolicy | A clickjacking-related response header is configured here. |
| HeaderWrites.cs:22:9:22:59 | access to indexer | A clickjacking-related response header is configured here. |
| HeaderWrites.cs:26:9:26:57 | call to method Append | A clickjacking-related response header is configured here. |
Loading
Loading