C# 如何使用反xss攻击清理web api中的输入数据
下面是我的代码片段 模范班 //Customer.csC# 如何使用反xss攻击清理web api中的输入数据,c#,xss,asp.net-web-api,modelbinder,C#,Xss,Asp.net Web Api,Modelbinder,下面是我的代码片段 模范班 //Customer.cs using CommonLayer; namespace Models { public class Customer { public int Id { get; set; } [MyAntiXss] public string Name { get; set; } } } 我想清理模型类“Name”字段中的值,如下所示 //CutstomModelBinder
using CommonLayer;
namespace Models
{
public class Customer
{
public int Id { get; set; }
[MyAntiXss]
public string Name { get; set; }
}
}
我想清理模型类“Name”字段中的值,如下所示
//CutstomModelBinder.cs
using Microsoft.Security.Application;
using System.ComponentModel;
using System.Linq;
using System.Web.Mvc;
namespace CommonLayer
{
public class CutstomModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.Attributes.OfType<MyAntiXssAttribute>().Any())
{
ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(propertyDescriptor.Name);
string filteredValue = Encoder.HtmlEncode(valueResult.AttemptedValue);
propertyDescriptor.SetValue(bindingContext.Model, filteredValue);
}
else
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
}
我编写了一个控制器类,如下所示
//CustomerController.cs
using Models;
using System.Collections.Generic;
using System.Web.Http;
namespace WebAPI.Controllers
{
public class CustomerController : ApiController
{
public string Post([FromBody]Customer customer)
{
//customer.Name = Encoder.HtmlEncode(customer.Name);
return string.Format("Id = {0}, Name = '{1}'", customer.Id, customer.Name);
}
}
}
当我按如下方式调用上述控制器的类“Post”方法时,它按预期调用了控制器类的“Post”方法。但它没有在我的“CutstomModelBinder”类中调用“BindProperty”方法
//Program.cs
using Models;
using System;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
namespace Client
{
public static class Program
{
public static void Main(params string[] args)
{
bool success = Post();
Console.WriteLine("success = " + success);
Console.Read();
}
private static HttpClient GetHttpClient()
{
HttpClient client = new HttpClient { BaseAddress = new Uri("http://localhost:49295/") };
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return client;
}
private static bool Post()
{
Customer customer = new Customer { Id = 1, Name = "<br>Anivesh</br>" };
HttpContent content = new ObjectContent<Customer>(customer, new JsonMediaTypeFormatter());
HttpClient client = GetHttpClient();
HttpResponseMessage response = client.PostAsync("Customer", content).Result;
client.Dispose();
if (response.IsSuccessStatusCode)
{
string expected = string.Format("Id = {0}, Name = '{1}'", customer.Id, customer.Name);
string result = response.Content.ReadAsAsync<string>().Result;
return expected == result;
}
else
return false;
}
}
}
使用模型;
使用制度;
使用System.Net.Http;
使用System.Net.Http.Formatting;
使用System.Net.Http.Header;
命名空间客户端
{
公共静态类程序
{
公共静态void Main(参数字符串[]args)
{
bool success=Post();
Console.WriteLine(“success=“+success”);
Console.Read();
}
私有静态HttpClient GetHttpClient()
{
HttpClient客户端=新HttpClient{BaseAddress=新Uri(“http://localhost:49295/") };
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(新的MediaTypeWithQualityHeaderValue(“应用程序/json”);
返回客户;
}
私有静态boolpost()
{
客户=新客户{Id=1,Name=“
Anivesh”};
HttpContent=新的ObjectContent(客户,新的JsonMediaTypeFormatter());
HttpClient=GetHttpClient();
HttpResponseMessage response=client.PostAsync(“客户”,内容)。结果;
client.Dispose();
if(响应。IsSuccessStatusCode)
{
字符串应为string.Format(“Id={0},Name='{1}',customer.Id,customer.Name);
字符串结果=response.Content.ReadAsAsync().result;
预期返回==结果;
}
其他的
返回false;
}
}
}
请让我知道使用“DataBinder”的正确方法,以便我可以在控制器中接收调用之前在公共位置清理输入数据。DefaultModelBinder位于System.Web.ModelBinding命名空间中,MVC控制器使用该命名空间 对于WebAPI项目,您需要实现System.Web.Http.ModelBinding.IModelBinder接口 从MSDN站点直接获取的样本模型活页夹如下所示:
public class GeoPointModelBinder : IModelBinder
{
// List of known locations.
private static ConcurrentDictionary<string, GeoPoint> _locations
= new ConcurrentDictionary<string, GeoPoint>(StringComparer.OrdinalIgnoreCase);
static GeoPointModelBinder()
{
_locations["redmond"] = new GeoPoint() { Latitude = 47.67856, Longitude = -122.131 };
_locations["paris"] = new GeoPoint() { Latitude = 48.856930, Longitude = 2.3412 };
_locations["tokyo"] = new GeoPoint() { Latitude = 35.683208, Longitude = 139.80894 };
}
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(GeoPoint))
{
return false;
}
ValueProviderResult val = bindingContext.ValueProvider.GetValue(
bindingContext.ModelName);
if (val == null)
{
return false;
}
string key = val.RawValue as string;
if (key == null)
{
bindingContext.ModelState.AddModelError(
bindingContext.ModelName, "Wrong value type");
return false;
}
GeoPoint result;
if (_locations.TryGetValue(key, out result) || GeoPoint.TryParse(key, out result))
{
bindingContext.Model = result;
return true;
}
bindingContext.ModelState.AddModelError(
bindingContext.ModelName, "Cannot convert value to Location");
return false;
}
}
公共类GeoPointModelBinder:IModelBinder
{
//已知地点的清单。
私有静态ConcurrentDictionary\u位置
=新的ConcurrentDictionary(StringComparer.OrdinalIgnoreCase);
静态地质点模型绑定器()
{
_地点[“redmond”]=新的地质点(){纬度=47.67856,经度=-122.131};
_地点[“巴黎”]=新地理点(){纬度=48.856930,经度=2.3412};
_地点[“东京”]=新地理点(){纬度=35.683208,经度=139.80894};
}
public bool BindModel(HttpActionContext actionContext,ModelBindingContext bindingContext)
{
if(bindingContext.ModelType!=typeof(地质点))
{
返回false;
}
ValueProviderResult val=bindingContext.ValueProvider.GetValue(
bindingContext.ModelName);
if(val==null)
{
返回false;
}
字符串键=val.RawValue作为字符串;
if(key==null)
{
bindingContext.ModelState.AddModelError(
bindingContext.ModelName,“错误的值类型”);
返回false;
}
地质点成果;
if(_locations.TryGetValue(键,输出结果)| | GeoPoint.TryParse(键,输出结果))
{
bindingContext.Model=result;
返回true;
}
bindingContext.ModelState.AddModelError(
bindingContext.ModelName,“无法将值转换为位置”);
返回false;
}
}
可以在此处找到支持此示例的完整帖子:要使用Web API以通用方式清理输入,您可以创建自己的ModelBinder,如我前面的回答中所述,然而,更简单的方法可能是修改现有的JsonMediaTypeFormatter,以便在ReadFromStreamAsync方法中包含所需的santization逻辑 您可以尝试的一种方法如下: 首先,创建一个通用属性,用于装饰DTO中需要清理的属性,即:
[AttributeUsage(AttributeTargets.Property)]
public sealed class SanitizeAttribute : Attribute
{ }
public sealed class SanitizingJsonMediaTypeFormatter : JsonMediaTypeFormatter
{
public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger, CancellationToken cancellationToken)
{
Task<object> resultTask = base.ReadFromStreamAsync(type, readStream, content, formatterLogger, cancellationToken);
var propertiesFlaggedForSanitization = type.GetProperties().Where(e => e.GetCustomAttribute<SanitizeAttribute>() != null).ToList();
if (propertiesFlaggedForSanitization.Any())
{
var result = resultTask.Result;
foreach (var propertyInfo in propertiesFlaggedForSanitization)
{
var raw = (string)propertyInfo.GetValue(result);
if (!string.IsNullOrEmpty(raw))
{
propertyInfo.SetValue(result, AntiXssEncoder.HtmlEncode(raw, true));
}
}
}
return resultTask;
}
}
然后创建负责清理的JsonMediaTypeFormatter的子类型,即:
[AttributeUsage(AttributeTargets.Property)]
public sealed class SanitizeAttribute : Attribute
{ }
public sealed class SanitizingJsonMediaTypeFormatter : JsonMediaTypeFormatter
{
public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger, CancellationToken cancellationToken)
{
Task<object> resultTask = base.ReadFromStreamAsync(type, readStream, content, formatterLogger, cancellationToken);
var propertiesFlaggedForSanitization = type.GetProperties().Where(e => e.GetCustomAttribute<SanitizeAttribute>() != null).ToList();
if (propertiesFlaggedForSanitization.Any())
{
var result = resultTask.Result;
foreach (var propertyInfo in propertiesFlaggedForSanitization)
{
var raw = (string)propertyInfo.GetValue(result);
if (!string.IsNullOrEmpty(raw))
{
propertyInfo.SetValue(result, AntiXssEncoder.HtmlEncode(raw, true));
}
}
}
return resultTask;
}
}
公共密封类清理JsonMediaTypeFormatter:JsonMediaTypeFormatter
{
公共重写任务ReadFromStreamAsync(类型类型、流readStream、HttpContent内容、IFormatterLogger格式记录器、CancellationToken CancellationToken)
{
Task resultTask=base.ReadFromStreamAsync(类型、readStream、内容、formatterLogger、cancellationToken);
var propertiesFlaggedForsanilization=type.GetProperties().Where(e=>e.GetCustomAttribute()!=null).ToList();
if(PropertiesFlaggedForSanization.Any())
{
var result=resultTask.result;
foreach(属性标记中的var propertyInfo用于组织化)
{
var raw=(字符串)propertyInfo.GetValue(结果);
如果(!string.IsNullOrEmpty(原始))
{
propertyInfo.SetValue(结果,AntiXssEncoder.HtmlEncode(原始,真));
}
}
}
返回结果任务;
}
}
这个实现只是检查结果类型是否有任何用Sanitize属性修饰的属性,如果有,则使用内置的System.Web.Security.AntiXss.AntiXssEncoder(.NET 4.5及更高版本)执行sa
[AttributeUsage(AttributeTargets.Property)]
public sealed class SanitizePropertyAttribute : Attribute
{
}
public class SanitizeTextInputFormatter: Microsoft.AspNetCore.Mvc.Formatters.TextInputFormatter
{
private List<String> ExcludeTypes = new List<string>()
{
"System.DateTime",
"System.Int32",
"System.Int64",
"System.Boolean",
"System.Char",
"System.Object"
};
private string CleanInput(string strIn)
{
// Replace invalid characters with empty strings.
try
{
// [<>/] or @"[^\w\.@-]"
return Regex.Replace(strIn, @"[<>/]", "",
RegexOptions.None, TimeSpan.FromSeconds(1.5));
}
// If we timeout when replacing invalid characters,
// we should return Empty.
catch (RegexMatchTimeoutException)
{
return String.Empty;
}
}
public SanitizeTextInputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json"));
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(Encoding.Unicode);
}
private bool ValidateSanitizeProperty(Type type, PropertyInfo PropertyInfo, List<PropertyInfo> orgTypes)
{
var listedProperty = orgTypes.Where(_ => _ == PropertyInfo).FirstOrDefault();
if (PropertyInfo != null && listedProperty == null) orgTypes.Add(PropertyInfo);
if (listedProperty != null) return false;
if (type.FullName == "System.String" && PropertyInfo != null)
{
var sanitizePropertyAttribute = PropertyInfo.GetCustomAttribute<SanitizePropertyAttribute>();
//var sanitizeClassAttribute = PropertyInfo.CustomAttributes.Where(e => e.AttributeType == typeof(SanitizePropertyAttribute)).FirstOrDefault();
return sanitizePropertyAttribute != null;
}
var typeProperties = type.GetProperties().Where(_ => _.PropertyType.IsAnsiClass == true && !ExcludeTypes.Contains(_.PropertyType.FullName)).ToList();
var doSanitizeProperty = false;
typeProperties.ForEach(typeProperty =>
{
if (doSanitizeProperty == false)
doSanitizeProperty = ValidateSanitizeProperty(typeProperty.PropertyType, typeProperty, orgTypes);
});
return doSanitizeProperty;
}
protected override bool CanReadType(Type type)
{
var result = ValidateSanitizeProperty(type, null, new List<PropertyInfo>());
return result;
}
private object SanitizeObject(object obj, Type modelType)
{
if (obj != null)
{
List<PropertyInfo> propertiesFlaggedForSanitization = modelType.GetProperties().Where(e => e.GetCustomAttribute<SanitizePropertyAttribute>() != null).ToList();
if (propertiesFlaggedForSanitization.Any())
{
foreach (var propertyInfo in propertiesFlaggedForSanitization)
{
var raw = (string)propertyInfo.GetValue(obj);
if (!string.IsNullOrEmpty(raw))
{
propertyInfo.SetValue(obj, CleanInput(raw));
//propertyInfo.SetValue(obj, AntiXssEncoder.HtmlEncode(raw, true));
//propertyInfo.SetValue(obj, AntiXssEncoder.UrlEncode(raw));
}
}
}
}
modelType.GetProperties().ToList().Where(_ => _.PropertyType.IsAnsiClass == true && !ExcludeTypes.Contains(_.PropertyType.FullName)).ToList().ForEach(property =>
{
try
{
var nObj = property.GetValue(obj);
if (nObj != null)
{
var sObj = SanitizeObject(nObj, property.PropertyType);
property.SetValue(obj, sObj);
}
}
catch(Exception ex)
{
}
});
return obj;
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (encoding == null)
{
throw new ArgumentNullException(nameof(encoding));
}
using (var streamReader = context.ReaderFactory(context.HttpContext.Request.Body, encoding))
{
string jsonData = await streamReader.ReadToEndAsync();
var nObj = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonData, context.ModelType);
var modelType = context.ModelType;
try
{
var sbj = SanitizeObject(nObj, modelType);
return await InputFormatterResult.SuccessAsync(sbj);
}catch (Exception ex)
{
return await InputFormatterResult.FailureAsync();
}
}
}
}
services.AddMvcCore(options => { options.InputFormatters.Add(new SanitizeTextInputFormatter()); })