Monday, February 1, 2010
customerrors tag and Information Leakage
The main two attributes for <customerrors> are Mode and defaultRedirect.
Mode can be set to On, Off or RemoteOnly.
defaultRedirect should be set to the name of your error page ( or you could just leave the parameter out; unset).
For example you could use the parameter like so:
<customErrors mode=“RemoteOnly” DefaultRedirect=“error.htm”>
The following table is a result of my having tested the behavior of ASP.NET (3.5) with different setting combinations for these two parameters.
From the results above we can deduce that a definite no no is to set Mode to Off, Ever!
RemoteOnly seems to be the most useful since it will allow developers to troubleshoot without leaking information to the user. Setting an error page is also recommended since it makes for a better looking error page than the one ASP.Net has to offer.
Tuesday, January 26, 2010
Whitelisting Vs. Blacklisting
// Filter out what you think are harmful
string comments = SanitizeData(this.txtComments.Text);
// Process the input fields
comments = System.Web.HttpUtility.HtmlEncode(comments);
}
private string SanitizeData(string input)
{
Regex badChars =
new Regex(@”(\n?<script[^>]*?>.*?</script[^>]*?>)(\n?<script[^>]*?/>)”);
string goodChars = badChars.Replace(input, “”);
return goodChars;
}
As Haidar acknowledges later on, blacklisting is of limited capability.
The Code above unfortunately is easily bypassed. Consider the following input:
<b>hello<s<script></script>cript>alert('hi')</script>;</b>
Of course RequestValidation should nullify the above string. However, in some cases, RequestValidation is turned off (to allow legal tags like <b> and <i>). In those cases whitelisting might be the best route to follow.
For example we could first convert all angle brackets < to something safe like a parenthesis (. Then we can go back and convert only legal sequences to tags; like for instance (i> to <i>.
private string SanitizeData(string input)
{
new Regex(@"<");
string goodChars = badChars.Replace(input, "(");
badChars = new Regex(@"\(i");
goodChars = badChars.Replace(goodChars, "<i");
return goodChars;
}
Client/Server Validation in ASP.NET
Server-side validation is not automatically enabled by simply having a control validator in the aspx code such as the following:
<asp:TextBox ID="TextBox1" runat="server">
<asp:RegularExpressionValidator runat=server ErrorMessage="Bad input"
ControlToValidate="TextBox1" ValidationExpression="[0-9]" Display=Dynamic />
The code-behind implementing the PostBack for the Button_click event should check the Page.IsValid Property, otherwise no server-side validation occurs.
protected void Button1_Click(object sender, EventArgs e)
{
Page.Validate();
if(Page.IsValid){
Label1.Text = TextBox1.Text;
}
}
The reason this is noted is that if you do not check the IsValid property, you will only have client-side validation. To prove this to myself I did the following:
- Set up IE to go through my favorite Proxy (burpsuite).
- Run with the above code and on the first response from the server modify the Javascript section to remove the client-side validation. (just change the regex expression to be .*)
- Submit the request with data that would have been invalid (the regex [0-9] should have only accepted numbers, to test it we put "abc"). You should get a nice Validation Error message. This means our form field validator was processed on the server-end.
Now,
Repeat the above sequence but comment out the check on the IsValid property, you should find that your request follows through.
Thursday, January 21, 2010
ViewState Replay Attack
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.