as I ask in detail at Can you spot the security implications/vulnerability of a small change to an ASP.NET MVC 3.0+ Model Binder? one of the versions of the CartModelBinder class (shown below) allows exploitation via MVC ModelBinding Vulnerability (also called OverPosting)
Can you spot which one?
Ideally you should provide your answer/results/proof using UnitTests :)
Version 1: Using DefaultModelBinder and CreateModel
public class CartModelBinder : DefaultModelBinder
{
private const string sessionKey = "Cart";
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
// get the Cart from the session
Cart cart = (Cart)controllerContext.HttpContext.Session[sessionKey];
// create the Cart if there wasn't one in the session data
if (cart == null)
{
cart = new Cart();
controllerContext.HttpContext.Session[sessionKey] = cart;
}
// return the cart
return cart;
}
}
Version 2: Using IModelBinder and BindModel
public class CartModelBinder : IModelBinder
{
private const string sessionKey = "Cart";
public object BindModel(ControllerContext controllerContext,ModelBindingContext bindingContext)
{
// get the Cart from the session
Cart cart = (Cart)controllerContext.HttpContext.Session[sessionKey];
// create the Cart if there wasn't one in the session data
if (cart == null)
{
cart = new Cart();
controllerContext.HttpContext.Session[sessionKey] = cart;
}
// return the cart
return cart;
}
}
Controller example:
public RedirectToRouteResult AddToCart(Cart cart, int productId, string returnUrl)
{
Product product = repository.Products
.FirstOrDefault(p => p.ProductID == productId);
if (product != null)
{
cart.AddItem(product, 1);
}
return RedirectToAction("Index", new { returnUrl });
}
Your current design can be easily misused like you suggested. A better solution would be to get the cart initially and use that instance.
public class CartController : Controller
{
private IProductRepository repository;
private IOrderProcessor orderProcessor;
private cart;
public CartController(IProductRepository repo, IOrderProcessor proc)
{
repository = repo;
orderProcessor = proc;
cart = Session["Cart"]; // or Cart.Current
}
public RedirectToRouteResult AddToCart(int productId, string returnUrl)
{
Product product = repository.Products
.FirstOrDefault(p => p.ProductID == productId);
if (product != null)
{
cart.AddItem(product, 1);
}
return RedirectToAction("Index", new { returnUrl });
}
}
Related
I have a MVC page that reads data from db. Also on the page I have some image links to /MyController/Photo which also makes some reads on the db. Now the photos are being fetched by the browser "simultaneously" so I notice that some photos don't show up and I also logged some errors:
System.NullReferenceException Object reference not set to an instance of an object.
at System.Data.SqlClient.SqlConnection.TryOpenInner(TaskCompletionSource`1 retry)
at System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry)
at System.Data.SqlClient.SqlConnection.Open()
at System.Data.EntityClient.EntityConnection.OpenStoreConnectionIf(Boolean openCondition, DbConnection storeConnectionToOpen, DbConnection originalConnection, String exceptionCode, String attemptedOperation, Boolean& closeStoreConnectionOnFailure)
at System.Data.EntityClient.EntityConnection.Open()
......
Here is my code:
In the view:
<img src="#Url.Action("Photo", "Profile", new { type = "listing", uID = f.uID })" />
In the controller action:
public ActionResult Photo(string type, int uID)
{
User u = null;
if (uID == 0)
u = Repository.repository.GetUserByName(User.Identity.Name);
else
u = Repository.repository.GetUserByID(uID);
if (u != null)
{
...
}
}
My question how can I synchronize the entity framework access to the context to make sure I don't get these errors?
I do this with IoC/Dependancy Injection and a singleton DatabaseFactory that gets passed into each repository. The DatabaseFactory class holds the only DbContext for the site, this way there is only one and don't get into any weird issues. And I also inject my repositories into my controllers.. I'm just lazy like that.
DatabaseFactory
public class DatabaseFactory : Disposable, IDatabaseFactory
{
private CragDbContext _dataContext;
private string _connectionString;
public string ConnectionString
{
get { return _connectionString; }
set { _connectionString = value; }
}
public DatabaseFactory(string connectionString)
{
if (connectionString == null)
throw new ArgumentNullException("connectionString");
_connectionString = connectionString;
}
public CragDbContext Get()
{
if (string.IsNullOrEmpty(_connectionString))
return _dataContext ?? (_dataContext = new CragDbContext());
return _dataContext ?? (_dataContext = new CragDbContext(_connectionString));
}
protected override void DisposeCore()
{
if (_dataContext != null)
_dataContext.Dispose();
}
}
BaseRepository (that all my respositories inherit from)
public class BaseRepository<T> : IRepository<T> where T : BaseEntity
{
protected CragDbContext DbContext;
protected readonly IDbSet<T> DbSet;
protected IDatabaseFactory DatabaseFactory { get; private set; }
protected CragDbContext Context
{
get { return DbContext ?? (DbContext = DatabaseFactory.Get()); }
}
#region Ctor
public BaseRepository(IDatabaseFactory databaseFactory)
{
if (databaseFactory == null)
throw new ArgumentNullException("databaseFactory");
DatabaseFactory = databaseFactory;
DbSet = Context.Set<T>();
}
#endregion
public virtual void Add(T entity)
{
DbSet.Add(entity);
}
.... etc
}
I actually have a UnitOfWork class in the middle, but you can do it without that. Hope this helps.
I’ve been trying to figure a way to have a model-binding go on with a model with a constructor with arguments.
the action:
[HttpPost]
public ActionResult Create(Company company, HttpPostedFileBase logo)
{
company.LogoFileName = SaveCompanyLogoImage(logo);
var newCompany = _companyProvider.Create(company);
return View("Index",newCompany);
}
and the model
public Company(CustomProfile customProfile)
{
DateCreated = DateTime.Now;
CustomProfile = customProfile;
}
I've done my research and seems I need to mess around with my ninjectControllerfactory:
public class NinjectControllerFactory : DefaultControllerFactory
{
private readonly IKernel ninjectKernel;
public NinjectControllerFactory()
{
ninjectKernel = new StandardKernel();
AddBindings();
}
protected override IController GetControllerInstance(RequestContext requestContext,
Type controllerType)
{
return controllerType == null
? null
: (IController) ninjectKernel.Get(controllerType);
}
private void AddBindings()
{
ninjectKernel.Bind<IAuthProvider>().To<FormsAuthProvider>();
ninjectKernel.Bind<IMembershipProvider>().To<MembershipProvider>();
ninjectKernel.Bind<ICustomProfileProvider>().To<CustomProfileProvider>();
ninjectKernel.Bind<ICompanyProvider>().To<CompanyProvider>();
}
}
I also feel I need to modify my model binder but I'm not clear on the way forward:
public class CustomProfileModelBinder : IModelBinder
{
private const string sessionKey = "CustomProfile";
#region IModelBinder Members
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
// get the Cart from the session
var customProfile = (CustomProfile) controllerContext.HttpContext.Session[sessionKey];
// create the Cart if there wasn't one in the session data
if (customProfile == null)
{
customProfile = new CustomProfile("default name");
controllerContext.HttpContext.Session[sessionKey] = customProfile;
}
// return the cart
return customProfile;
}
#endregion
}
Hope this explains my issue, I'm sorry if its a rather long winded question!
Thanks for any assistance
In this case it seems that the parameter you need to create (CustomProfile) must be taken from the session. You could then use a specific model binder for the Company model that derives from the default model binder, changing only the way it creates an instance of the Company class (it will then populate the properties in the same way as the default one):
public class CompanyModelBinder: DefaultModelBinder
{
private const string sessionKey = "CustomProfile";
protected override object CreateModel(ControllerContext controllerContext,
ModelBindingContext bindingContext,
Type modelType)
{
if(modelType == typeOf(Company))
{
var customProfile = (CustomProfile) controllerContext.HttpContext.Session[sessionKey];
// create the Cart if there wasn't one in the session data
if (customProfile == null)
{
customProfile = new CustomProfile("default name");
controllerContext.HttpContext.Session[sessionKey] = customProfile;
}
return new Company(customProfile);
}
else
{
//just in case this gets registered for any other type
return base.CreateModel(controllerContext, bindingContext, modelType)
}
}
}
You will register this binder only for the Company type by adding this to the global.asax Application_Start method:
ModelBinders.Binders.Add(typeOf(Company), CompanyModelBinder);
Another option could be to create a dependency-aware model binder using the Ninject dependencies by inheriting from the DefaultModelBinder (As you are using Ninject, it knows how to build instances of concrete types without the need of registering them).
However you would need to configure a custom method that builds the CustomProfile in Ninject, which I believe you could do using the ToMethod().
For this you would extract you would extract your configuration of your Ninject kernel outside the controller factory:
public static class NinjectBootStrapper{
public static IKernel GetKernel()
{
IKernel ninjectKernel = new StandardKernel();
AddBindings(ninjectKernel);
}
private void AddBindings(IKernel ninjectKernel)
{
ninjectKernel.Bind<IAuthProvider>().To<FormsAuthProvider>();
ninjectKernel.Bind<IMembershipProvider>().To<MembershipProvider>();
ninjectKernel.Bind<ICustomProfileProvider>().To<CustomProfileProvider>();
ninjectKernel.Bind<ICompanyProvider>().To<CompanyProvider>();
ninjectKernel.Bind<CustomProfile>().ToMethod(context => /*try to get here the current session and the custom profile, or build a new instance */ );
}
}
public class NinjectControllerFactory : DefaultControllerFactory
{
private readonly IKernel ninjectKernel;
public NinjectControllerFactory(IKernel kernel)
{
ninjectKernel = kernel;
}
protected override IController GetControllerInstance(RequestContext requestContext,
Type controllerType)
{
return controllerType == null
? null
: (IController) ninjectKernel.Get(controllerType);
}
}
In that case you would create this model binder:
public class NinjectModelBinder: DefaultModelBinder
{
private readonly IKernel ninjectKernel;
public NinjectModelBinder(IKernel kernel)
{
ninjectKernel = kernel;
}
protected override object CreateModel(ControllerContext controllerContext,
ModelBindingContext bindingContext,
Type modelType)
{
return ninjectKernel.Get(modelType) ?? base.CreateModel(controllerContext, bindingContext, modelType)
}
}
And you would update the global.asax as:
IKernel kernel = NinjectBootStrapper.GetKernel();
ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory(kernel));
ModelBinders.Binders.DefaultBinder = new NinjectModelBinder(kernel);
I have a custom modelbinder, its check the authentication cookie and return the value.
public class UserDataModelBinder<T> : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (controllerContext.RequestContext.HttpContext.Request.IsAuthenticated)
{
var cookie =
controllerContext.RequestContext.HttpContext.Request.Cookies[FormsAuthentication.FormsCookieName];
if (cookie == null)
return null;
var decrypted = FormsAuthentication.Decrypt(cookie.Value);
if (!string.IsNullOrWhiteSpace(decrypted.UserData))
return JsonSerializer.DeserializeFromString<T>(decrypted.UserData);
}
return null;
}
}
if I need to use it, I just need to pass it to the action. everything works.
public ActionResult Index(UserData userData)
{
AccountLoginWidgetVM model = new AccountLoginWidgetVM();
if (null != userData)
model.UserData = userData;
return View(userData);
}
However, I want to use it in my master page, because once user login, i want to display their info on the top on every page. I tried a few things, coudln't get it work
#Html.RenderPartial("LoginPartial", ???model here??)
We did it as follows:
Defined separate viewmodel for masterpages.
public class MasterPageViewModel
{
public Guid CurrentUserId { get; set; }
public string CurrentUserFullName { get; set; }
}
Added injection filter and filter provider.
public class MasterPageViewModelInjectorFilterProvider: IFilterProvider
{
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
return new [] {new Filter(new MasterPageViewModelInjectorFilter(), FilterScope.Action, null), };
}
private class MasterPageViewModelInjectorFilter: IResultFilter
{
public void OnResultExecuting(ResultExecutingContext filterContext)
{
var viewResult = filterContext.Result as ViewResult;
if (viewResult == null)
return;
if (viewResult.ViewBag.MasterPageViewModel != null)
return;
//setup model whichever way you want
var viewModel = new MasterPageViewModel();
//inject model into ViewBag
viewResult.ViewBag.MasterPageViewModel = viewModel;
}
public void OnResultExecuted(ResultExecutedContext filterContext)
{
}
}
}
Configure filter provider:
//in Application_Start
FilterProviders.Providers.Add(new MasterPageViewModelInjectorFilterProvider());
Use in master:
ViewBag.MasterPageViewModel
This way you have fine uncoupled architecture. Of course you can combine it with Dependency Injection (we do, but I left it out for clarity) and configure your action filter for every action whichever way you want.
In this case you can use ViewBag.
public ActionResult Index(UserData userData)
{
AccountLoginWidgetVM model = new AccountLoginWidgetVM();
if (null != userData)
model.UserData = userData;
ViewBag.UserData = userData;
return View(userData);
}
#Html.RenderPartial("LoginPartial", ViewBag.UserData)
You have to make sure that userData is not null. If it'll be null the passed model will be default model of the view.
It seems that when MVC validates a Model that it runs through the DataAnnotation attributes (like required, or range) first and if any of those fail it skips running the Validate method on my IValidatableObject model.
Is there a way to have MVC go ahead and run that method even if the other validation fails?
You can manually call Validate() by passing in a new instance of ValidationContext, like so:
[HttpPost]
public ActionResult Create(Model model) {
if (!ModelState.IsValid) {
var errors = model.Validate(new ValidationContext(model, null, null));
foreach (var error in errors)
foreach (var memberName in error.MemberNames)
ModelState.AddModelError(memberName, error.ErrorMessage);
return View(post);
}
}
A caveat of this approach is that in instances where there are no property-level (DataAnnotation) errors, the validation will be run twice. To avoid that, you could add a property to your model, say a boolean Validated, which you set to true in your Validate() method once it runs and then check before manually calling the method in your controller.
So in your controller:
if (!ModelState.IsValid) {
if (!model.Validated) {
var validationResults = model.Validate(new ValidationContext(model, null, null));
foreach (var error in validationResults)
foreach (var memberName in error.MemberNames)
ModelState.AddModelError(memberName, error.ErrorMessage);
}
return View(post);
}
And in your model:
public bool Validated { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
// perform validation
Validated = true;
}
There's a way to do it without requiring boilerplate code at the top of each controller action.
You'll need to replace the default model binder with one of your own:
protected void Application_Start()
{
// ...
ModelBinderProviders.BinderProviders.Clear();
ModelBinderProviders.BinderProviders.Add(new CustomModelBinderProvider());
// ...
}
Your model binder provider looks like this:
public class CustomModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(Type modelType)
{
return new CustomModelBinder();
}
}
Now create a custom model binder that actually forces the validation. This is where the heavy lifting's done:
public class CustomModelBinder : DefaultModelBinder
{
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
base.OnModelUpdated(controllerContext, bindingContext);
ForceModelValidation(bindingContext);
}
private static void ForceModelValidation(ModelBindingContext bindingContext)
{
var model = bindingContext.Model as IValidatableObject;
if (model == null) return;
var modelState = bindingContext.ModelState;
var errors = model.Validate(new ValidationContext(model, null, null));
foreach (var error in errors)
{
foreach (var memberName in error.MemberNames)
{
// Only add errors that haven't already been added.
// (This can happen if the model's Validate(...) method is called more than once, which will happen when
// there are no property-level validation failures.)
var memberNameClone = memberName;
var idx = modelState.Keys.IndexOf(k => k == memberNameClone);
if (idx < 0) continue;
if (modelState.Values.ToArray()[idx].Errors.Any()) continue;
modelState.AddModelError(memberName, error.ErrorMessage);
}
}
}
}
You'll need an IndexOf extension method, too. This is a cheap implementation but it'll work:
public static int IndexOf<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
if (source == null) throw new ArgumentNullException("source");
if (predicate == null) throw new ArgumentNullException("predicate");
var i = 0;
foreach (var item in source)
{
if (predicate(item)) return i;
i++;
}
return -1;
}
I would like to create model binding functionality so a user can enter ',' '.' etc for currency values which bind to a double value of my ViewModel.
I was able to do this in MVC 1.0 by creating a custom model binder, however since upgrading to MVC 2.0 this functionality no longer works.
Does anyone have any ideas or better solutions for performing this functionality? A better solution would be to use some data annotation or custom attribute.
public class MyViewModel
{
public double MyCurrencyValue { get; set; }
}
A preferred solution would be something like this...
public class MyViewModel
{
[CurrencyAttribute]
public double MyCurrencyValue { get; set; }
}
Below is my solution for model binding in MVC 1.0.
public class MyCustomModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
object result = null;
ValueProviderResult valueResult;
bindingContext.ValueProvider.TryGetValue(bindingContext.ModelName, out valueResult);
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueResult);
if (bindingContext.ModelType == typeof(double))
{
string modelName = bindingContext.ModelName;
string attemptedValue = bindingContext.ValueProvider[modelName].AttemptedValue;
string wantedSeperator = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator;
string alternateSeperator = (wantedSeperator == "," ? "." : ",");
try
{
result = double.Parse(attemptedValue, NumberStyles.Any);
}
catch (FormatException e)
{
bindingContext.ModelState.AddModelError(modelName, e);
}
}
else
{
result = base.BindModel(controllerContext, bindingContext);
}
return result;
}
}
You might try something among the lines:
// Just a marker attribute
public class CurrencyAttribute : Attribute
{
}
public class MyViewModel
{
[Currency]
public double MyCurrencyValue { get; set; }
}
public class CurrencyBinder : DefaultModelBinder
{
protected override object GetPropertyValue(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor,
IModelBinder propertyBinder)
{
var currencyAttribute = propertyDescriptor.Attributes[typeof(CurrencyAttribute)];
// Check if the property has the marker attribute
if (currencyAttribute != null)
{
// TODO: improve this to handle prefixes:
var attemptedValue = bindingContext.ValueProvider
.GetValue(propertyDescriptor.Name).AttemptedValue;
return SomeMagicMethodThatParsesTheAttemptedValue(attemtedValue);
}
return base.GetPropertyValue(
controllerContext,
bindingContext, propertyDescriptor,
propertyBinder
);
}
}
public class HomeController: Controller
{
[HttpPost]
public ActionResult Index([ModelBinder(typeof(CurrencyBinder))] MyViewModel model)
{
return View();
}
}
UPDATE:
Here's an improvement of the binder (see TODO section in previous code):
if (!string.IsNullOrEmpty(bindingContext.ModelName))
{
var attemptedValue = bindingContext.ValueProvider
.GetValue(bindingContext.ModelName).AttemptedValue;
return SomeMagicMethodThatParsesTheAttemptedValue(attemtedValue);
}
In order to handle collections you will need to register the binder in Application_Start as you will no longer be able to decorate the list with the ModelBinderAttribute:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
ModelBinders.Binders.Add(typeof(MyViewModel), new CurrencyBinder());
}
And then your action could look like this:
[HttpPost]
public ActionResult Index(IList<MyViewModel> model)
{
return View();
}
Summarizing the important part:
bindingContext.ValueProvider.GetValue(bindingContext.ModelName)
A further improvement step of this binder would be to handle validation (AddModelError/SetModelValue)