Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d66fa18
Incorporate CSRF token handling into the GAM security provider.
claudiamurialdo Aug 7, 2023
d315f98
Fix build error.
claudiamurialdo Aug 7, 2023
f5b8a36
Remove unnecessary assignment.
claudiamurialdo Aug 7, 2023
8b5c0fd
Rename Config key to ValidateCSRF
claudiamurialdo Aug 7, 2023
6cf9ce8
Add ValidateCsrfToken routine and reuse code.
claudiamurialdo Aug 7, 2023
a908a95
validateCSRFToken was not initialized when objIntegratedSecurityLevel…
claudiamurialdo Aug 9, 2023
5f0b721
validateCSRFToken was not initialized when objIntegratedSecurityLevel…
claudiamurialdo Aug 10, 2023
80768f7
Add Cookie-to-header AntiForgery token validation.
claudiamurialdo Aug 17, 2023
19cebd0
The ValidateCsrf configuration was always true, regardless of the val…
claudiamurialdo Aug 17, 2023
7ee3b59
Validate forgery token also in PUT and DELETE verbs.
claudiamurialdo Aug 17, 2023
ad26c79
Add Unit Test for CSRF Validation
claudiamurialdo Aug 17, 2023
d011249
Remove unsused package references.
claudiamurialdo Aug 17, 2023
d45953f
Remove unused package.
claudiamurialdo Aug 17, 2023
e0a0e0a
Remove unused packages
claudiamurialdo Aug 17, 2023
ad6e960
Move VerificationToken to MapRoute.
claudiamurialdo Aug 17, 2023
962d16d
Fix test.
claudiamurialdo Aug 17, 2023
fea75cf
SuppressXFrameOptionsHeader is false.
claudiamurialdo Aug 17, 2023
b1e3fa0
Disable some tests until solve concurrency issue.
claudiamurialdo Aug 17, 2023
5d03d72
Enable Antiforgery validation for rest services in .NET Framework
claudiamurialdo Aug 18, 2023
c70f1fe
Remove unneeded using.
claudiamurialdo Aug 18, 2023
e002e7e
Set ReasonPhrase to code InvalidCSRFToken on failed validation.
claudiamurialdo Aug 18, 2023
ecec3fe
Fix System.MethodAccessException: Attempt by security transparent met…
claudiamurialdo Aug 21, 2023
427e4f5
Revert changes for CSRFTOken validation in GAM layer.
claudiamurialdo Aug 22, 2023
1e5e57d
Minor format changes.
claudiamurialdo Aug 22, 2023
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
11 changes: 9 additions & 2 deletions dotnet/DotNetStandardClasses.sln
Original file line number Diff line number Diff line change
Expand Up @@ -243,9 +243,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GxOffice", "src\dotnetcore\
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "excel", "excel", "{B521FF6D-E081-4DE6-AC00-A9BE3B6439D1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GXEventRouter", "src\dotnetcore\Providers\Messaging\GXEventRouter\GXEventRouter.csproj", "{5BBC75F0-E51A-4EBD-A628-92498D319B1D}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GXEventRouter", "src\dotnetcore\Providers\Messaging\GXEventRouter\GXEventRouter.csproj", "{5BBC75F0-E51A-4EBD-A628-92498D319B1D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GXAzureEventGrid", "src\dotnetcore\Providers\Messaging\GXAzureEventGrid\GXAzureEventGrid.csproj", "{7250CDB1-95C4-4822-B01B-3CBD73324CC9}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GXAzureEventGrid", "src\dotnetcore\Providers\Messaging\GXAzureEventGrid\GXAzureEventGrid.csproj", "{7250CDB1-95C4-4822-B01B-3CBD73324CC9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCoreAttackMitigationTest", "test\DotNetCoreAttackMitigationTest\DotNetCoreAttackMitigationTest.csproj", "{2D615969-53E2-4B77-9A9A-75C33865CF76}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -597,6 +599,10 @@ Global
{7250CDB1-95C4-4822-B01B-3CBD73324CC9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7250CDB1-95C4-4822-B01B-3CBD73324CC9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7250CDB1-95C4-4822-B01B-3CBD73324CC9}.Release|Any CPU.Build.0 = Release|Any CPU
{2D615969-53E2-4B77-9A9A-75C33865CF76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2D615969-53E2-4B77-9A9A-75C33865CF76}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2D615969-53E2-4B77-9A9A-75C33865CF76}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2D615969-53E2-4B77-9A9A-75C33865CF76}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -715,6 +721,7 @@ Global
{B521FF6D-E081-4DE6-AC00-A9BE3B6439D1} = {2261B65E-3757-4E5B-9DCD-EAE8D1E236A3}
{5BBC75F0-E51A-4EBD-A628-92498D319B1D} = {4C43F2DA-59E5-46F5-B691-195449498555}
{7250CDB1-95C4-4822-B01B-3CBD73324CC9} = {30159B0F-BE61-4DB7-AC02-02851426BE4B}
{2D615969-53E2-4B77-9A9A-75C33865CF76} = {1D6F1776-FF4B-46C2-9B3D-BC46CCF049DC}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E18684C9-7D76-45CD-BF24-E3944B7F174C}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
[assembly: InternalsVisibleTo("DotNetCoreWebUnitTest")]
[assembly: InternalsVisibleTo("GxNetCoreStartup")]
[assembly: InternalsVisibleTo("GeneXus.Deploy.AzureFunctions.Handlers")]
[assembly: InternalsVisibleTo("AzureFunctionsTest")]
[assembly: InternalsVisibleTo("AzureFunctionsTest")]
[assembly: InternalsVisibleTo("DotNetCoreAttackMitigationTest")]
1 change: 1 addition & 0 deletions dotnet/src/dotnetcore/GxClasses/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
[assembly: InternalsVisibleTo("GXEventRouter")]
[assembly: InternalsVisibleTo("DotNetCoreUnitTest")]
[assembly: InternalsVisibleTo("DotNetCoreWebUnitTest")]
[assembly: InternalsVisibleTo("DotNetCoreAttackMitigationTest")]
[assembly: InternalsVisibleTo("GeneXus.Deploy.AzureFunctions.Handlers")]
[assembly: InternalsVisibleTo("AzureFunctionsTest")]
[assembly: InternalsVisibleTo("GXMessageBroker")]
99 changes: 97 additions & 2 deletions dotnet/src/dotnetcore/GxNetCoreStartup/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@


using System;
using System.Collections.Generic;
using System.IO;
Expand All @@ -12,6 +14,7 @@
using GxClasses.Web.Middleware;
using log4net;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Diagnostics;
Expand Down Expand Up @@ -104,6 +107,10 @@ private static void LocatePhysicalLocalPath()

public static class GXHandlerExtensions
{
public static IApplicationBuilder UseAntiforgeryTokens(this IApplicationBuilder app, string basePath)
{
return app.UseMiddleware<ValidateAntiForgeryTokenMiddleware>(basePath);
}
public static IApplicationBuilder UseGXHandlerFactory(this IApplicationBuilder builder, string basePath)
{
return builder.UseMiddleware<HandlerFactory>(basePath);
Expand Down Expand Up @@ -199,7 +206,14 @@ public void ConfigureServices(IServiceCollection services)
}
});


if (RestAPIHelpers.ValidateCsrfToken())
{
services.AddAntiforgery(options =>
{
options.HeaderName = HttpHeader.X_GXCSRF_TOKEN;
options.SuppressXFrameOptionsHeader = false;
});
}
services.AddDirectoryBrowser();
if (GXUtil.CompressResponse())
{
Expand Down Expand Up @@ -386,6 +400,12 @@ public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IHos

string restBasePath = string.IsNullOrEmpty(VirtualPath) ? REST_BASE_URL : $"{VirtualPath}/{REST_BASE_URL}";
string apiBasePath = string.IsNullOrEmpty(VirtualPath) ? string.Empty : $"{VirtualPath}/";
IAntiforgery antiforgery = null;
if (RestAPIHelpers.ValidateCsrfToken())
{
antiforgery = app.ApplicationServices.GetRequiredService<IAntiforgery>();
app.UseAntiforgeryTokens(restBasePath);
}
app.UseMvc(routes =>
{
foreach (string serviceBasePath in servicesBase)
Expand All @@ -397,6 +417,16 @@ public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IHos
routes.MapRoute($"{s}", new RequestDelegate(gxRouting.ProcessRestRequest));
}
}
routes.MapRoute($"{restBasePath}VerificationToken", (context) =>
{
string requestPath = context.Request.Path.Value;

if (string.Equals(requestPath, $"/{restBasePath}VerificationToken", StringComparison.OrdinalIgnoreCase) && antiforgery!=null)
{
ValidateAntiForgeryTokenMiddleware.SetAntiForgeryTokens(antiforgery, context);
}
return Task.CompletedTask;
});
routes.MapRoute($"{restBasePath}{{*{UrlTemplateControllerWithParms}}}", new RequestDelegate(gxRouting.ProcessRestRequest));
routes.MapRoute("Default", VirtualPath, new { controller = "Home", action = "Index" });
});
Expand Down Expand Up @@ -474,9 +504,11 @@ bool IsAspx(HttpContext context, string basePath)
}
public class CustomExceptionHandlerMiddleware
{
const string InvalidCSRFToken = "InvalidCSRFToken";
static readonly ILog log = log4net.LogManager.GetLogger(typeof(CustomExceptionHandlerMiddleware));
public async Task Invoke(HttpContext httpContext)
{
string httpReasonPhrase=string.Empty;
Exception ex = httpContext.Features.Get<IExceptionHandlerFeature>()?.Error;
HttpStatusCode httpStatusCode = (HttpStatusCode)httpContext.Response.StatusCode;
if (ex!=null)
Expand All @@ -485,6 +517,13 @@ public async Task Invoke(HttpContext httpContext)
{
httpStatusCode = HttpStatusCode.NotFound;
}
else if (ex is AntiforgeryValidationException)
{
//"The required antiforgery header value "X-GXCSRF-TOKEN" is not present.
httpStatusCode = HttpStatusCode.BadRequest;
httpReasonPhrase = InvalidCSRFToken;
GXLogging.Error(log, $"Validation of antiforgery failed", ex);
}
else
{
httpStatusCode = HttpStatusCode.InternalServerError;
Expand All @@ -495,13 +534,19 @@ public async Task Invoke(HttpContext httpContext)
{
string redirectPage = Config.MapCustomError(httpStatusCode.ToString(HttpHelper.INT_FORMAT));
if (!string.IsNullOrEmpty(redirectPage))
{
{
httpContext.Response.Redirect($"{httpContext.Request.GetApplicationPath()}/{redirectPage}");
}
else
{
httpContext.Response.StatusCode = (int)httpStatusCode;
}
if (!string.IsNullOrEmpty(httpReasonPhrase))
{
IHttpResponseFeature responseReason = httpContext.Response.HttpContext.Features.Get<IHttpResponseFeature>();
if (responseReason!=null)
responseReason.ReasonPhrase = httpReasonPhrase;
}
}
await Task.CompletedTask;
}
Expand Down Expand Up @@ -542,4 +587,54 @@ public IActionResult Index()
return Redirect(defaultFiles[0]);
}
}
public class ValidateAntiForgeryTokenMiddleware
{
static readonly ILog log = log4net.LogManager.GetLogger(typeof(ValidateAntiForgeryTokenMiddleware));

private readonly RequestDelegate _next;
private readonly IAntiforgery _antiforgery;
private string _basePath;

public ValidateAntiForgeryTokenMiddleware(RequestDelegate next, IAntiforgery antiforgery, String basePath)
{
_next = next;
_antiforgery = antiforgery;
_basePath = "/" + basePath;
}

public async Task Invoke(HttpContext context)
{
if (context.Request.Path.HasValue && context.Request.Path.Value.StartsWith(_basePath))
{
if (HttpMethods.IsPost(context.Request.Method) ||
HttpMethods.IsDelete(context.Request.Method) ||
HttpMethods.IsPut(context.Request.Method))
{
string cookieToken = context.Request.Cookies[HttpHeader.X_GXCSRF_TOKEN];
string headerToken = context.Request.Headers[HttpHeader.X_GXCSRF_TOKEN];
GXLogging.Debug(log, $"Antiforgery validation, cookieToken:{cookieToken}, headerToken:{headerToken}");

await _antiforgery.ValidateRequestAsync(context);
GXLogging.Debug(log, $"Antiforgery validation OK");
}
else if (HttpMethods.IsGet(context.Request.Method))
{
string tokens = context.Request.Cookies[HttpHeader.X_GXCSRF_TOKEN];
if (string.IsNullOrEmpty(tokens))
{
SetAntiForgeryTokens(_antiforgery, context);
}
}
}
await _next(context);
}
internal static void SetAntiForgeryTokens(IAntiforgery _antiforgery, HttpContext context)
{
AntiforgeryTokenSet tokenSet = _antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append(HttpHeader.X_GXCSRF_TOKEN, tokenSet.RequestToken,
new CookieOptions { HttpOnly = false, Secure = GxContext.GetHttpSecure(context) == 1 });
GXLogging.Debug(log, $"Setting cookie ", HttpHeader.X_GXCSRF_TOKEN, "=", tokenSet.RequestToken);
}

}
}
10 changes: 7 additions & 3 deletions dotnet/src/dotnetframework/GxClasses/Core/GXApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2133,18 +2133,22 @@ public string GetBrowserVersion()
}
}
public virtual short GetHttpSecure()
{
return GetHttpSecure(_HttpContext);
}
static internal short GetHttpSecure(HttpContext httpContext)
{
try
{
if (HttpContext == null)
if (httpContext == null)
return 0;
if (_HttpContext.Request.GetIsSecureFrontEnd())
if (httpContext.Request.GetIsSecureFrontEnd())
{
GXLogging.Debug(log, "Front-End-Https header activated");
return 1;
}
else
return _HttpContext.Request.GetIsSecureConnection();
return httpContext.Request.GetIsSecureConnection();
}
catch
{
Expand Down
8 changes: 4 additions & 4 deletions dotnet/src/dotnetframework/GxClasses/Core/gxconfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -794,8 +794,8 @@ public class Preferences
public static string DefaultRewriteFile = "rewrite.config";
const string USE_NAMED_PARAMETERS = "UseNamedParameters";
const string REST_DATES_WITH_MILLIS = "REST_DATES_WITH_MILLIS";
const string YES = "1";
const string NO = "0";
internal const string YES = "1";
internal const string NO = "0";
static string defaultDatastore;
const string DEFAULT_DS = "Default";
static int httpclient_max_per_route = -1;
Expand Down Expand Up @@ -867,9 +867,9 @@ public static bool RewriteEnabled
if (rewriteEnabled == -1)
{
#if NETCORE
var basePath = FileUtil.GetBasePath();
string basePath = FileUtil.GetBasePath();
#else
var basePath = Directory.GetParent(FileUtil.GetStartupDirectory()).FullName;
string basePath = Directory.GetParent(FileUtil.GetStartupDirectory()).FullName;
#endif
string rewriteFile = Path.Combine(basePath, DefaultRewriteFile);
rewriteEnabled = File.Exists(rewriteFile)?1:0;
Expand Down
1 change: 1 addition & 0 deletions dotnet/src/dotnetframework/GxClasses/GxClasses.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<ItemGroup>
<PackageReference Include="GeneXus.NodaTime" Version="2.4.19.1" />
<PackageReference Include="ManagedFusion.Rewriter" Version="3.7.0" />
<PackageReference Include="Microsoft.AspNet.WebPages" Version="3.2.9" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.5.1" />
<PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.0" />
<PackageReference Include="MySqlConnector" Version="2.2.3" />
Expand Down
8 changes: 6 additions & 2 deletions dotnet/src/dotnetframework/GxClasses/Helpers/GXRestUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
using log4net;
using System.IO;
using Jayrock.Json;


using GeneXus.Configuration;

namespace GeneXus.Utils
{
Expand Down Expand Up @@ -140,5 +139,10 @@ public static Dictionary<string, object> ReadRestBodyParameters(Stream stream)
}
return bodyParameters;
}

internal static bool ValidateCsrfToken()
{
return Config.GetValueOf("ValidateCSRF", Preferences.NO) == Preferences.YES;
}
}
}
1 change: 1 addition & 0 deletions dotnet/src/dotnetframework/GxClasses/Helpers/HttpHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public class HttpHeader
public static string XGXFILENAME = "x-gx-filename";
internal static string ACCEPT = "Accept";
internal static string TRANSFER_ENCODING = "Transfer-Encoding";
internal static string X_GXCSRF_TOKEN = "X-GXCSRF-TOKEN";
}
internal class HttpHeaderValue
{
Expand Down
Loading