Easily detect and block malicious HTTP requests targeting IIS/ASP.NET using “BLACKIPS”

In a previous blog, I have detailed how to Install IIS Dynamic IP Restrictions in an Azure Web Role to block DOS attack targeting a web role in Azure. In many situation, an attacker may combine other attacks to DOS such as script injection attacks trying to test and target application's vulnerabilities by sending malicious requests. Fortunately, ASP.NET request validation feature is able to detect many malicious request but intrusion detection and prevention may still require painful tasks like collecting and analyzing IIS and event logs. In this blog, we'll see how we can automate the detection of malicious requests by adding very little code to an existing ASP.NET application.

I – Detecting malicious HTTP requests

Malicious requests sent to ASP.NET generally translate in a massive amount of 400 responses in IIS logs:

2014-04-25 10:20:11 W3SVC1 MEMMANUBO ::1 GET /a&b - 80 - ::1 HTTP/1.1 Mozilla/5.0+(Windows+NT+6.3;+WOW64;+Trident/7.0;+rv:11.0)+like+Gecko - - localhost 400 0 0 3804 391 0
2014-04-25 10:20:11 W3SVC1 MEMMANUBO ::1 GET /a&b - 80 - ::1 HTTP/1.1 Mozilla/5.0+(Windows+NT+6.3;+WOW64;+Trident/7.0;+rv:11.0)+like+Gecko - - localhost 400 0 0 3804 391 0

2014-04-25 10:20:12 W3SVC1 MEMMANUBO ::1 GET /a&b - 80 - ::1 HTTP/1.1 Mozilla/5.0+(Windows+NT+6.3;+WOW64;+Trident/7.0;+rv:11.0)+like+Gecko - - localhost 400 0 0 3804 391 0

In addition, when ASP.NET detects a malicious request, it logs the following ASP.NET 1309 warning in Windows application event log:

The above 1309 event provides detailed information including the malicious request detected and the IP address that has sent the "dangerous request" :

Event code: 3005
Event message: An unhandled exception has occurred.
<snip>
Request information:
Request URL: https://localhost/a&b
Request path: /a&b
User host address: ::1

<snip>

II – Automating the detection using "BLACKIPS"

With the above in mind, wouldn't it be cool to track the 1309/malicious requests in real time and provide some easy way to block the attacker using Windows Firewall ?

Well, that's exactly what the attached code "BLACKIP" does : It simply catches HTTP exceptions in global.asax/Application_Error and, in case the exception deals with a malicious request, it adds the client IP to a list. Another page simply allows to dump out the list and provides netsh command to blacklist the malicious clients using Windows Firewall :

In this example, 3 malicious IPs were detected and the netsh command to backlist them is automatically built:

netsh advfirewall firewall add rule name=BLACKIPS dir=in interface=any action=block remoteip=fe80::5d4a:3cd0:71c3:1df1,10.190.31.138,172.19.200.54

Let's have a look at the code :

[global.asax – code to detect malicious requests]

<script language="C#" runat="server">

protected void Application_Error(object sender, EventArgs e)
{
// check if tracing has been disabled and exit immediately
if (Application["traceBadIPs"] != null)
{
bool tracingon = (bool)Application["traceBadIPs"];
if (!tracingon) return;
}
// get exception and process only HttpException "Exception message: A potentially dangerous Request.Path value was detected from the client "
Exception objErr = Server.GetLastError().GetBaseException();
if ((objErr is System.Web.HttpException) && (objErr.Message.IndexOf("potentially dangerous Request") > 0))
{
// get client IP
string clientIp = Request.ServerVariables["REMOTE_ADDR"];
// if application's "malicious IPs" dictionnary doesn't exist, create it
if (Application["maliciousIPs"] == null) Application["maliciousIPs"] = new Dictionary<string, int>();
Dictionary<string, int> dictionary = Application["maliciousIPs"] as Dictionary<string, int>;
// if client IP is already known, increase hit count
if (dictionary.ContainsKey(clientIp))
dictionary[clientIp] = dictionary[clientIp] + 1;
else

// this is a new malicious IP, add it to the dictionary
dictionary.Add(clientIp, 1);
}
}
</script>

[malicious.aspx – code to dump attacker's IPs]

<script language="C#" runat="server">

protected void Page_Load(object sender, EventArgs e)
{
// get application's "malicious IPs" dictionnary
Dictionary<string, int> dictionary = Application["maliciousIPs"] as Dictionary<string, int>;
if (dictionary == null)
// no IP detected, just exit
{
TableBadIps.Text = NetshCmdAdd.Text = "No maliciousIP detected";
}
else
{
// dump dictionnary's content in a table & build list of malicious IPs list to use with netsh advfirewall
string strtable = "<TABLE BORDER=1><TR><TD>IP</TD><TD>Hits</TD></TR>";
string blocklist = "";
foreach (KeyValuePair<string, int> pair in dictionary)
{
string ip = pair.Key;
strtable += "<TR><TD>" + ip + "</TD><TD>" + pair.Value + "</TD></TR>";
// remove scope in IPv6 address
int pos = ip.IndexOf("%");
if (pos > 0) ip = ip.Substring(0, pos);
if (blocklist != "") blocklist = blocklist + ",";
blocklist = blocklist + ip;
}
strtable += "</TABLE>";
// show the malicious IPs table
TableBadIps.Text = strtable;
// show the firewall setup string
NetshCmdAdd.Text = "netsh advfirewall firewall add rule name=BADIPS dir=in interface=any action=block remoteip=" + blocklist;
}
// control tracing on/off
if (Request.QueryString["tracing"] != null)
Application["traceBadIPs"] = (Request.QueryString["tracing"] == "on");
if (Application["traceBadIPs"] == null)
Application["traceBadIPs"] = true;
if ((bool)Application["traceBadIPs"])
ControlTracing.Text = "<a href=Malicious.aspx?tracing=off>Stop tracing</a>";
else
ControlTracing.Text = "<a href=Malicious.aspx?tracing=on>Start tracing</a>";
}
</script>
<html lang="en" xmlns="https://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="refresh" content="5">
<meta charset="utf-8" />
<title>Malicious Request(s)</title>

<script>
function ClipBoard(elemid) {
var elem = document.getElementById(elemid);
window.clipboardData.setData("Text", elem.innerText);
}
</script>
</head>
<body>

<CENTER><B>Malicious Request(s)</B></CENTER>
<p>
List of malicious IP(s) (detected from ASP.NET (event 1309 in application event log)):<br><br>
<asp:label id=TableBadIps runat=server /><br>
</p>
<p>
You may "blacklist" malicious IPs by creating a firewall rule named BADIPS using netsh:<br><br />
<asp:label id=NetshCmdAdd runat=server /><br><BUTTON onClick="ClipBoard('NetshCmdAdd');">Copy to Clipboard</BUTTON> <br><br>
To delete the above rule, use the following netsh command:<br><br />
<span id="NetshCmdDel">netsh advfirewall firewall delete rule name=BADIPS</span><br><BUTTON onClick="ClipBoard('NetshCmdDel');">Copy to Clipboard</BUTTON> <br>
</p>
<p>
The following allows to enable/disable tracing of malicious IPs:<br><br />
<asp:label id=ControlTracing runat=server /><br>
</p>
</body>
</html>

III –BLACKIPS setup and testing

To setup the detection code, you just need to include the above code to your ASP.NET project:

  1. add the Application_Error handler as it is implemented in the global.asax attached in the global.asax file of your application
  2. Copy the malicious.aspx file to any location in your application (I would suggest in some hidden location)

Once your application is updated with the above changes, you can simply test the feature by requesting a malicious URL in IE (https://servername/a&b ).
If ASP.NET verbose error is enabled (which is the case if you access the web site locally), you will get this output:

If debug is not enabled, your browser will simply display the generic error for HTTP 400. You may also check that Event 1309  is logged in Windows application log. At this point, you can simply navigate to https://servername/malicious.aspx and check that your malicious request was detected.

IV – Important notes

Before running the netsh command to blacklist an IP, carefully verify suspicious IP addresses using reverse DNS…etc. Typically, a user of your application (or a code bug) may accidently cause a malicious request to be received by the application. In this situation, blacklisting user IP will just prevent the specific user encountering the error from connect to the application. So, be careful to not "shoot yourself in the foot"!

If you implement the BLACKIPS code in an Azure Webrole with multiple instances:

  • Keep in mind that output may differ from one instance to another due to Load Balancing
  • Keep in mind that the netsh command to black list attackers needs to be added in a startup task so that it gets implemented on all instances

V – Conclusion

Our little BLACKIPS tool is complementary to Static/Dynamic IP Restrictions and it has some cool features

  • Possibility to dynamically monitor malicious IPs (no need to RDP, gather & analyze application event logs)
  • Blacklisting client IP with Windows Firewall blocks the attacker at the network level (even before HTTP.SYS handles the request)
  • Compared to static IP Restrictions, the use of Windows Firewall doesn't require any application restart (which can occur if you modify web.config to setup new IP Restrictions)

That being said, our little BLACKIP tool likely needs some improvements:

  • Persist the IP list dictionary so that it won't be lost upon application pool recycling…etc
  • Implement reverse IP resolution to display IP owner, country…etc
  • Improved UI

Note that I'm providing the BLACKIPS the code as it is and you'll need to test it to make sure it works in your environment.  Feel free to customize it to meet your needs and reports improvements made so that it is beneficial to the community!

Emmanuel Boersma
Cloud Integration Engineer

blackips.zip

Comments

  • Anonymous
    October 15, 2014
    Good article

  • Anonymous
    January 20, 2015
    By the way, this has now been packaged into a Nuget package called Azure Black Ips. We have blogged about it here: blogs.msdn.com/.../azure-black-ips-intro.aspx .