Thursday, January 21, 2010

ViewState Replay Attack

This article discusses the viability of ASP.NET ViewState as a trusted source of data for developers. As the following article Scott Mitchell describes: http://scottonwriting.net/sowblog/posts/3747.aspx, ViewState is no place to put data that is sensitive. The rule of thumb would be if this data does not need to come from the client end, it shouldn't be stored/extracted from there.


This is a proof-of-concept example for the ViewState Replay possibility mentioned in Bilal Haidar's book (ASP.NET 3.5 Security, Membership, and Role Management with C# and VB).


We start by looking at the code we'll be attacking:


protected void Page_Load(object sender, EventArgs e)

{

if (!this.IsPostBack)

{
// Set up a gridview
DataTable BillInfo = new DataTable("Products");
BillInfo.Columns.Add("Item");
BillInfo.Columns.Add("Amount");
DataRow tblBill = BillInfo.NewRow();
// Obtain the product data, this would normally be
// a communication to the backend database

if (Request.QueryString["product_id"] == "1")
{
tblBill["Item"] = "CD Player";
tblBill["Amount"] = "12";
}
else if(Request.QueryString["product_id"] == "2")
{
tblBill["Item"] = "Mercedes";
tblBill["Amount"] = "12000";
}
// Add the data to the GridView and bind it
BillInfo.Rows.Add(tblBill);
GridView1.DataSource = BillInfo;
GridView1.DataBind();
GridView1.Visible = true;
}
}

The code above simulates the code that should query the database for details on the product that the user is browsing. The product_id variable is extracted from the QueryString (sent in the URL) and is used to lookup that data.


protected void Button1_Click(object sender, EventArgs e)
{
foreach (GridViewRow dr in GridView1.Rows)
{

// Get the price from the gridview and charge the client
// The code to process the payment must make the same error
// shown here which is to get the product id from the query string
// and the price information from the GridView
Label1.Text = "Amount Charged: " + dr.Cells[1].Text.ToString() +
" For Item: " + Request.QueryString["product_id"];
}
}

The code above simulates the payment activity caused by clicking the button on the page. The developer here makes the mistake of mixing trust boundaries, the data on which item to buy is extracted from the URL, the Data on how much it costs is extracted from the ViewState.

Now for the demo. Lets say we browse to the following URL: http://localhost/ViewStateReplay/Default.aspx?product_id=1


With a proxy (burp suite is used here) in place we hit the Pay button and look at the request going out to the server. Note the ViewState parameter with the base64 encoding of ViewState information. Lets copy the contents of that parameter along with the EventValidation contents for future mischief.


Of course the natural outcome of the above POST command is the following page:



Next lets look at another product, http://localhost/ViewStateReplay/Default.aspx?product_id=2

The new item is evidently much more expensive. Lets have a look through the proxy...



Here we alter the ViewState and EventValidation parameters with the ones saved from the prior request and then Forward our request.

Note how the amount charged is that for a CD player while the Product ID is that of the Mercedes. The moral of the story is as Mitchell says, don't trust the ViewState, and from a strict security mindset it only makes sense to extract sensitive information from the most trusted source you have. Between the backend database and the user controlled input it should be evident which is the more trust-worthy source.

No comments:

Post a Comment