IIS Level SQL Injection Prevention

  • Posted on: 3 September 2015
  • By: siteadm

It has been long time since my last post, I have been really busy these days. But I hope this nice post would compensate for days I didn't post anything. So this post has a back story, but again, I can not go in detail, can not name any particular application or company. So I won't be disclosing any information regarding the case, but I will do my best to explain the situation.

About a month ago, during a penetration testing project, as usual, bunch of vulnerabilities have been discovered. Nothing new, right? Yes, except this time will have a little difference. For this project, we found an SQL injection in a web application that have been developed ~8 years ago by a 3rd party vendor, which I shouldn't be really calling it "3rd party vendor", rather I think I should say "bunch of guys wrote some code and sold it". They sold it to this company (client) and they are using it for long time.

The code was written in ASP.NET/C# and the client just received a bunch of compiled ASP.NET codes to put in their wwwroot folder and let it run. It worked fine since then and the client probably used other companies to do pen testing on this application several times. But yeah, sometimes it takes 8 years for some vulnerabilities to be discovered.

Anyway, when we reported this finding to the client, client wasn't able to fix it *cough* always same *cough*. But exceptionally, this time client was right and their reason for "don't know how to fix it" was really acceptable. Reason is there is no code, no vendor, nothing to fix basically! It's bunch of DLLs. So in the end, we have been tasked to fix the vulnerability.

So we setup a meeting and everyone is throwing out ideas and fixes and suggestions and I'm "that annoying guy" who rejects each opinion without providing his own. I did it for quite sometime. So just to give you examples of ideas and how I rejected them, here I list some of them for you:

- Idea: Let's put a WAF and it will take care of it.

* Objection: This particular SQL injection is extremely stupid. (without going much in detail publicly here about it) just know that, it's a HUGE XML POST call and it accepts some SQL command from XML POST by design. Let's say it gets TableName from XML. I really want to say more here, but I don't like lawyers. So just accept it from me that WAF, a normal WAF, wasn't going to cut it and everyone else agreed with me. A generic WAF won't do it. (later we confirmed it)

- Idea: Let's decompile the DLL and fix the code.

* Objection: Really? It seems like you have never heard of lawyers! Yes, they do exist!

- Idea: Let's monitor the logs and watch it till we find a replacement for the application (client already started developing their own application to replace this old code, but it will take a year)

* Objection: Yeah, maybe... But it would be detecting the hacker after the fact. Hacker would take a backup of your DB and then you would detect it in your SIEM.

- Idea: Let's just delete wwwroot and put an HTML that says email us your queries and we will manually run it and send it back to you. So we can call it "email browser". (J.K.)

* Objection: Nice, but there is better way.

So at this point, I threw my idea out there without knowing that it is not really an easy task, I said things like, "Oh yeah, I did it before, it's easy, we can do it, bla bla bla" But the truth is I have never done it before, I saw some ISAPI codes before, like old WebKnight WAF code. But it was lone time ago. It didn't take long time to eventually understand that I made a huge mistake by suggesting it and consequently accepting this challenge.

Anyway, everyone loved the idea and I have been told that as long as I am develop it and I am not bothering anyone else, they are fine with that. Challenge accepted.

So as a programmer, first things first, I started googling. "IIS ISAPI C++ example", "IIS ISAPI example source code", "IIS Filter C++ code", sounds familiar, right? Oh yeah, everyone does that.

So let me tell you what happened... I just wasted days... Days... Let me tell you what happened so you would appreciate this post more:

- As dinosaur programmer, I just remembered word "ISAPI" and put all my time on ISAPI. So I started writing a skeleton ISAPI filter. Which I did, I also did lots of stupid stuff, like I kept compiling the ISAPI DLL, installed it on IIS, kept debugging it and basically it never worked. I thought I'm not installing it right, did lots of stuff. Also there was wrong, no crash, no message, no error. Nothing to analyze! Guess what? I was compiling 32bit DLL and IIS was x64.... Yeah.....

Anyway, it gets worse... When I fixed everything and got a skeleton ISAPI filter working, guess what happened? Whole approach was wrong!!! Why? Let me explain...

For ISAPI Filter DLL, basically you just need to write a DLL that exports two functions: GetFilterVersion and HttpFilterProc. So did I. Then in GetFilterVersion, you define what type of notifications you want to receive in your HttpFilterProc. So I wrote the whole code for testing purposes initially with SF_NOTIFY_PREPROC_HEADERS to see if I can catch requests going to VulnerableCode.aspx. So I did it. It worked and I was happy.

Then when I tried to add some real code to access POST data to check for SQL injection signs, I hit the brick wall! To access POST data, you need to register a filter that would receive SF_NOTIFY_READ_RAW_DATA. So I did that. But as soon as I added SF_NOTIFY_READ_RAW_DATA flag to my ISAPI Filter, IIS blocked my filter with error saying it is no longer supported. Yes! My whole work was for nothing. After IIS 6, Microsoft removed SF_NOTIFY_READ_RAW_DATA from ISAPI filters, so your old WebKnight WAF code won't work.

So I returned back to my genius friend, Google. I started googling, "SF_NOTIFY_READ_RAW_DATA not supported what to do now bro". I started to see pages that people were talking about IIS extension, instead of ISAPI filter! So I said wow! That's the way to go. I started writing an ISAPI extension, but this time I had funnier problem. When you register an IIS extension, think about it this way, you are writing an HTML page. Basically, IIS expects your ISAPI extension to return the resulting page. So for example you can write a C++ code for checking valid credit card number C++, compile it as IIS ISAPI extension, export HttpExtensionProc and use it. The problem is when a browser request hits your IIS Extension, there is no way of passing the execution to the next extension/handler. So I ended up getting ALL requests my DLL.

I said, OK, that might be an issue for ordinary programmer, not for a reverse engineer. I am going to fix it in "hackish" way. I found the next ISAPI handler in the chain for ASPX pages, which was aspnet_isapi.dll, I said, I will just do LoadLibrary, GetProcAddress(hMod, "HttpExtensionProc") and call the next extension if everything was fine in the POST data. But it just didn't work, I don't know, I just kept getting ASP.NET compilation errors. DLL load and passing the argument part was sort of OK, it was working, I was getting ASP.NET error page.

So I said ENOUGH! WHAT SHOULD I DO?????

After some more research, I found out, IIS does have "native module"!!!! Microsoft, can you please get it together and clearly state what is what, which one is for what purpose, who's who, who's not who? Microsoft, Please....

So I had already entered a journey of no-return, I wasn't able to go back and say, "hey guys, I think your ideas was so good, I was just picky". So I said, I will fix it, this time, let's see what "IIS native module" does!!

Long story short, guys.... Third time's a charm! This was the answer! After going here and there, eventually I figured it out all, I understood how to do everything I wanted and finally wrote the code, installed it on client's IIS, re-tested it and no SQL injection anymore!!!

That was it. Bye and thanks for reading.................

That would have been harsh, making you read the whole thing, without providing the solution. Not today.

After talking a lot to my employer, client, lawyers, mayor of city, council of foreign relations, council of conservative citizens, avengers team members, Mr. President and finally God, I got permission to strip down everything in the code that "sort of relates to company and/or client" and then publish it. But believe me, It is even better for you guys, you will have just the pure skeleton DLL, you can further develop it for your custom needs.

So using the code below, basically, you can just write your own WAF for IIS.

Code:

 

#define _WINSOCKAPI_
#include <windows.h>
#include <sal.h>
#include <httpserv.h>
#include <shlwapi.h>
#pragma comment(lib, "shlwapi.lib")


PCWSTR szVulnerableFile = L"Test2.aspx";
char ErrorPage[] = "Hack AttemptSQL Injection Detected! Your IP address has been logged!\r\n";

class CSQLFilter : public CGlobalModule
{
public:

	// Process a GL_PRE_BEGIN_REQUEST notification.
	GLOBAL_NOTIFICATION_STATUS OnGlobalPreBeginRequest(IN IPreBeginRequestProvider * pProvider)
	{
		// Initialize all the variables
		HRESULT hr = S_OK;
		IHttpContext* pHttpContext = pProvider->GetHttpContext();
		IHttpResponse * pHttpResponse = pHttpContext->GetResponse();
		IHttpRequest * pHttpReq = pHttpContext->GetRequest();
		DWORD cbBytesReceived = pHttpReq->GetRemainingEntityBytes();

		// Read URL address from Http Request
		DWORD dwBuffSize = 0;
		PCWSTR szURL = new TCHAR[2048];
		pHttpContext->GetServerVariable("URL", &szURL, &dwBuffSize);

		// if it is not our vulnerable file, just skip the tests and let the execution continue
		if (!StrStrW(szURL, szVulnerableFile)) return GL_NOTIFICATION_CONTINUE;

		// If there is no POST data, skip the additional tests and let the execution continue
		if (cbBytesReceived > 0)
		{
			// Read 2KB from POST data
			DWORD cbBytesReceived = 2048;
			void * pvRequestBody = pHttpContext->AllocateRequestMemory(cbBytesReceived);
			pHttpReq->ReadEntityBody(pvRequestBody, cbBytesReceived, false, &cbBytesReceived, NULL);

			char *found = StrStrIA((char*)pvRequestBody, "HACK");
			if (found)
			{
				HTTP_DATA_CHUNK dataChunk1;
				pHttpResponse->Clear();

				int BUFFERLENGTH = 256;
				char* szBuffer = (char *)pHttpContext->AllocateRequestMemory(BUFFERLENGTH);
				dataChunk1.DataChunkType = HttpDataChunkFromMemory;
				strcpy_s(szBuffer, 255, ErrorPage);

				dataChunk1.FromMemory.pBuffer = (PVOID)szBuffer;
				dataChunk1.FromMemory.BufferLength = (ULONG)strlen(szBuffer);
				hr = pHttpResponse->WriteEntityChunkByReference(&dataChunk1, -1);

				if (FAILED(hr))
				{
					pProvider->SetErrorStatus(hr);
					return GL_NOTIFICATION_HANDLED;
				}
				return GL_NOTIFICATION_HANDLED;
			}
		}

		return GL_NOTIFICATION_CONTINUE;
	}

	VOID Terminate()
	{
		// Remove the class from memory.
		delete this;
	}

	CSQLFilter()
	{	}

	~CSQLFilter()
	{	}

private:


};

// Create the module's exported registration function.
HRESULT __stdcall RegisterModule(DWORD dwServerVersion, IHttpModuleRegistrationInfo * pModuleInfo, IHttpServer * pGlobalInfo)
{
	UNREFERENCED_PARAMETER(dwServerVersion);
	UNREFERENCED_PARAMETER(pGlobalInfo);

	// Create an instance of the global module class.
	CSQLFilter * pGlobalModule = new CSQLFilter;
	
	// Test for an error.
	if (NULL == pGlobalModule)	return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
	
	// Set the global notifications and exit.
	return pModuleInfo->SetGlobalNotifications(pGlobalModule, GL_PRE_BEGIN_REQUEST);
}

 

So I compiled the code and as you can see in the code, I set it to check ONLY Test2.aspx and check to see if the POST data have "hack" string in it. Obviously for final solution I did much more than that, but this is what I was able to share with you.

So here is the code when I called unprotected file with "Hack" in the POST, nothing happens (both files have identical source code):

Calling the right file (protected file) with "hack" in POST:

To compile the code, just create a New project in Visual Studio, then C++ project -> Win 32 -> Dll -> Empty project.

Now add a def file and call it for example, MyDLL.def and paste this in it:

LIBRARY "DLLTest"
EXPORTS
    RegisterModule

 

Then add a CPP file and paste the code above. Compile and enjoy!

But, if you are lazy just like me, I like you. So here is all files together, ready to compile.

To install the IIS native module you just made, run this command:

%systemroot%\system32\inetsrv\appcmd.exe install module /name:MySQLPrevent /image:C:\Windows\MySQLPrevent.dll

I hope you enjoy it! Feel free to ask questions! Thanks for reading my lengthy post!

Comments

Vulnerability in my case was too odd, it was in XML nodes and method of exploiting the vulnerability was special. It already had mod_security, but mod_security didn't stop the attack and couldn't. I tried.

Add new comment

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
CAPTCHA
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.
Image CAPTCHA
Enter the characters shown in the image.