Skip to content

Commit

Permalink
Merge pull request #90 from atidev/UpdateXssAttribute
Browse files Browse the repository at this point in the history
Update xss attribute
  • Loading branch information
Artikud authored Aug 28, 2024
2 parents 9b37263 + d307af7 commit 7df83b7
Show file tree
Hide file tree
Showing 17 changed files with 165 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Net;
using System.Net.Http;
using ATI.Services.Common.Logging;
using ATI.Services.Common.Metrics;
using ATI.Services.Common.Options;
using Microsoft.Extensions.DependencyInjection;
using NLog;
Expand All @@ -14,7 +13,6 @@
using Polly.Extensions.Http;
using Polly.Registry;
using Polly.Timeout;
using Prometheus;

namespace ATI.Services.Common.Http.Extensions;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
Expand Down
2 changes: 0 additions & 2 deletions ATI.Services.Common/Logging/LoggerExtension.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using ATI.Services.Common.Serializers;
using ATI.Services.Common.Serializers.SystemTextJsonSerialization;
using JetBrains.Annotations;
using NLog;
using static ATI.Services.Common.Serializers.SystemTextJsonSerialization.SystemTextJsonSerializerBase;

namespace ATI.Services.Common.Logging;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Net.Http;
using System.Threading.Tasks;
using ATI.Services.Common.Behaviors;
using ATI.Services.Common.Http;
using ATI.Services.Common.Http.Extensions;
using ATI.Services.Common.Logging;
using ATI.Services.Common.Variables;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using ATI.Services.Common.Extensions;
using ATI.Services.Common.Http;
using ATI.Services.Common.Localization;
using ATI.Services.Common.Variables;
using Microsoft.AspNetCore.Http;

namespace ATI.Services.Common.Metrics.HttpWrapper;
Expand Down
1 change: 0 additions & 1 deletion ATI.Services.Common/Metrics/MetricsInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Runtime.CompilerServices;
using ATI.Services.Common.Http;
using ATI.Services.Common.Logging;
using ATI.Services.Common.Variables;
using Microsoft.AspNetCore.Http;
using Prometheus;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System.Linq;
using System.Threading.Tasks;
using ATI.Services.Common.Http;
using ATI.Services.Common.Variables;
using Microsoft.AspNetCore.Http;
using Prometheus;

Expand Down
1 change: 0 additions & 1 deletion ATI.Services.Common/Metrics/MetricsTimer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Diagnostics;
using System.Text.Json;
using ATI.Services.Common.Logging;
using ATI.Services.Common.Serializers;
using ATI.Services.Common.Serializers.SystemTextJsonSerialization;
using NLog;
using Prometheus;
Expand Down
39 changes: 39 additions & 0 deletions ATI.Services.Common/Xss/Attribute/XssAttributesHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using ATI.Services.Common.Behaviors;
using Microsoft.AspNetCore.Mvc.Filters;

namespace ATI.Services.Common.Xss.Attribute;

static class XssAttributesHelper
{
public static async Task FieldValidationAction(ActionExecutingContext context, ActionExecutionDelegate next,
Func<string, bool> isXssInjectedFunc)
{
var actionArguments = context.ActionArguments;

foreach (var actionArgumentPair in actionArguments)
{
var model = actionArgumentPair.Value;
var modelType = model.GetType();
var properties = modelType.GetProperties()
.Where(x => x.IsDefined(typeof(XssValidateAttribute), true));

foreach (var property in properties)
{
var value = property.GetValue(model)?.ToString();
if (value == null) continue;

if (isXssInjectedFunc(value))
{
context.Result = CommonBehavior.GetActionResult(ActionStatus.BadRequest,
false, "XSS injection detected from property attribute.");
return;
}
}
}

await next();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters;

namespace ATI.Services.Common.Xss.Attribute;

public class XssFieldStrictValidationFilterAttribute : System.Attribute, IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
await XssAttributesHelper.FieldValidationAction(context, next, XssHelper.IsStrictXssInjected);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.Linq;
using System.Threading.Tasks;
using ATI.Services.Common.Behaviors;
using Microsoft.AspNetCore.Mvc.Filters;

namespace ATI.Services.Common.Xss.Attribute;
Expand All @@ -9,28 +7,6 @@ public class XssFieldValidationFilterAttribute : System.Attribute, IAsyncActionF
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var actionArguments = context.ActionArguments;

foreach (var actionArgumentPair in actionArguments)
{
var model = actionArgumentPair.Value;
var modelType = model.GetType();
var properties = modelType.GetProperties()
.Where(x => x.IsDefined(typeof(XssValidateAttribute), true));

foreach (var property in properties)
{
var value = property.GetValue(model)?.ToString();
if(value == null) continue;

if (XssHelper.IsXssInjected(value))
{
context.Result = CommonBehavior.GetActionResult(ActionStatus.BadRequest,
false, "XSS injection detected from property attribute.");
}
}
}

await next();
await XssAttributesHelper.FieldValidationAction(context, next, XssHelper.IsXssInjected);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Threading.Tasks;
using ATI.Services.Common.Behaviors;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Mvc.Filters;

namespace ATI.Services.Common.Xss.Attribute;

[PublicAPI]
[AttributeUsage(AttributeTargets.Method)]
public class XssInputStrictValidationFilterAttribute : System.Attribute, IAsyncResourceFilter
{
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{
var httpContext = context.HttpContext;
if (await XssHelper.IsStrictXssInjected(httpContext))
{
context.Result = CommonBehavior.GetActionResult(ActionStatus.BadRequest,
false, "XSS injection detected from middleware.");
}
else
{
await next();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using System;
using System.Threading.Tasks;
using ATI.Services.Common.Behaviors;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Mvc.Filters;

namespace ATI.Services.Common.Xss.Attribute;

[PublicAPI]
[AttributeUsage(AttributeTargets.Method)]
public class XssInputValidationFilterAttribute : System.Attribute, IAsyncResourceFilter
{
Expand All @@ -21,4 +23,4 @@ public async Task OnResourceExecutionAsync(ResourceExecutingContext context, Res
await next();
}
}
}
}
29 changes: 21 additions & 8 deletions ATI.Services.Common/Xss/XssExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
using ATI.Services.Common.Xss.Attribute;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

namespace ATI.Services.Common.Xss;

[PublicAPI]
public static class XssExtensions
{
public static IApplicationBuilder UseXssValidation(this IApplicationBuilder builder)
{
return builder.UseMiddleware<XssMiddleware>();
{
return builder.UseMiddleware<XssMiddleware>();
}

public static IApplicationBuilder UseXssStrictValidation(this IApplicationBuilder builder)
{
return builder.UseMiddleware<XssStrictMiddleware>();
}


public static IMvcBuilder AddXssStrictValidationAttribute(this IServiceCollection services)
{
return services.AddControllers(options =>
{
options.Filters.Add(typeof(XssFieldStrictValidationFilterAttribute));
});
}

public static IMvcBuilder AddXssValidationAttribute(this IServiceCollection services)
{
return services.AddControllers(options =>
{
options.Filters.Add(typeof(XssFieldValidationFilterAttribute));
});
return services.AddControllers(
options => { options.Filters.Add(typeof(XssFieldValidationFilterAttribute)); });
}
}
}
47 changes: 39 additions & 8 deletions ATI.Services.Common/Xss/XssHelper.cs
Original file line number Diff line number Diff line change
@@ -1,35 +1,66 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using AngleSharp.Css.Dom;
using Ganss.Xss;
using Microsoft.AspNetCore.Http;

namespace ATI.Services.Common.Xss;

public static class XssHelper
{
private static readonly HtmlSanitizer Sanitizer = new();

private static readonly HtmlSanitizer DefaultSanitizer = new();

private static readonly HtmlSanitizer StrictSanitizer = new(new HtmlSanitizerOptions()
{
AllowedTags = new HashSet<string>(),
AllowedAttributes = new HashSet<string>(),
AllowedCssProperties = new HashSet<string>(),
AllowedSchemes = new HashSet<string>(),
UriAttributes = HtmlSanitizerDefaults.UriAttributes,
AllowedAtRules = new HashSet<CssRuleType>(),
AllowedCssClasses = new HashSet<string>()
});


public static bool IsXssInjected(string rawText)
{
var sanitised = Sanitizer.Sanitize(rawText);
var sanitised = DefaultSanitizer.Sanitize(rawText);
return !rawText.Equals(sanitised);
}

public static bool IsStrictXssInjected(string rawText)
{
var sanitised = StrictSanitizer.Sanitize(rawText);
return !rawText.Equals(sanitised);
}

public static async Task<bool> IsXssInjected(HttpContext httpContext)
{
var raw = await GetStringsFromHttpContext(httpContext);
return IsXssInjected(raw);
}

public static async Task<bool> IsStrictXssInjected(HttpContext httpContext)
{
var raw = await GetStringsFromHttpContext(httpContext);
return IsStrictXssInjected(raw);
}

private static async Task<string> GetStringsFromHttpContext(HttpContext httpContext)
{
// enable buffering so that the request can be read by the model binders next
httpContext.Request.EnableBuffering();

// leaveOpen: true to leave the stream open after disposing,
// so it can be read by the model binders
using var streamReader = new StreamReader(httpContext.Request.Body, Encoding.UTF8, leaveOpen: true);

var raw = await streamReader.ReadToEndAsync();
// rewind the stream for the next middleware
httpContext.Request.Body.Seek(0, SeekOrigin.Begin);
return IsXssInjected(raw);

return raw;
}
}
11 changes: 2 additions & 9 deletions ATI.Services.Common/Xss/XssMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,8 @@

namespace ATI.Services.Common.Xss;

public class XssMiddleware
public class XssMiddleware(RequestDelegate next)
{
private readonly RequestDelegate _next;

public XssMiddleware(RequestDelegate next)
{
_next = next;
}

public async Task InvokeAsync(HttpContext httpContext)
{
if (await XssHelper.IsXssInjected(httpContext))
Expand All @@ -22,7 +15,7 @@ public async Task InvokeAsync(HttpContext httpContext)
}
else
{
await _next(httpContext);
await next(httpContext);
}
}
}
21 changes: 21 additions & 0 deletions ATI.Services.Common/Xss/XssStrictMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;

namespace ATI.Services.Common.Xss;

public class XssStrictMiddleware(RequestDelegate next)
{
public async Task InvokeAsync(HttpContext httpContext)
{
if (await XssHelper.IsStrictXssInjected(httpContext))
{
httpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
await httpContext.Response.WriteAsync("XSS injection detected from middleware.");
}
else
{
await next(httpContext);
}
}
}

0 comments on commit 7df83b7

Please sign in to comment.