Shutting up Invalid ViewState from SPAM bots
Somehow, sometime ago a spambot got into my West Wind Web Store and picked up a couple of pages and decided to just POST random (and randy <s>) SPAM to this URL to the tune of a few hundred POSTs a day by now. The funny thing about this is that the URL is just a product page that accepts only a single input quantity. For example:
http://www.west-wind.com/wwstore/item.aspx?sku=WWHELP40
There's absolutely no redeeming value to keep spamming this URL and posting data to it that goes absolutely nowhere.
Now the good news is that ASP.NET - because of Viewstate and Request Validation - will fail all of these requests and nothing is really lost other than the wasted bandwidth and processor time.
However, in all of my Web applications I have a global error handler hooked up to HttpApplication.Error that handles error logging, error notification and display of error messages in a centralized location. I have a fair amount of stuff in this app level handler code. Here's what it looks like to give you an idea:
protected void HandleError() { Exception ex = Server.GetLastError(); // *** Check for POST spam and write out IP blocking list this.IsSpam(ex); WebErrorHandler Handler = null; // *** Try to log the inner Exception since that's what // *** contains the 'real' error. if (ex.InnerException != null) Handler = new WebErrorHandler(ex.InnerException); else // *** If no inner exception then we have access errors // *** or handler errors - log main exception Handler = new WebErrorHandler(ex); // *** Log the error if specified if (App.Configuration.LogErrors != ErrorLogTypes.None) { if (App.Configuration.LogErrors == ErrorLogTypes.XmlFile) // XML Logging { Handler.LogFileName = App.Configuration.XmlErrorLogFile; Handler.LogErrorToXml(true); } else if (App.Configuration.LogErrors == ErrorLogTypes.SqlServer) // Sql Server with WebRequestLog Handler.LogErrorToSql(App.Configuration.ConnectionString); } // *** Retrieve the detailed String information of the Error string ErrorDetail = Handler.ToString(); // *** Optionally email it to the Admin contacts set up in WebStoreConfig if (App.Configuration.SendAdminEmail) WebUtils.SendAdminEmail(App.Configuration.ApplicationName + "Error: " + Request.RawUrl, ErrorDetail); // *** Debug modes handle different error display mechanisms // *** Default - default ASP.Net - depends on web.config settings // *** Developer - display a generic application error message with no error info // *** User - display a detailed error message with full error info independent of web.config setting if (App.Configuration.DebugMode == DebugModes.DeveloperErrorMessage) { Server.ClearError(); MessageDisplay.DisplayMessage("Application Error", "<pre style='font: normal 8pt Arial'>" + ErrorDetail + "</pre>"); return; } else if (App.Configuration.DebugMode == DebugModes.ApplicationErrorMessage) { string StockMessage = "The Server Administrator has been notified and the error logged.<p>" + "Please continue on by either clicking the back button or by returning to the home page.<p>" + "<center><b><a href='" + App.Configuration.StoreBaseUrl + "'>West Wind Web Store Home Page</a></b></center>"; // Handle some stock errors that may require special error pages HttpException HEx = ex as HttpException; if (HEx != null) { int HttpCode = HEx.GetHttpCode(); Server.ClearError(); if (HttpCode == 404) // Page Not Found { Response.StatusCode = 404; MessageDisplay.DisplayMessage("Page not found", "You've accessed an invalid page in this Web server. " + StockMessage); return; } if (HttpCode == 401) // Access Denied { Response.StatusCode = 401; MessageDisplay.DisplayMessage("Access Denied", "You've accessed a resource that requires a valid login. " + StockMessage); return; } } Server.ClearError(); Response.StatusCode = 500; MessageDisplay.DisplayMessage("Application Error", "We're sorry, but an unhandled error occurred on the server. " + StockMessage); return; } return; }
This method is just called off the Application_Error event and it does basically all the work to log, notify and handle error display. Global error handling is really nice because with this code the entire application is covered and the code is nearly completely generic so it can be dropped into any app with the error handling component available. It's not totally generic though - there are a couple of app specific things - bascically the configuration settings (from App.Configuration) and a few custom things like checking for IsSpam to write out IP Addresses for later exclusion (more on that in a minute). Close enough though and it's usually one of the first things I do in an application. It's invaluable for getting useful information on your running application and I highly recommend some sort of error tracking with every Web app however trivial.
Error logging and notification is very useful but unfortunately, in the case of the SPAM bots firing a few hundred messages each day it also causes several hundred error messages and more importantly several hundred email messages of crap to be sent out.
So although I'm loath to put error handling code into a specific page, I decided I had to do it to get control back of my error logs and email box <s>. As you probably know ASP.NET can also capture errors on a page/control level by overriding the OnError method.
protected override void OnError(EventArgs e) { base.OnError(e); Exception ex = Server.GetLastError(); if (ex.InnerException != null) ex = ex.InnerException; // *** Special handling to avoid SPAM messages if (ex is ViewStateException || ex is HttpRequestValidationException) { Response.StatusCode = 500; MessageDisplay.DisplayMessage("Application Error", "Invalid input received for this request. Please retry it again with valid data."); } }
It took me a few tries to get this right - I always forget that the 'real' exception is typically buried in the InnerException rather than the primary exception returned by GetLastError() which only returns HttpException objects rather than say ViewStateException or HttpRequestValidationException.
Now I can get some piece of mind <s>...
As to the spam shit it still irks me to no end that these requests are being fired at my server consuming bandwidth and server resources. A while back I implemented some check code to to capture IP addresses on these errors and started writing them out to a file:
protected bool IsSpam(Exception ex) { bool blockIp = false; if (ex is HttpRequestValidationException || ex is ViewStateException) { if (Request.Path.ToLower().Contains("item.aspx")) blockIp = true; } if (blockIp) { StreamWriter writer = new StreamWriter(Server.MapPath("~/admin/spamip.txt"), true); writer.WriteLine(Request.UserHostAddress); writer.Close(); } return blockIp; }
In a matter of a couple of weeks I ended up with over 2000 Ip addresses which I went ahead and blocked. I even built a routine to automate this process through the Blocking IIS IP Addresses with ASP.NET that lets me add IP address. But they just keep coming even though pages are returning 500 errors. I also played with returning 404 errors for a while but none of that matters to the robots apparently. Once set nothing will stop this non-sense. Apparently IP blocking is also becoming useless with IP spoofing and more likely huge banks of IP addresses based in China serving just for that purpose.
Is there really any way to combat this? I suppose it's not worth it until it becomes a full scale DOS attack...
Oh well, one more thing we have to live with I guess, but man does that irk - all that wasted bandwidth and server resources and for what? It's not even exploiting anything useful... The world is full of M-O-R-O-N-S.
Other Posts you might also like
- Adding minimal OWIN Identity Authentication to an Existing ASP.NET MVC Application
- Resolving Paths To Server Relative Paths in .NET Code
- Getting the Client IP Address in ASP.NET Core
- Map Physical Paths with an HttpContext.MapPath() Extension Method in ASP.NET
- Back to Basics: Rewriting a URL in ASP.NET Core
The Voices of Reason
# re: Shutting up Invalid ViewState from SPAM bots
# re: Shutting up Invalid ViewState from SPAM bots
# re: Shutting up Invalid ViewState from SPAM bots
Might also consider just ending the response without returning anything to appear more like a black hole.
# re: Shutting up Invalid ViewState from SPAM bots
@Wilco - That's a great idea as well, although I don't think this would help much in this module particular scenario. These bots are smart enough to not send more than a 2 requests at a time and then wait for a half an hour or so for more. Further the IP Address vary on every request and they are widely ranged on top of it and don't seem to repeat often.
@McGurk - Blocking huge swaths - been thinking about it, but I do get a fair amount of legit traffic from Asia so it'd be tough. I haven't looked closely at the list of IPs captured but they are ALL OVER THE MAP. I don't think they are geographically isolated (ie. probably spoofed although I'm not sure how that's done).
@Mads - the problem for something generic is coming up with the right rules. I think what Wilco mentions is as close as it gets. In my case it's a very specific page that's easy to trap. But in other situations it may be much more difficult as ViewState errors may actually be 'valid' (ie. timed out pages etc) and you wouldn't just want to kill it. There are no easy answers I think...
Even a Module still hits ASP.NET too, so a lot of the overhead is already there although it certainly beats running all the way into a page <s>.
# re: Shutting up Invalid ViewState from SPAM bots
Its funny you mention this because I have similar global exception logging code to yours, and after I installed my RPC Ping server "fire and forget" code, I noticed some bogus requests started coming in and added a couple of IP addresses to my "IsBannedIP" method. This method is called very early in the Request pipline and simply stops them dead in their tracks before you even spin up a handler. It turned out that one of them was the googlebot! So, while I still have a few IPs in the list, I check them carefully.
# re: Shutting up Invalid ViewState from SPAM bots
As to IP trapping I put it right into the IIS restricted list for the site
http://west-wind.com/WebLog/posts/59731.aspx, but that's only an option if you run your own server and have full rights of course. I do it as a separate step too - not as part of the request cycle because that's pretty overhead intensive (DirectoryServices and all).
But the chance to cut something like a Google bot off is easily possible so gotta be careful <s>...
# re: Shutting up Invalid ViewState from SPAM bots
# re: Shutting up Invalid ViewState from SPAM bots
It sure feels like a losing battle. The best you can hope for is that the SPAM doesn't actually have any effect on your application.
# re: Shutting up Invalid ViewState from SPAM bots
# re: Shutting up Invalid ViewState from SPAM bots
# re: Shutting up Invalid ViewState from SPAM bots
# re: Shutting up Invalid ViewState from SPAM bots
You have great blog topics by the way, rock on.
# re: Shutting up Invalid ViewState from SPAM bots
Typically I want to know about all errors in a system so I certainly don't want to globally block ViewState errors, but in this case it seems the attacks are isolated to this particular URL and it's unlikely (though possible) that legitimate Viewstate errors would occur.
Captcha's not option in the middle of a Web Store <s>... and it wouldn't solve this problem of the errors anyway.
# re: Shutting up Invalid ViewState from SPAM bots
Usually it's like a spam e-mail's text, too. Don't these guys have lives? Are they trying to spam server admins/webmasters?
# re: Shutting up Invalid ViewState from SPAM bots
I've had an HttpModule similar to your for some time now. It looks at common view state "attacks" and black-lists offending IPs on the spot. It's all automated so don't worry if it grows to 2000 entries or whatever. I have 10,300 so far. Those are zombies and free proxies and crap anyway.
Also, those view state attacks always have patterns. They either feed you entire emails (what are they thinking?) or post links to farms, etc. They are easy enough to anaylze and code against.
One more thing: if they actually feed a bogus view state string (which was the case on my site), there's always something wacky with it, like an illegal character in the middle. ASP.NET won't botch up view state like that, neighter will Google or a news agent. So it's a pretty safe bet that if view state is corrupted, something is messing with it deliberately, so you can ban them.
I have an HttpModule here: http://www.aspnetresources.com/blog/fighting_view_state_spam.aspx which you are welcome to borrow. I also always recommend to generate a strong
machineKey
Hope this helps!
# re: Shutting up Invalid ViewState from SPAM bots
# re: Shutting up Invalid ViewState from SPAM bots
So my AXD files stemming from AJAX (WebResource.axd,ScriptResource.axd) are kicking off view state errors. Can a global error handler handle AXDs as well as ASPX?
# re: Shutting up Invalid ViewState from SPAM bots
Obviously this means a few of the exceptions will still get through, but at least I don't get batches of thousands of those exceptions anymore in the logs. I guess keeping a list of IPs would prevent that, but on the other hand, what if the IP is shared by many people (e.g. a large organization which had one exploited machine, or a university)?