+
+
+
+
+ A server can send the
+ "Access-Control-Allow-Credentials"
CORS header to control
+ when a browser may send user credentials in Cross-Origin HTTP
+ requests.
+
+
+
+
+ When the Access-Control-Allow-Credentials
header
+ is "true"
, the Access-Control-Allow-Origin
+ header must have a value different from "*"
in order to
+ make browsers accept the header. Therefore, to allow multiple origins
+ for Cross-Origin requests with credentials, the server must
+ dynamically compute the value of the
+ "Access-Control-Allow-Origin"
header. Computing this
+ header value from information in the request to the server can
+ therefore potentially allow an attacker to control the origins that
+ the browser sends credentials to.
+
+
+
+
+
+
+
+
+
+
+ When the Access-Control-Allow-Credentials
header
+ value is "true"
, a dynamic computation of the
+ Access-Control-Allow-Origin
header must involve
+ sanitization if it relies on user-controlled input.
+
+
+
+
+
+ Since the "null"
origin is easy to obtain for an
+ attacker, it is never safe to use "null"
as the value of
+ the Access-Control-Allow-Origin
header when the
+ Access-Control-Allow-Credentials
header value is
+ "true"
.
+
+
+
+
+
+
+
+ In the example below, the server allows the browser to send
+ user credentials in a Cross-Origin request. The request header
+ origins
controls the allowed origins for such a
+ Cross-Origin request.
+
+
+
+
+
+
+
+ This is not secure, since an attacker can choose the value of
+ the origin
request header to make the browser send
+ credentials to their own server. The use of a allowlist containing
+ allowed origins for the Cross-Origin request fixes the issue:
+
+
+
+
+
+
+
+ Mozilla Developer Network: CORS, Access-Control-Allow-Origin.
+ Mozilla Developer Network: CORS, Access-Control-Allow-Credentials.
+ PortSwigger: Exploiting CORS Misconfigurations for Bitcoins and Bounties
+ W3C: CORS for developers, Advice for Resource Owners
+
+
diff --git a/csharp/ql/src/experimental/CWE-942/CorsMisconfiguration.ql b/csharp/ql/src/experimental/CWE-942/CorsMisconfiguration.ql
new file mode 100644
index 000000000000..6e13f9fc03cc
--- /dev/null
+++ b/csharp/ql/src/experimental/CWE-942/CorsMisconfiguration.ql
@@ -0,0 +1,54 @@
+/**
+ * @name Credentialed CORS Misconfiguration
+ * @description Allowing any origin while allowing credentials may result in security issues as third party website may be able to
+ * access private resources.
+ * @kind problem
+ * @problem.severity error
+ * @security-severity 7.5
+ * @precision high
+ * @id cs/web/cors-misconfiguration
+ * @tags security
+ * external/cwe/cwe-942
+ */
+
+import csharp
+import CorsMisconfigurationLib
+
+/**
+ * Holds if the application allows an origin using "*" origin.
+ */
+private predicate allowAnyOrigin(MethodCall m) {
+ m.getTarget()
+ .hasFullyQualifiedName("Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicyBuilder",
+ "AllowAnyOrigin")
+}
+
+/**
+ * Holds if the application uses a vulnerable CORS policy.
+ */
+private predicate hasDangerousOrigins(MethodCall m) {
+ m.getTarget()
+ .hasFullyQualifiedName("Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicyBuilder",
+ "WithOrigins") and
+ exists(StringLiteral idStr |
+ idStr.getValue().toLowerCase().matches(["null", "*"]) and
+ TaintTracking::localExprTaint(idStr, m.getAnArgument())
+ )
+}
+
+from MethodCall add_policy, MethodCall child
+where
+ (
+ usedPolicy(add_policy) and
+ // Misconfigured origin affects used policy
+ getCallableFromExpr(add_policy.getArgument(1)).calls*(child.getTarget())
+ or
+ add_policy
+ .getTarget()
+ .hasFullyQualifiedName("Microsoft.AspNetCore.Cors.Infrastructure.CorsOptions",
+ "AddDefaultPolicy") and
+ // Misconfigured origin affects added default policy
+ getCallableFromExpr(add_policy.getArgument(0)).calls*(child.getTarget())
+ ) and
+ (hasDangerousOrigins(child) or allowAnyOrigin(child))
+select add_policy, "The following CORS policy may allow requests from 3rd party websites"
diff --git a/csharp/ql/src/experimental/CWE-942/CorsMisconfigurationCredentials.ql b/csharp/ql/src/experimental/CWE-942/CorsMisconfigurationCredentials.ql
new file mode 100644
index 000000000000..0252830a6f01
--- /dev/null
+++ b/csharp/ql/src/experimental/CWE-942/CorsMisconfigurationCredentials.ql
@@ -0,0 +1,42 @@
+/**
+ * @name Credentialed CORS Misconfiguration
+ * @description Allowing any origin while allowing credentials may result in security issues as third party website may be able to
+ * access private resources.
+ * @kind problem
+ * @problem.severity error
+ * @security-severity 7.5
+ * @precision high
+ * @id cs/web/cors-misconfiguration-credentials
+ * @tags security
+ * external/cwe/cwe-942
+ */
+
+import csharp
+import CorsMisconfigurationLib
+
+/** A call to `CorsPolicyBuilder.AllowCredentials`. */
+class AllowsCredentials extends MethodCall {
+ AllowsCredentials() {
+ this.getTarget()
+ .hasFullyQualifiedName("Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicyBuilder",
+ "AllowCredentials")
+ }
+}
+
+from MethodCall add_policy, MethodCall setIsOriginAllowed, AllowsCredentials allowsCredentials
+where
+ (
+ getCallableFromExpr(add_policy.getArgument(1)).calls*(setIsOriginAllowed.getTarget()) and
+ usedPolicy(add_policy) and
+ getCallableFromExpr(add_policy.getArgument(1)).calls*(allowsCredentials.getTarget())
+ or
+ add_policy
+ .getTarget()
+ .hasFullyQualifiedName("Microsoft.AspNetCore.Cors.Infrastructure.CorsOptions",
+ "AddDefaultPolicy") and
+ getCallableFromExpr(add_policy.getArgument(0)).calls*(setIsOriginAllowed.getTarget()) and
+ getCallableFromExpr(add_policy.getArgument(0)).calls*(allowsCredentials.getTarget())
+ ) and
+ setIsOriginAllowedReturnsTrue(setIsOriginAllowed)
+select add_policy,
+ "The following CORS policy may allow credentialed requests from 3rd party websites"
diff --git a/csharp/ql/src/experimental/CWE-942/CorsMisconfigurationLib.qll b/csharp/ql/src/experimental/CWE-942/CorsMisconfigurationLib.qll
new file mode 100644
index 000000000000..be3310476a88
--- /dev/null
+++ b/csharp/ql/src/experimental/CWE-942/CorsMisconfigurationLib.qll
@@ -0,0 +1,111 @@
+import csharp
+import DataFlow
+
+/**
+ * Gets the actual callable corresponding to the expression `e`.
+ */
+Callable getCallableFromExpr(Expr e) {
+ exists(Expr dcArg | dcArg = e.(DelegateCreation).getArgument() |
+ result = dcArg.(CallableAccess).getTarget() or
+ result = dcArg.(AnonymousFunctionExpr)
+ )
+ or
+ result = e
+}
+
+/**
+ * Holds if the `Callable` c throws any exception other than `ThrowsArgumentNullException`
+ */
+predicate callableMayThrowException(Callable c) {
+ exists(ThrowStmt thre | c = thre.getEnclosingCallable()) and
+ not callableOnlyThrowsArgumentNullException(c)
+}
+
+/**
+ * Holds if any exception being thrown by the callable is of type `System.ArgumentNullException`
+ * It will also hold if no exceptions are thrown by the callable
+ */
+predicate callableOnlyThrowsArgumentNullException(Callable c) {
+ forall(ThrowElement thre | c = thre.getEnclosingCallable() |
+ thre.getThrownExceptionType().hasFullyQualifiedName("System", "ArgumentNullException")
+ )
+}
+
+/**
+ * Hold if the `Expr` e is a `BoolLiteral` with value true,
+ * the expression has a predictable value == `true`,
+ * or if it is a `ConditionalExpr` where the `then` and `else` expressions meet `isExpressionAlwaysTrue` criteria
+ */
+predicate isExpressionAlwaysTrue(Expr e) {
+ e.(BoolLiteral).getBoolValue() = true
+ or
+ e.getValue() = "true"
+ or
+ e instanceof ConditionalExpr and
+ isExpressionAlwaysTrue(e.(ConditionalExpr).getThen()) and
+ isExpressionAlwaysTrue(e.(ConditionalExpr).getElse())
+ or
+ exists(Callable callable |
+ callableHasAReturnStmtAndAlwaysReturnsTrue(callable) and
+ callable.getACall() = e
+ )
+}
+
+/**
+ * Holds if the lambda expression `le` always returns true
+ */
+predicate lambdaExprReturnsOnlyLiteralTrue(AnonymousFunctionExpr le) {
+ isExpressionAlwaysTrue(le.getExpressionBody())
+}
+
+/**
+ * Holds if the callable has a return statement and it always returns true for all such statements
+ */
+predicate callableHasAReturnStmtAndAlwaysReturnsTrue(Callable c) {
+ c.getReturnType() instanceof BoolType and
+ not callableMayThrowException(c) and
+ forex(ReturnStmt rs | rs.getEnclosingCallable() = c |
+ rs.getNumberOfChildren() = 1 and
+ isExpressionAlwaysTrue(rs.getChildExpr(0))
+ )
+}
+
+/**
+ * Holds if `c` always returns `true`.
+ */
+private predicate alwaysReturnsTrue(Callable c) {
+ callableHasAReturnStmtAndAlwaysReturnsTrue(c)
+ or
+ lambdaExprReturnsOnlyLiteralTrue(c)
+}
+
+/**
+ * Holds if SetIsOriginAllowed always returns true. This sets the Access-Control-Allow-Origin to the requester
+ */
+predicate setIsOriginAllowedReturnsTrue(MethodCall mc) {
+ mc.getTarget()
+ .hasFullyQualifiedName("Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicyBuilder",
+ "SetIsOriginAllowed") and
+ alwaysReturnsTrue(mc.getArgument(0))
+}
+
+/**
+ * Holds if UseCors is called with the relevant cors policy
+ */
+predicate usedPolicy(MethodCall add_policy) {
+ exists(MethodCall uc |
+ uc.getTarget()
+ .hasFullyQualifiedName("Microsoft.AspNetCore.Builder.CorsMiddlewareExtensions", "UseCors") and
+ (
+ // Same hardcoded name
+ uc.getArgument(1).getValue() = add_policy.getArgument(0).getValue() or
+ // Same variable access
+ uc.getArgument(1).(VariableAccess).getTarget() =
+ add_policy.getArgument(0).(VariableAccess).getTarget() or
+ DataFlow::localExprFlow(add_policy.getArgument(0), uc.getArgument(1))
+ )
+ ) and
+ add_policy
+ .getTarget()
+ .hasFullyQualifiedName("Microsoft.AspNetCore.Cors.Infrastructure.CorsOptions", "AddPolicy")
+}
diff --git a/csharp/ql/src/experimental/CWE-942/examples/CorsBad.cs b/csharp/ql/src/experimental/CWE-942/examples/CorsBad.cs
new file mode 100644
index 000000000000..214dbcd5263e
--- /dev/null
+++ b/csharp/ql/src/experimental/CWE-942/examples/CorsBad.cs
@@ -0,0 +1,64 @@
+using Leaf.Middlewares;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace Leaf
+{
+ public class Startup
+ {
+ public Startup(IConfiguration configuration)
+ {
+ Configuration = configuration;
+ }
+
+ public IConfiguration Configuration { get; }
+
+ // This method gets called by the runtime. Use this method to add services to the container.
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddControllers();
+ //services.AddTransient