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