Skip to content
Open
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
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 Security_Features.MissingXFrameOptionsLib

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
(
isXFrameOptionsHeaderName(name)
or
isContentSecurityPolicyHeaderName(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();
});
}
118 changes: 118 additions & 0 deletions csharp/ql/src/Security Features/MissingXFrameOptionsLib.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/** 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 isXFrameOptionsHeaderName(string name) { name.toLowerCase() = "x-frame-options" }

/** Holds if `name` is the enforced `Content-Security-Policy` header name, ignoring case. */
bindingset[name]
predicate isContentSecurityPolicyHeaderName(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 isHeaderNamesField(Expr name, string fieldName) {
exists(FieldAccess access |
name.stripImplicit() = access and
access.getTarget().hasFullyQualifiedName("Microsoft.Net.Http.Headers", "HeaderNames", fieldName)
)
}

private predicate isXFrameOptionsHeaderNameExpr(Expr name) {
isXFrameOptionsHeaderName(name.stripImplicit().getValue())
or
isHeaderNamesField(name, "XFrameOptions")
}

private predicate isContentSecurityPolicyHeaderNameExpr(Expr name) {
isContentSecurityPolicyHeaderName(name.stripImplicit().getValue())
or
isHeaderNamesField(name, "ContentSecurityPolicy")
}

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

private predicate isClickjackingHeader(Expr name, Expr value) {
isXFrameOptionsHeaderNameExpr(name)
or
isContentSecurityPolicyHeaderNameExpr(name) and containsFrameAncestorsDirectiveExpr(value)
}

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

private predicate isResponseHeadersAccess(Expr expr) {
exists(Expr directAccess |
isDirectResponseHeadersAccess(directAccess) and
DataFlow::localExprFlow(directAccess, expr.stripImplicit())
)
}

private Expr getHeaderDictionaryReceiver(MethodCall call) {
result = call.getQualifier()
or
call.getTarget().isExtensionMethod() and
result = call.getArgumentForParameter(call.getTarget().getParameter(0))
}

private predicate isClickjackingHeaderCall(MethodCall call) {
(
call.getTarget() = any(SystemWebHttpResponseClass r).getAppendHeaderMethod() or
call.getTarget() = any(SystemWebHttpResponseClass r).getAddHeaderMethod()
) and
isClickjackingHeader(call.getArgumentForName("name"), call.getArgumentForName("value"))
or
call.getTarget().hasUndecoratedName(["Append", "Add", "TryAdd"]) and
isResponseHeadersAccess(getHeaderDictionaryReceiver(call)) and
isClickjackingHeader(call.getArgumentForName("key"), call.getArgumentForName("value"))
}

private predicate isClickjackingHeaderIndexerAssignment(AssignExpr assignment) {
exists(IndexerCall indexer |
assignment.getLeftOperand() = indexer and
isResponseHeadersAccess(indexer.getQualifier()) and
isClickjackingHeader(indexer.getArgument(0), assignment.getRightOperand())
)
}

private predicate isClickjackingNamedHeaderPropertyAssignment(AssignExpr assignment) {
exists(PropertyAccessExpr header |
assignment.getLeftOperand() = header and
isResponseHeadersAccess(header.(QualifiableExpr).getQualifier()) and
(
header.getProperty().hasName("XFrameOptions")
or
header.getProperty().hasName("ContentSecurityPolicy") and
containsFrameAncestorsDirectiveExpr(assignment.getRightOperand())
)
)
}

/** Gets an expression that configures a clickjacking-related response header. */
Expr getAClickjackingHeaderWrite() {
result = any(MethodCall call | isClickjackingHeaderCall(call))
or
result =
any(AssignExpr assignment |
isClickjackingHeaderIndexerAssignment(assignment) or
isClickjackingNamedHeaderPropertyAssignment(assignment)
)
}
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,52 @@
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"] =
"default-src 'self'; frame-ancestors 'none'"; // $ Alert

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

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:60 | ... = ... | A clickjacking-related response header is configured here. |
| HeaderWrites.cs:17:9:18:56 | ... = ... | A clickjacking-related response header is configured here. |
| HeaderWrites.cs:20:9:20:55 | ... = ... | A clickjacking-related response header is configured here. |
| HeaderWrites.cs:21:9:22:56 | ... = ... | A clickjacking-related response header is configured here. |
| HeaderWrites.cs:23:9:24:56 | ... = ... | A clickjacking-related response header is configured here. |
| HeaderWrites.cs:27:9:27:57 | call to method Append | A clickjacking-related response header is configured here. |
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* @kind problem
* @id cs/test/clickjacking-header-write
* @problem.severity warning
*/

import csharp
import Security_Features.MissingXFrameOptionsLib

from Expr write
where write = getAClickjackingHeaderWrite()
select write, "A clickjacking-related response header is configured here."
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
query: HeaderWrites.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Security Features/CWE-451/MissingXFrameOptions.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration />
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
semmle-extractor-options: /nostdlib /noconfig
semmle-extractor-options: --load-sources-from-project:${testdir}/../../../../../resources/stubs/_frameworks/Microsoft.NETCore.App/Microsoft.NETCore.App.csproj
semmle-extractor-options: --load-sources-from-project:${testdir}/../../../../../resources/stubs/_frameworks/Microsoft.AspNetCore.App/Microsoft.AspNetCore.App.csproj
Original file line number Diff line number Diff line change
@@ -1 +1 @@
| Web.config:0:0:0:0 | Web.config | Configuration file is missing the X-Frame-Options setting. |
| Web.config:0:0:0:0 | Web.config | Configuration file is missing clickjacking protection. |
Loading