12

以下は私のコードのスニペットです

モデルクラス

// Customer.cs

using CommonLayer;

namespace Models
{
    public class Customer
    {
        public int Id { get; set; }

        [MyAntiXss]
        public string Name { get; set; }
    }
}

以下のように、モデル クラスの「名前」フィールドの値をサニタイズしたい

// 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);
            }
        }
    }

以下のように、「DefaultBinder」を「CutstomModelBinder」に変更しました

// Global.asax.cs

using CommonLayer;
using System.Web.Http;
using System.Web;
using System.Web.Mvc;

namespace WebAPI
{
    public class WebApiApplication : HttpApplication
    {
        protected void Application_Start()
        {
            GlobalConfiguration.Configure(WebApiConfig.Register);
            ModelBinders.Binders.DefaultBinder = new CutstomModelBinder();
        }
    }
}

以下のようにコントローラクラスを書きました

// 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;
        }
    }
}

コントローラーで呼び出しを受け取る前に、共通の場所で入力データをサニタイズできるように、「DataBinders」の正しい使用方法を教えてください。

4

3 に答える 3

10

Web API を使用して一般的な方法で入力をサニタイズするには、以前の回答で説明したように独自の ModelBinder を作成できますが、既存の JsonMediaTypeFormatter を変更して、必要なサニタイズ ロジックをReadFromStreamAsyncメソッド内に含める方法がより簡単な方法です。

試すことができる1つのアプローチは次のとおりです。

最初に、サニタイズが必要な DTO 内のプロパティを装飾するために使用される汎用属性を作成します。

 [AttributeUsage(AttributeTargets.Property)]
 public sealed class SanitizeAttribute : Attribute
 { }

次に、サニタイズを処理する JsonMediaTypeFormatter のサブタイプを作成します。

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;
    }
}

この実装は、結果の Type に Sanitize 属性で修飾されたプロパティがあるかどうかを確認するだけであり、存在する場合は、組み込みの System.Web.Security.AntiXss.AntiXssEncoder (.NET 4.5 以降) を使用してサニタイズを実行します。 .

このクラスを最適化して、型とプロパティの情報をキャッシュし、逆シリアル化のたびに重いリフレクション コールを実行しないようにすることをお勧めします。

プロセスの最後のステップは、組み込みの JSON メディア タイプ フォーマッタを、WebAPI スタートアップ コード内で独自のものに置き換えることです。

var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
config.Formatters.Remove(jsonFormatter);
config.Formatters.Add(new SanitizingJsonMediaTypeFormatter());

Sanitize 属性で装飾されたプロパティを持つ DTO は、DTO がコントローラーに到達する前に適切にエンコードされるようになりました。

于 2016-04-18T16:58:43.910 に答える
4

.NetCore Web API 2. InputFormatter を使用して、受信 JSON のすべてのプロパティを (任意の深さで) 再帰的にサニタイズします。

[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();
            }
        }
    }
}

そして、startup.cs クラスの public void ConfigureServices(IServiceCollection services) 関数で、次のように宣言します。

services.AddMvcCore(options => { options.InputFormatters.Add(new SanitizeTextInputFormatter()); })
于 2019-02-19T23:02:50.353 に答える
0

DefaultModelBinder は、MVC コントローラーによって使用される System.Web.ModelBinding名前空間にあります。

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;
    }
}

このサンプルをバックアップする完全な投稿は、次の場所にあります: MSDN モデル バインディング

于 2016-04-18T14:59:15.533 に答える