Skip to content

Commit ff7edbf

Browse files
authored
Merge pull request #22431 from michaelnebel/csharp/csrf
C#: The `cs/web/missing-token-validation` query now recognizes an ASP.NET Core `AutoValidateAntiforgeryTokenAttribute`.
2 parents e452286 + 0cdad21 commit ff7edbf

7 files changed

Lines changed: 117 additions & 24 deletions

File tree

csharp/ql/lib/semmle/code/csharp/frameworks/microsoft/AspNetCore.qll

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,15 @@ class ValidateAntiForgeryAttribute extends Attribute {
144144
}
145145
}
146146

147+
/**
148+
* The `Microsoft.AspNetCore.Mvc.AutoValidateAntiforgeryTokenAttribute` class.
149+
*/
150+
class AutoValidateAntiforgeryTokenAttribute extends Class {
151+
AutoValidateAntiforgeryTokenAttribute() {
152+
this.hasFullyQualifiedName("Microsoft.AspNetCore.Mvc", "AutoValidateAntiforgeryTokenAttribute")
153+
}
154+
}
155+
147156
/**
148157
* A class that has a name like `[Auto...]Validate[...]Anti[Ff]orgery[...Token]` and implements `IFilterMetadata` interface
149158
* This class can be added to a collection of global `MvcOptions.Filters` collection.
@@ -164,8 +173,8 @@ class MicrosoftAspNetCoreMvcFilterCollection extends Class {
164173

165174
/** Gets an `Add` method. */
166175
Method getAddMethod() {
167-
result = this.getAMethod("Add") or
168-
result = this.getABaseType().getAMethod("Add")
176+
result = this.getAMethod(["Add", "Add`1"]) or
177+
result = this.getABaseType().getAMethod(["Add", "Add`1"])
169178
}
170179
}
171180

@@ -230,11 +239,20 @@ private Assembly getAnAssemblyFor(Type type) {
230239
result = getACompilationFor(type).getOutputAssembly()
231240
}
232241

233-
private predicate isMicrosoftAspNetCoreMvcRegistration(MethodCall call) {
234-
call.getTarget()
235-
.hasFullyQualifiedName("Microsoft.Extensions.DependencyInjection",
236-
["MvcServiceCollectionExtensions", "MvcCoreServiceCollectionExtensions"],
237-
["AddControllers", "AddControllersWithViews", "AddMvc", "AddMvcCore"])
242+
/**
243+
* A method that is a registration of an ASP.NET Core MVC service, i.e. `AddControllers`, `AddControllersWithViews`, `AddMvc`, or `AddMvcCore`.
244+
*/
245+
class MicrosoftAspNetCoreMvcRegistration extends Method {
246+
MicrosoftAspNetCoreMvcRegistration() {
247+
this.hasFullyQualifiedName("Microsoft.Extensions.DependencyInjection",
248+
["MvcServiceCollectionExtensions", "MvcCoreServiceCollectionExtensions"],
249+
["AddControllers", "AddControllersWithViews", "AddMvc", "AddMvcCore"])
250+
}
251+
}
252+
253+
/** Holds if the method call is a registration of an ASP.NET Core MVC service. */
254+
predicate isMicrosoftAspNetCoreMvcRegistration(MethodCall call) {
255+
call.getTarget() instanceof MicrosoftAspNetCoreMvcRegistration
238256
}
239257

240258
private predicate isMicrosoftAspNetCoreMvcApplication(Compilation compilation) {

csharp/ql/src/Security Features/CWE-352/MissingAntiForgeryTokenValidation.ql

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
*/
1313

1414
import csharp
15+
import semmle.code.csharp.commons.Compilation
1516
import semmle.code.csharp.frameworks.system.Web
1617
import semmle.code.csharp.frameworks.system.web.Helpers
1718
import semmle.code.csharp.frameworks.system.web.Mvc
@@ -34,20 +35,41 @@ private Method getAStartedMethod() {
3435
getAStartedMethod().calls(result)
3536
}
3637

37-
/**
38-
* Holds if the project has a global anti forgery filter.
39-
*
40-
* No AspNetCore case here as the corresponding class doesn't seem to exist.
41-
*/
42-
predicate hasGlobalAntiForgeryFilter() {
43-
// A global filter added
38+
private predicate hasGlobalWebMvcAntiforgeryFilter(Compilation compilation) {
4439
exists(MethodCall addGlobalFilter |
4540
// addGlobalFilter adds a filter to the global filter collection
4641
addGlobalFilter.getTarget() = any(GlobalFilterCollection gfc).getAddMethod() and
4742
// The filter is an antiforgery filter
4843
addGlobalFilter.getArgumentForName("filter").getType() instanceof AntiForgeryAuthorizationFilter and
4944
// The filter is added by the Application_Start() method
50-
getAStartedMethod() = addGlobalFilter.getEnclosingCallable()
45+
getAStartedMethod() = addGlobalFilter.getEnclosingCallable() and
46+
addGlobalFilter.getFile() = compilation.getAFileCompiled()
47+
)
48+
}
49+
50+
predicate hasGlobalAspNetMvcAntiForgeryFilter(Compilation compilation) {
51+
exists(MethodCall addGlobalFilter, MethodCall registrationCall |
52+
(
53+
// The filter is the `AutoValidateAntiforgeryTokenAttribute` filter.
54+
addGlobalFilter.getTarget() =
55+
any(AspNetCore::MicrosoftAspNetCoreMvcFilterCollection collection).getAddMethod() and
56+
(
57+
addGlobalFilter.getArgument(0).getType() instanceof
58+
AspNetCore::AutoValidateAntiforgeryTokenAttribute or
59+
addGlobalFilter.getArgument(0).(TypeofExpr).getTypeAccess().getTarget() instanceof
60+
AspNetCore::AutoValidateAntiforgeryTokenAttribute
61+
)
62+
or
63+
addGlobalFilter.getTarget().getUnboundDeclaration() =
64+
any(AspNetCore::MicrosoftAspNetCoreMvcFilterCollection collection).getAddMethod() and
65+
addGlobalFilter.getTarget().(ConstructedGeneric).getTypeArgument(0) instanceof
66+
AspNetCore::AutoValidateAntiforgeryTokenAttribute
67+
) and
68+
// The filter is added in an ASP.NET Core registration call, which is provided as a lambda argument
69+
// to the Mvc registration method.
70+
registrationCall.getTarget() instanceof AspNetCore::MicrosoftAspNetCoreMvcRegistration and
71+
registrationCall.getAnArgument() = addGlobalFilter.getEnclosingCallable() and
72+
addGlobalFilter.getFile() = compilation.getAFileCompiled()
5173
)
5274
}
5375

@@ -67,11 +89,12 @@ private class RequireAntiforgeryTokenAttribute extends Attribute {
6789
}
6890
}
6991

70-
private predicate hasAspNetCoreAntiForgeryMiddleware() {
92+
private predicate hasAspNetCoreAntiForgeryMiddleware(Compilation compilation) {
7193
exists(MethodCall call |
7294
call.getTarget()
7395
.hasFullyQualifiedName("Microsoft.AspNetCore.Builder",
74-
"AntiforgeryApplicationBuilderExtensions", "UseAntiforgery")
96+
"AntiforgeryApplicationBuilderExtensions", "UseAntiforgery") and
97+
call.getFile() = compilation.getAFileCompiled()
7598
)
7699
}
77100

@@ -106,7 +129,12 @@ private RequireAntiforgeryTokenAttribute getEffectiveRequireAntiforgeryTokenAttr
106129
class MvcControllerPostMethod extends Method {
107130
private Controller controller;
108131

109-
MvcControllerPostMethod() { controller.getAPostActionMethod() = this }
132+
MvcControllerPostMethod() {
133+
controller.getAPostActionMethod() = this and
134+
exists(Compilation compilation | compilation.getAFileCompiled() = this.getFile() |
135+
not hasGlobalWebMvcAntiforgeryFilter(compilation)
136+
)
137+
}
110138

111139
predicate hasValidateAntiForgeryAttribute() {
112140
this.getAnAttribute() instanceof ValidateAntiForgeryTokenAttribute or
@@ -116,10 +144,13 @@ class MvcControllerPostMethod extends Method {
116144

117145
class AspNetCoreControllerPostMethod extends Method {
118146
private AspNetCore::MicrosoftAspNetCoreMvcController controller;
147+
private Compilation compilation;
119148

120149
AspNetCoreControllerPostMethod() {
121150
controller.getAnActionMethod() = this and
122-
this.getAnAttribute() instanceof AspNetCore::MicrosoftAspNetCoreMvcHttpPostAttribute
151+
this.getAnAttribute() instanceof AspNetCore::MicrosoftAspNetCoreMvcHttpPostAttribute and
152+
compilation.getAFileCompiled() = this.getFile() and
153+
not hasGlobalAspNetMvcAntiForgeryFilter(compilation)
123154
}
124155

125156
predicate hasValidateAntiForgeryAttribute() {
@@ -128,7 +159,7 @@ class AspNetCoreControllerPostMethod extends Method {
128159
}
129160

130161
predicate hasRequireAntiForgeryAttribute() {
131-
hasAspNetCoreAntiForgeryMiddleware() and
162+
hasAspNetCoreAntiForgeryMiddleware(compilation) and
132163
(
133164
getEffectiveRequireAntiforgeryTokenAttributeOnMethod(this).requiresValidation()
134165
or
@@ -157,7 +188,7 @@ Element getAValidatedElement() {
157188
or
158189
any(AspNetCore::ValidateAntiForgeryAttribute a).getTarget() = result
159190
or
160-
hasAspNetCoreAntiForgeryMiddleware() and
191+
hasAspNetCoreAntiForgeryMiddleware(_) and
161192
any(RequireAntiforgeryTokenAttribute a | a.requiresValidation()).getTarget() = result
162193
}
163194

@@ -167,9 +198,7 @@ where
167198
// Verify that validate anti forgery token attributes are used somewhere within this project, to
168199
// avoid reporting false positives on projects that use an alternative approach to mitigate CSRF
169200
// issues.
170-
exists(getAValidatedElement()) and
171-
// Also ignore cases where a global anti forgery filter is in use.
172-
not hasGlobalAntiForgeryFilter()
201+
exists(getAValidatedElement())
173202
select postMethod,
174203
"Method '" + postMethod.getName() +
175204
"' handles a POST request without performing CSRF token validation."
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* The `cs/web/missing-token-validation` query now recognizes an ASP.NET Core `AutoValidateAntiforgeryTokenAttribute` registered as a global MVC filter through `AddControllersWithViews` (and friends), avoiding false-positive results for covered actions.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using Microsoft.AspNetCore.Builder;
2+
using Microsoft.AspNetCore.Mvc;
3+
using Microsoft.AspNetCore.Mvc.ViewFeatures;
4+
using Microsoft.AspNetCore.Routing;
5+
using Microsoft.Extensions.DependencyInjection;
6+
7+
public class HomeController : Controller
8+
{
9+
// GOOD: This is validated by the global filter.
10+
[HttpPost]
11+
public ActionResult Login()
12+
{
13+
return View();
14+
}
15+
16+
// GOOD: Antiforgery token is validated explicitly.
17+
[HttpPost]
18+
[ValidateAntiForgeryToken]
19+
public ActionResult UpdateDetails()
20+
{
21+
return View();
22+
}
23+
}
24+
25+
public class Program
26+
{
27+
public static void Main(string[] args)
28+
{
29+
var builder = WebApplication.CreateBuilder(args);
30+
31+
// Register MVC controllers and Razor views.
32+
// The global filter automatically validates antiforgery tokens
33+
// for unsafe HTTP methods such as POST, PUT, PATCH, and DELETE.
34+
builder.Services.AddControllersWithViews(options =>
35+
{
36+
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
37+
});
38+
}
39+
}

csharp/ql/test/query-tests/Security Features/CWE-352/global-aspnetcore/MissingAntiForgeryTokenValidation.expected

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
query: Security Features/CWE-352/MissingAntiForgeryTokenValidation.ql
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
semmle-extractor-options: /nostdlib /noconfig
2+
semmle-extractor-options: --load-sources-from-project:${testdir}/../../../../resources/stubs/_frameworks/Microsoft.AspNetCore.App/Microsoft.AspNetCore.App.csproj

0 commit comments

Comments
 (0)