Today I was pointed to a recent email to the BUGTRAQ mailing list (a mailing frequented by security experts) concerning replay attacks and ASP.NET view state. The email was posted by Michal Zalewski, titled: ASP.NET __VIEWSTATE crypto validation prone to replay attacks. As I describe in my article Understanding ASP.NET ViewState, The contents stashed in the hidden __VIEWSTATE form field are base-64 encoded and (by default) cryptographically signed to prevent view state tampering by a malicious client and ensure validity of the view state's contents across postback.
The signature occurs by salting the view state with both the HashCode for the virtual directory in which the page exists along the page's HashCode. This signature prevents a nefarious user from modifying the view state and posting it back. If such a situation unfolds, the ASP.NET runtime will not that the reposted view state's signature does not match up and throw an exception. Similarly, this mechanism prevents against replay attacks where the view state is taken, without modification, and posted back to a different page. Since the view state is signed by the page's HashCode, the signature wouldn't match up.
What Michal reasoned out, though, was that the view state was vulnerable to replay attacks to the same page. His example is a common one: you have an eCommerce site where the data loaded on the page is dependent upon querystring parameters, such as GetProduct.aspx?ID=productID. If things like price are stored in the view state (such as in a shopping cart DataGrid or put there programmatically), a frugal visitor could visit GetProducts.aspx?ID=cheapProduct, save the view state in the hidden form field, then repost that to GetProducts.aspx?ID=expensiveProduct. The end effect might be that the user could order the expensive product for the price of the cheap one. (Of course this would only be possible if the eCommerce site relied solely on the view state for pricing information when computing the final bill.)
The point is, don't trust view state (or the data that is put there by Web controls, such as the DataGrid). That is, if you have important information, such as pricing data, it's OK if it is placed in view state (such as in a row in a DataGrid), but don't grab the pricing data to charge by just poking around the view state (as in programmatically accessing the contents of a DataGrid). Instead, if you need to get pricing information (or any other important bit of information) for the final order processing, it is imperative that you requery the database.
Another point Michal made in his email was that view state can be used in a one-click attacks. That is, a nefarious hacker could pass along one view state through a third-party, the end effect being that the third-party sees a page with a view state they did not “create” themselves. Michal points out that to avoid this there needs to be someway to associate the view state with information particular to a user. This is possible in ASP.NET 1.1 via the ViewStateUserKey property, which is an additional, optional parameter that is used as a salt in the signature. You can assign this property to some unique user value to prevent the attack; see Improving Web Application Security: Threats and Countermeasures for more information.
The last point of Michal's email to BUGTRAQ was concerned unsigned view states. If you opt not to sign the view state (that is, if you set EnableViewStateMac to false), ASP.NET naively attempts to decode the view state passed in. This can be used as a denial of service vector by dumping in a very large hidden view state field, which can severely hamper the performance of the web server. (Of course, the web server is likely configured to reject any incoming request that's larger than 4 MB in size, but this is still an easy point of attack since crafting and posting a large view state is a trivial task from the attacker's point of view.) To protect yourself from this don't set EnableViewStateMac to false, if possible. There have been some claims that you need to set EnableViewStateMac to false in order to have Server.Transfer work properly. There is a workaround, as discussed in this KB article. It is my understanding, however, that you'll need to set EnableViewStateMac to false if you are using a web farm without server affinity.
I followed up Michal's email to BUGTRAQ with an offlist discussion. His main point, as I understand it, is that he in concerned because developers might think that view state is a safe place to put sensitive data or data that need not be revalidated due to the signing/encrypting options view state provides. However, as Michal's email shows, view state - even signed and/or encrypted view state - is not safe from replay attacks on sites where a single page's differs merely by the querystring. The short of it - don't trust that your view state information has not been modified and always requery the database before using information whose correctness is important.