Säkerhetsproblem med modellbindning i MVC

Det finns ett enkelt sätt med MVC att koppla ihop data från formulär till en datamodell. Det är användbart vid exempelvis formulär för att spara ner data. Det är snabbt och smidigt, men som så ofta när något är snabbt och smidigt är det väldigt generellt och öppnar därmed för säkerhetsproblem.

Som exempel kan vi ta en sida för att redigera produkter, där endast produktnamnet skall vara redigerbart.
Datamodellen ser ut på följande sätt

public class Product
{
    public int ProductId {get; set;}
    public string ProductName {get; set;}
    public int Price {get; set;}
}

Vi kan då skapa ett formulär på exempelvis följande sätt

<% using (Html.BeginForm()) {%>
    <%=Html.Hidden("ProductId", Model.ProductId) %><br />
    Namn: <%=Html.TextBox("ProductName", Model.ProductName) %><br />
    Pris: <%=Model.Price %><br />
    <input type="submit" value="Save" />
<% } %>

Det finns nu lite olika sätt att ta emot formulärdata exempelvis att specifikt ange vilka argument man förväntar sig, i vårt fall ProductId och ProductName. MVC kommer därför att försöka koppla ihop formulärdata till rätt variabel i Edit-metoden.

public ActionResult Edit(int productId, string productName)

Ett annat alternativ är att man tar emot hela formulärkollektionen

public ActionResult Edit(FormCollection form)

Det vi skall gå igenom nu är när man låter MVC koppla ihop de properties som finns i Product-klassen och de som finns i formulärdatat. Detta gör MVC via namnen.

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Product product)
{
        //ProductController.Update(p);
        return RedirectToAction("Index");
}

Notera att Edit-metoden tar emot en Product och att det är en POST-metod. Därför skall metoden endast anropas när det kommer en POST-request.

Ovanstående kod kan då se ut på följande sätt när man kör debuggning efter att ha postat formuläret. Notera att Price är 0 eftersom formuläret inte innehåller något prisfält.

url: http://www.example.local/products/edit/5
Pris är 0

Om vi nu är en elak hacker som inte nöjer sig med att ändra produktnamnet, utan vill ändra exempelvis priset så kan man göra på två sätt. Det första alternativet är att direkt i url:en specificera ett querystringvärde. Exempelvis kan vi sätta price till 20.

url: http://www.example.local/products/edit/5?price=20
Pris är 20

Notera att product nu har fått priset som vi valde och om man har byggt en generell uppdateringsmetod som uppdaterar alla properties som skiljer sig från defaultvärde och/eller är null så har den elaka hackern lyckats ändra priset. Ovanstående fungerar eftersom MVC försöker ta värden från både POST och GET när den matchar och bygger propertiesvärden.

Det andra alternativet är att via exempelvis tamper data lägga till ett formulärfält där man matar in det nya priset.

Det kan se ut på följande sätt

url: http://www.example.local/products/edit/5
Tamper data
Priset är 25

För att skydda sig mot detta men ändå använda modellbindning med typade objekt kan man sätta attribut som säger vilka properties som skall bindas, alternativt vilka properties som inte skall bindas.

Det kan då se ut på följande sätt om man specifikt anger vilka properties MVC skall försöka binda.

public ActionResult Edit([Bind(Include="ProductId,ProductName")]Product product)
{
    //ProductController.Update(p);
    return RedirectToAction("Index");
}

Alternativt såhär om man väljer att ange vilka man vill exkludera.

public ActionResult Edit([Bind(Exclude="Price")]Product product)
{
    //ProductController.Update(p);
    return RedirectToAction("Index");
}

Givetvis är det mer säkert att använda sig av inkluderingsalternativet.

1 kommentar

  1. […] Men detta räcker inte för att skydda mot möjligheten för inloggade användare att skicka in fler fält än vad som är tillåtet. Den svaga länken ligger nu i att vi utnyttjar ASP.NET MVC-ramverkets inbyggda standard-ModelBinder. Patrik Corneliusson har skrivit en utförlig bloggpost om denna typ av problematik här. […]

RSS feed for comments on this post

Kommentarer inaktiverade.

%d bloggare gillar detta: