From 5bb31afc834f53d5ea719d782744ff9c7ab70fc2 Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Mon, 8 Dec 2025 11:51:01 +0000 Subject: [PATCH] C# CSRF query: add support for ASP.NET Core --- .../MissingAntiForgeryTokenValidation.ql | 29 +++++++++++++---- .../MissingAntiForgeryTokenValidation.cs | 31 +++++++++++++++++++ ...MissingAntiForgeryTokenValidation.expected | 1 + .../MissingAntiForgeryTokenValidation.qlref | 1 + .../CWE-352/missing-aspnetcore/options | 2 ++ 5 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 csharp/ql/test/query-tests/Security Features/CWE-352/missing-aspnetcore/MissingAntiForgeryTokenValidation.cs create mode 100644 csharp/ql/test/query-tests/Security Features/CWE-352/missing-aspnetcore/MissingAntiForgeryTokenValidation.expected create mode 100644 csharp/ql/test/query-tests/Security Features/CWE-352/missing-aspnetcore/MissingAntiForgeryTokenValidation.qlref create mode 100644 csharp/ql/test/query-tests/Security Features/CWE-352/missing-aspnetcore/options diff --git a/csharp/ql/src/Security Features/CWE-352/MissingAntiForgeryTokenValidation.ql b/csharp/ql/src/Security Features/CWE-352/MissingAntiForgeryTokenValidation.ql index 77fc64f65a7..acdc4637221 100644 --- a/csharp/ql/src/Security Features/CWE-352/MissingAntiForgeryTokenValidation.ql +++ b/csharp/ql/src/Security Features/CWE-352/MissingAntiForgeryTokenValidation.ql @@ -15,6 +15,7 @@ import csharp import semmle.code.csharp.frameworks.system.Web import semmle.code.csharp.frameworks.system.web.Helpers import semmle.code.csharp.frameworks.system.web.Mvc +import semmle.code.csharp.frameworks.microsoft.AspNetCore as AspNetCore private Method getAValidatingMethod() { result = any(AntiForgeryClass a).getValidateMethod() @@ -35,6 +36,8 @@ private Method getAStartedMethod() { /** * Holds if the project has a global anti forgery filter. + * + * No AspNetCore case here as the corresponding class doesn't seem to exist. */ predicate hasGlobalAntiForgeryFilter() { // A global filter added @@ -48,16 +51,30 @@ predicate hasGlobalAntiForgeryFilter() { ) } -from Controller c, Method postMethod +predicate isUnvalidatedPostMethod(Class c, Method m) { + c.(Controller).getAPostActionMethod() = m and + not m.getAnAttribute() instanceof ValidateAntiForgeryTokenAttribute and + not c.getAnAttribute() instanceof ValidateAntiForgeryTokenAttribute + or + c.(AspNetCore::MicrosoftAspNetCoreMvcController).getAnActionMethod() = m and + m.getAnAttribute() instanceof AspNetCore::MicrosoftAspNetCoreMvcHttpPostAttribute and + not m.getAnAttribute() instanceof AspNetCore::ValidateAntiForgeryAttribute and + not c.getAnAttribute() instanceof AspNetCore::ValidateAntiForgeryAttribute +} + +Element getAValidatedElement() { + any(ValidateAntiForgeryTokenAttribute a).getTarget() = result + or + any(AspNetCore::ValidateAntiForgeryAttribute a).getTarget() = result +} + +from Class c, Method postMethod where - postMethod = c.getAPostActionMethod() and - // The method is not protected by a validate anti forgery token attribute - not postMethod.getAnAttribute() instanceof ValidateAntiForgeryTokenAttribute and - not c.getAnAttribute() instanceof ValidateAntiForgeryTokenAttribute and + isUnvalidatedPostMethod(c, postMethod) and // Verify that validate anti forgery token attributes are used somewhere within this project, to // avoid reporting false positives on projects that use an alternative approach to mitigate CSRF // issues. - exists(ValidateAntiForgeryTokenAttribute a, Element e | e = a.getTarget()) and + exists(getAValidatedElement()) and // Also ignore cases where a global anti forgery filter is in use. not hasGlobalAntiForgeryFilter() select postMethod, diff --git a/csharp/ql/test/query-tests/Security Features/CWE-352/missing-aspnetcore/MissingAntiForgeryTokenValidation.cs b/csharp/ql/test/query-tests/Security Features/CWE-352/missing-aspnetcore/MissingAntiForgeryTokenValidation.cs new file mode 100644 index 00000000000..84d904f900d --- /dev/null +++ b/csharp/ql/test/query-tests/Security Features/CWE-352/missing-aspnetcore/MissingAntiForgeryTokenValidation.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Mvc; + +public class HomeController : Controller +{ + // BAD: Anti forgery token has been forgotten + [HttpPost] + public ActionResult Login() + { + return View(); + } + + // GOOD: Anti forgery token is validated + [HttpPost] + [ValidateAntiForgeryToken] + public ActionResult UpdateDetails() + { + return View(); + } + + // No validation required, as this is a GET method. + public ActionResult ShowHelp() + { + return View(); + } + + // Should be ignored, because it is not an action method + [NonAction] + public void UtilityMethod() + { + } +} diff --git a/csharp/ql/test/query-tests/Security Features/CWE-352/missing-aspnetcore/MissingAntiForgeryTokenValidation.expected b/csharp/ql/test/query-tests/Security Features/CWE-352/missing-aspnetcore/MissingAntiForgeryTokenValidation.expected new file mode 100644 index 00000000000..859fd4be920 --- /dev/null +++ b/csharp/ql/test/query-tests/Security Features/CWE-352/missing-aspnetcore/MissingAntiForgeryTokenValidation.expected @@ -0,0 +1 @@ +| MissingAntiForgeryTokenValidation.cs:7:25:7:29 | Login | Method 'Login' handles a POST request without performing CSRF token validation. | diff --git a/csharp/ql/test/query-tests/Security Features/CWE-352/missing-aspnetcore/MissingAntiForgeryTokenValidation.qlref b/csharp/ql/test/query-tests/Security Features/CWE-352/missing-aspnetcore/MissingAntiForgeryTokenValidation.qlref new file mode 100644 index 00000000000..5e1ab2426c6 --- /dev/null +++ b/csharp/ql/test/query-tests/Security Features/CWE-352/missing-aspnetcore/MissingAntiForgeryTokenValidation.qlref @@ -0,0 +1 @@ +query: Security Features/CWE-352/MissingAntiForgeryTokenValidation.ql diff --git a/csharp/ql/test/query-tests/Security Features/CWE-352/missing-aspnetcore/options b/csharp/ql/test/query-tests/Security Features/CWE-352/missing-aspnetcore/options new file mode 100644 index 00000000000..698ad488b6d --- /dev/null +++ b/csharp/ql/test/query-tests/Security Features/CWE-352/missing-aspnetcore/options @@ -0,0 +1,2 @@ +semmle-extractor-options: /nostdlib /noconfig +semmle-extractor-options: --load-sources-from-project:${testdir}/../../../../resources/stubs/_frameworks/Microsoft.AspNetCore.App/Microsoft.AspNetCore.App.csproj