Skip to content

Commit 5d03d72

Browse files
Enable Antiforgery validation for rest services in .NET Framework
1 parent b1e3fa0 commit 5d03d72

File tree

5 files changed

+74
-32
lines changed

5 files changed

+74
-32
lines changed

dotnet/src/dotnetcore/GxNetCoreStartup/Startup.cs

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
using Microsoft.Extensions.DependencyInjection;
3131
using Microsoft.Extensions.FileProviders;
3232
using Microsoft.Extensions.Logging;
33+
using Microsoft.Extensions.Options;
3334
using StackExchange.Redis;
3435

3536

@@ -206,7 +207,14 @@ public void ConfigureServices(IServiceCollection services)
206207
}
207208
});
208209

209-
210+
if (RestAPIHelpers.ValidateCsrfToken())
211+
{
212+
services.AddAntiforgery(options =>
213+
{
214+
options.HeaderName = HttpHeader.X_GXCSRF_TOKEN;
215+
options.SuppressXFrameOptionsHeader = false;
216+
});
217+
}
210218
services.AddDirectoryBrowser();
211219
if (GXUtil.CompressResponse())
212220
{
@@ -230,15 +238,6 @@ public void ConfigureServices(IServiceCollection services)
230238
options.EnableForHttps = true;
231239
});
232240
}
233-
if (RestAPIHelpers.ValidateCsrfToken())
234-
{
235-
services.AddAntiforgery(options =>
236-
{
237-
options.HeaderName = HttpHeader.X_GXCSRF_TOKEN;
238-
options.Cookie.Name = HttpHeader.X_GXCSRF_TOKEN;
239-
options.SuppressXFrameOptionsHeader = false;
240-
});
241-
}
242241
DefineCorsPolicy(services);
243242
services.AddMvc();
244243
}
@@ -424,9 +423,7 @@ public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IHos
424423

425424
if (string.Equals(requestPath, $"/{restBasePath}VerificationToken", StringComparison.OrdinalIgnoreCase) && antiforgery!=null)
426425
{
427-
var tokenSet = antiforgery.GetTokens(context);
428-
context.Response.Cookies.Append(HttpHeader.X_GXCSRF_TOKEN, tokenSet.RequestToken!,
429-
new CookieOptions { HttpOnly = false });
426+
ValidateAntiForgeryTokenMiddleware.SetAntiForgeryTokens(antiforgery, context);
430427
}
431428
return Task.CompletedTask;
432429
});
@@ -605,33 +602,31 @@ public async Task Invoke(HttpContext context)
605602
HttpMethods.IsDelete(context.Request.Method) ||
606603
HttpMethods.IsPut(context.Request.Method))
607604
{
608-
GXLogging.Debug(log, $"Antiforgery validation starts");
609605
string cookieToken = context.Request.Cookies[HttpHeader.X_GXCSRF_TOKEN];
610606
string headerToken = context.Request.Headers[HttpHeader.X_GXCSRF_TOKEN];
607+
GXLogging.Debug(log, $"Antiforgery validation, cookieToken:{cookieToken}, headerToken:{headerToken}");
611608

612-
if (!string.IsNullOrEmpty(headerToken) && !string.IsNullOrEmpty(cookieToken) && headerToken == cookieToken)
613-
{
614-
GXLogging.Debug(log, $"headerToken == cookieToken OK");
615-
}
616-
else
617-
{
618-
await _antiforgery.ValidateRequestAsync(context);
619-
GXLogging.Debug(log, $"Antiforgery validation OK");
620-
}
609+
await _antiforgery.ValidateRequestAsync(context);
610+
GXLogging.Debug(log, $"Antiforgery validation OK");
621611
}
622612
else if (HttpMethods.IsGet(context.Request.Method))
623613
{
624614
string tokens = context.Request.Cookies[HttpHeader.X_GXCSRF_TOKEN];
625615
if (string.IsNullOrEmpty(tokens))
626616
{
627-
AntiforgeryTokenSet tokenSet = _antiforgery.GetTokens(context);
628-
context.Response.Cookies.Append(HttpHeader.X_GXCSRF_TOKEN, tokenSet.RequestToken!,new CookieOptions { HttpOnly = false });
629-
GXLogging.Debug(log, $"Setting cookie ", HttpHeader.X_GXCSRF_TOKEN, "=", tokenSet.RequestToken!);
617+
SetAntiForgeryTokens(_antiforgery, context);
630618
}
631619
}
632620
}
633621
await _next(context);
634622
}
635-
623+
internal static void SetAntiForgeryTokens(IAntiforgery _antiforgery, HttpContext context)
624+
{
625+
AntiforgeryTokenSet tokenSet = _antiforgery.GetAndStoreTokens(context);
626+
context.Response.Cookies.Append(HttpHeader.X_GXCSRF_TOKEN, tokenSet.RequestToken,
627+
new CookieOptions { HttpOnly = false, Secure = GxContext.GetHttpSecure(context) == 1 });
628+
GXLogging.Debug(log, $"Setting cookie ", HttpHeader.X_GXCSRF_TOKEN, "=", tokenSet.RequestToken);
629+
}
630+
636631
}
637632
}

dotnet/src/dotnetframework/GxClasses/Core/GXApplication.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2133,18 +2133,22 @@ public string GetBrowserVersion()
21332133
}
21342134
}
21352135
public virtual short GetHttpSecure()
2136+
{
2137+
return GetHttpSecure(_HttpContext);
2138+
}
2139+
static internal short GetHttpSecure(HttpContext httpContext)
21362140
{
21372141
try
21382142
{
2139-
if (HttpContext == null)
2143+
if (httpContext == null)
21402144
return 0;
2141-
if (_HttpContext.Request.GetIsSecureFrontEnd())
2145+
if (httpContext.Request.GetIsSecureFrontEnd())
21422146
{
21432147
GXLogging.Debug(log, "Front-End-Https header activated");
21442148
return 1;
21452149
}
21462150
else
2147-
return _HttpContext.Request.GetIsSecureConnection();
2151+
return httpContext.Request.GetIsSecureConnection();
21482152
}
21492153
catch
21502154
{

dotnet/src/dotnetframework/GxClasses/GxClasses.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<ItemGroup>
1313
<PackageReference Include="GeneXus.NodaTime" Version="2.4.19.1" />
1414
<PackageReference Include="ManagedFusion.Rewriter" Version="3.7.0" />
15+
<PackageReference Include="Microsoft.AspNet.WebPages" Version="3.2.9" />
1516
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.5.1" />
1617
<PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.0" />
1718
<PackageReference Include="MySqlConnector" Version="2.2.3" />

dotnet/src/dotnetframework/GxClasses/Services/GXRestServices.cs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Net.Http;
88
using System.Runtime.Serialization;
99
using System.Runtime.Serialization.Json;
10+
using System.Security;
1011
using System.ServiceModel;
1112
using System.ServiceModel.Channels;
1213
using System.ServiceModel.Configuration;
@@ -15,13 +16,16 @@
1516
using System.ServiceModel.Web;
1617
using System.Text;
1718
using System.Web;
19+
using System.Web.Helpers;
20+
using System.Web.Mvc;
1821
using GeneXus.Application;
1922
using GeneXus.Configuration;
2023
using GeneXus.Data;
2124
using GeneXus.Http;
2225
using GeneXus.Metadata;
2326
using GeneXus.Security;
2427
using log4net;
28+
using Microsoft.IdentityModel.Tokens;
2529
using Microsoft.Net.Http.Headers;
2630

2731
namespace GeneXus.Utils
@@ -388,7 +392,7 @@ public void WebException(Exception ex)
388392
{
389393
throw ex;
390394
}
391-
else if (ex is FormatException)
395+
else if (ex is FormatException || ex is HttpAntiForgeryException)
392396
{
393397
HttpHelper.SetUnexpectedError(httpContext, HttpStatusCode.BadRequest, ex);
394398
}
@@ -556,8 +560,13 @@ void AddHeader(string header, string value)
556560
httpContext.Response.Headers[header]= value;
557561
}
558562
}
563+
[SecuritySafeCritical]
559564
public bool ProcessHeaders(string queryId)
560565
{
566+
if (RestAPIHelpers.ValidateCsrfToken())
567+
{
568+
ValidateAntiforgery();
569+
}
561570

562571
NameValueCollection headers = GetHeaders();
563572
String language = null, theme = null, etag = null;
@@ -597,6 +606,39 @@ public bool ProcessHeaders(string queryId)
597606
return true;
598607
}
599608

609+
[SecurityCritical]
610+
private void ValidateAntiforgery()
611+
{
612+
string cookieToken, formToken;
613+
string httpMethod = context.HttpContext.Request.HttpMethod;
614+
string tokens = context.HttpContext.Request.Cookies[HttpHeader.X_GXCSRF_TOKEN]?.Value;
615+
string internalCookieToken = context.HttpContext.Request.Cookies[HttpHeader.X_GXCSRF_TOKEN]?.Value;
616+
if (httpMethod == HttpMethod.Get.Method && (string.IsNullOrEmpty(tokens) || string.IsNullOrEmpty(internalCookieToken)))
617+
{
618+
AntiForgery.GetTokens(null, out cookieToken, out formToken);
619+
#pragma warning disable SCS0009 // The cookie is missing security flag HttpOnly
620+
HttpCookie cookie = new HttpCookie(HttpHeader.X_GXCSRF_TOKEN, formToken)
621+
{
622+
HttpOnly = false,
623+
Secure = context.GetHttpSecure() == 1,
624+
};
625+
#pragma warning restore SCS0009 // The cookie is missing security flag HttpOnly
626+
HttpCookie internalCookie = new HttpCookie(AntiForgeryConfig.CookieName, cookieToken)
627+
{
628+
HttpOnly = true,
629+
Secure = context.GetHttpSecure() == 1,
630+
};
631+
context.HttpContext.Response.SetCookie(cookie);
632+
context.HttpContext.Response.SetCookie(internalCookie);
633+
}
634+
if (httpMethod == HttpMethod.Delete.Method || httpMethod == HttpMethod.Post.Method || httpMethod == HttpMethod.Put.Method)
635+
{
636+
cookieToken = context.HttpContext.Request.Cookies[AntiForgeryConfig.CookieName]?.Value;
637+
string headerToken = context.HttpContext.Request.Headers[HttpHeader.X_GXCSRF_TOKEN];
638+
AntiForgery.Validate(cookieToken, headerToken);
639+
}
640+
}
641+
600642
private void SendCacheHeaders()
601643
{
602644
if (string.IsNullOrEmpty(context.GetHeader(HttpHeader.CACHE_CONTROL)))

dotnet/test/DotNetCoreAttackMitigationTest/Middleware/RestServiceTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public async Task TestSimpleRestPost()
3535
Assert.Equal(System.Net.HttpStatusCode.BadRequest, response.StatusCode);
3636
}
3737

38-
[Fact(Skip ="Disable until solve concurrency issue")]
38+
[Fact]
3939
public async Task RunController()
4040
{
4141

0 commit comments

Comments
 (0)