Avoiding Server Hijacking

Avoiding Server Hijacking

Server hijacking happens when an application allows a local user to intercept and manipulate information meant for a server that the local user didn't start themselves. First let's get an idea of how such a thing could happen. When a server starts up, it first creates a socket and binds that socket according to the protocol you want to work with. If it's a Transmission Control Protocol (TCP) or User Datagram Protocol (UDP) socket, the socket is bound to a port. Less commonly used protocols might have very different addressing schemes. A port is represented by an unsigned short (16-bit) integer in C or C++, so it can range from 0 to 65535. The bind function looks like this:

int bind ( SOCKET s, const struct sockaddr FAR* name, int namelen );

This function is written to allow us to communicate using a wide variety of protocols. If you're writing code for Internet Protocol version 4 (IPv4), the variant you want to use is a sockaddr_in structure, which is defined like so:

struct sockaddr_in{ short sin_family; unsigned short sin_port; struct in_addr sin_addr; char sin_zero[8]; };

NOTE
At the time the first edition of this book was written, IPv6 was not in wide use. As of this writing, it is still not in wide use but will ship in Microsoft Windows .NET Server 2003 and Service Pack 1 for Microsoft Windows XP. IPv6 changes will be covered later on in this chapter. The examples in this chapter are confined to IPv4. Unless otherwise noted, the concepts presented should be applicable to both protocols.

When you bind a socket, the important bits are the sin_port and sin_addr members. With a server, you'd almost always specify a port to listen on, but the problem comes when we start dealing with the sin_addr member. The documentation on bind tells us that if you bind to INADDR_ANY (really 0), you're listening on all the available network interfaces. If you bind to a specific IP address, you're listening for packets addressed to only that one address. Here's an interesting twist in the way that sockets work that will bite you: it is possible to bind more than one socket to the same port.

The sockets libraries decide who wins and gets the incoming packet by determining which binding is most specific. A socket bound to INADDR_ANY loses to a socket bound to a specific IP address. For example, if your server has two IP addresses, 157.34.32.56 and 172.101.92.44, the socket software would pass incoming data on that socket to an application binding to 172.101.92.44 rather than an application binding to INADDR_ANY. One solution would be to identify and bind every available IP address on your server, but this is annoying. If you want to deal with the fact that network interfaces might be popping up (and going away) on the fly, you have to write a lot more code. Fortunately, you have a way out, which I'll illustrate in the following code example. A socket option named SO_EXCLUSIVEADDRUSE, which was first introduced in Microsoft Windows NT 4 Service Pack 4, solves this problem.

One of the reasons Microsoft introduced this socket option is the work of Chris Wysopal (Weld Pond). Chris ported Netcat written by Hobbit to Windows, and in the course of testing found a vulnerability in several servers under Windows NT that had this binding problem. Chris and Hobbit were members of a sharp hacker group called the L0pht (now part of @stake). I've written a demo that shows off the problem and solution:

/* BindDemoSvr.cpp */ #include <winsock2.h> #include <stdio.h> #include <assert.h> #include "SocketHelper.h" //If you have an older version of winsock2.h #ifndef SO_EXCLUSIVEADDRUSE #define SO_EXCLUSIVEADDRUSE ((int)(~SO_REUSEADDR)) #endif /* This application demonstrates a generic UDP-based server. It listens on port 8391. If you have something running there, change the port number and remember to change the client too. */ int main(int argc, char* argv[]) { SOCKET sock; sockaddr_in sin; DWORD packets; bool hijack = false; bool nohijack = false; if(argc < 2 argc > 3) { printf("Usage is %s [address to bind]\n", argv[0]); printf("Options are:\n\t-hijack\n\t-nohijack\n"); return -1; } if(argc == 3) { //Check to see whether hijacking mode or no-hijack mode is //enabled. if(strcmp("-hijack", argv[2]) == 0) { hijack = true; } else if(strcmp("-nohijack", argv[2]) == 0) { nohijack = true; } else { printf("Unrecognized argument %s\n", argv[2]); return -1; } } if(!InitWinsock()) return -1; //Create your socket. sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if(sock == INVALID_SOCKET) { printf("Cannot create socket - err = %d\n", GetLastError()); return -1; } //Now let's bind the socket. //First initialize the sockaddr_in. //I'm picking a somewhat random port that shouldn't have //anything running. if(!InitSockAddr(&sin, argv[1], 8391)) { printf("Can't initialize sockaddr_in - doh!\n"); closesocket(sock); return -1; } //Let's demonstrate the hijacking and //anti-hijacking options here. if(hijack) { BOOL val = TRUE; if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&val, sizeof(val)) == 0) { printf("SO_REUSEADDR enabled - Yo Ho Ho\n"); } else { printf("Cannot set SO_REUSEADDR - err = %d\n", GetLastError()); closesocket(sock); return -1; } } else if(nohijack) { BOOL val = TRUE; if(setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char*)&val, sizeof(val)) == 0) { printf("SO_EXCLUSIVEADDRUSE enabled\n"); printf("No hijackers allowed!\n"); } else { printf("Cannot set SO_ EXCLUSIVEADDRUSE - err = %d\n", GetLastError()); closesocket(sock); return -1; } } if(bind(sock, (sockaddr*)&sin, sizeof(sockaddr_in)) == 0) { printf("Socket bound to %s\n", argv[1]); } else { if(hijack) { printf("Curses! Our evil warez are foiled!\ n"); } printf("Cannot bind socket - err = %d\n", GetLastError()); closesocket(sock); return -1; } // OK, now we've got a socket bound. Let's see whether someone //sends us any packets - put a limit so that we don't have to //write special shutdown code. for(packets = 0; packets < 10; packets++) { char buf[512]; sockaddr_in from; int fromlen = sizeof(sockaddr_in); // Remember that this function has a TRINARY return; //if it is greater than 0, we have some data; //if it is 0, there was a graceful shutdown //(shouldn't apply here); //if it is less than 0, there is an error. if(recvfrom(sock, buf, 512, 0, (sockaddr*)&from, &fromlen)> 0) { printf("Message from %s at port %d:\n%s\n", inet_ntoa(from.sin_addr), ntohs(from.sin_port), buf); // If we're hijacking them, change the message and //send it to the real server. if(hijack) { sockaddr_in local; if(InitSockAddr(&local, "127.0.0.1", 83 91)) { buf[sizeof(buf)-1] = '\0'; strncpy(buf, "You are hacked!", siz eof(buf) -1); if(sendto(sock, buf, strlen(buf) + 1, 0, (sockaddr*)&local, sizeof(sockaddr_in)) < 1) { printf ("Cannot send message to localhost - err = %d\n", GetLastError()); } } } } else { //I'm not sure how we get here, but if we do, //we'll die gracefully. printf("Ghastly error %d\n", GetLastError() ); break; } } return 0; }

This sample code is also available in the companion content in the folder Secureco2\Chapter15\BindDemo. Let's quickly review how the code works, and then we'll look at some results. I've hidden a couple of helper functions in SocketHelper.cpp I'll be reusing these functions throughout the chapter. I also hope that the code might turn out to be useful in your own applications.

First we check the arguments. I have two options available: hijack and nohijack. We'll use the hijack option on the attacker and the nohijack option to prevent the attack. The difference here is which socket options we set. The hijack option uses SO_REUSEADDR to allow the attacker to bind to an active port. The nohijack option uses SO_EXCLUSIVEADDRUSE, which prevents SO_REUSEADDR from functioning. If you specify no options, the server will just bind the port normally. Once the socket is bound, we'll log where the packet originated and the message. If we're attacking the other server, we'll change the message to show the consequences of this problem.

So, let's take a look at what happens if the server doesn't use SO_EXCLUSIVEADDRUSE. Invoke the victim server with this:

BindDemo.exe 0.0.0.0

Next invoke the attacker with the following substitute 192.168.0.1 with your own IP address:

BindDemo.exe 192.168.0.1 -hijack

Now use the client to send a message:

BindDemoClient.exe 192.168.0.1

Here are the results from the attacker:

SO_REUSEADDR enabled - Yo Ho Ho Socket bound to 192.168.0.1 Message from 192.168.0.1 at port 4081: Hey you!

Here's what the victim sees:

Socket bound to 0.0.0.0 Message from 192.168.0.1 at port 8391: You are hacked!

If your application uses careful logging for example, recording the time, date, client IP address, and port number of all requests to an appropriately ACL'd text file you might notice that this attacker was a little sloppy and left some traces. Any logs you might have show packets originating from the server itself. Do not let this give you any comfort when we get into spoofing later in this chapter, I'll show you how this could have been trivially overcome by the attacker.

Now, here's how to do it right. Invoke the server no longer a hapless victim with

BindDemo.exe 0.0.0.0 nohijack

Start the attacker as before with

BindDemo.exe 192.168.0.1 hijack

The server responds with

SO_EXCLUSIVEADDRUSE enabled - no hijackers allowed! Socket bound to 0.0.0.0

And the attacker complains:

SO_REUSEADDR enabled - Yo Ho Ho Curses! Our evil warez are foiled! Cannot bind socket - err = 10013

Now, when the client sends a message, our server gets the right one:

Message from 192.168.0.1 at port 4097: Hey you!

There is one drawback to using SO_EXCLUSIVEADDRUSE if your application needs to restart, it could fail unless you shut down properly. The base problem is that although your application has exited and all of the handles have been closed, connections may be lingering in the TCP/IP stack at the operating system level. The correct approach is to call shutdown on the socket and then call recv until no more data is available or you get an error return. You can then call closesocket, and restart your application. See the SDK documentation on shutdown for full details.

When Microsoft Windows .NET Server 2003 ships, SO_EXCLUSIVEADDRUSE should not be needed any longer in most cases a reasonable DACL that grants access to the current user and administrators is applied to a socket. This approach gets us out of the problem just cited and prevents hijacking attacks.



Writing Secure Code
Writing Secure Code, Second Edition
ISBN: 0735617228
EAN: 2147483647
Year: 2001
Pages: 286

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net