Chapter 15. Socket Security

Sockets are at the heart of any application that communicates using the TCP/IP protocol. The IP protocol and associated transports, such as TCP and UDP, were not designed to meet the threat environments we currently face. However, as we move to IPv6—Internet Protocol version 6, described in the "IPv6 Is Coming!" section later in this chapter—some of these problems will be mitigated. Some of the issues I’ll cover in this chapter include binding your server so that it cannot be hijacked by local users, writing a server that can listen on the network interfaces the user chooses, and managing how you accept connections. I’ll also discuss general rules for writing firewall-friendly applications, spoofing, and host-based and port-based trust.

This chapter assumes familiarity with the fundamentals of sockets programming. If you are new to sockets programming, a book I found helpful is Windows Sockets Network Programming (Addison-Wesley Publishing Co., 1995), by Bob Quinn and David Shute. The example programs are written in C, with a touch of C++ thrown in. I like to use the .cpp extension to get stricter compiler warnings, but the applications should be accessible to anyone who can read C. Some of the specific socket options and interface management functions are Microsoft-specific, but the general ideas should be useful to people writing code for any platform.

If you’re interested in using built-in Windows functionality to authenticate your clients and servers and to establish privacy and integrity (including SSL/TLS), look at the documentation for the SSPI (Security Support Provider Interface) API. Although it has lots of useful functionality, be warned that this bunch of APIs is not for the faint of heart. As mentioned in Chapter 4, a good explanation of SSPI is in Programming Server-Side Applications for Microsoft Windows 2000 (Microsoft Press, 2000), by Jeffrey Richter and Jason Clark.

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]
", argv[0]);
        printf("Options are:
	-hijack
	-nohijack
");
        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
", 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
", 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!
");
        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
");
        }
        else
        {
            printf("Cannot set SO_REUSEADDR -  err = %d
", 
                   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
");
            printf("No hijackers allowed!
");
        }
        else
        {
            printf("Cannot set SO_ EXCLUSIVEADDRUSE -  err = %d
", 
                   GetLastError());
            closesocket(sock);
            return -1;
        }
    }

    if(bind(sock, (sockaddr*)&sin, sizeof(sockaddr_in))  == 0)
    {
        printf("Socket bound to %s
", argv[1]);
    }
    else
    {
        if(hijack)
        {
            printf("Curses! Our evil warez are foiled! n");
        }

        printf("Cannot bind socket -  err = %d
", 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:
%s
",
                   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] = ’’;
                    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
",
                      GetLastError());
                    }
                }
            }
        }
        else
        {
            //I’m not sure how we get here, but if we do, 
            //we’ll die gracefully.
            printf("Ghastly error %d
", GetLastError() );
            break;
        }
    }

    return 0;

}

This sample code is also available in the companion content in the folder Secureco2Chapter15BindDemo. 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.

TCP Window Attacks

A particularly nasty attack that is allowed by the TCP RFCs is an intentional variant on the silly window syndrome. A TCP connection uses a window size advertisement in ACK packets to help the server send data no faster than the client can receive it. If the client’s buffers are completely full, it can even send the server a window size of zero, which causes the server to wait to send more data. For a much more thorough description, see Internetworking with TCP/IP Vol. 1: Principles, Protocols, and Architectures (4th Edition) by Douglas Comer (Prentice Hall, 2000).

The way the attack works is that a malicious client will create a connection, set the window size to a very small number (or zero), and cause the server to send the data very slowly and with very high overhead. For every few bytes of data, there’s around 40 bytes worth of TCP and IP headers. Depending on how you’ve written your server application, it could cause you to start blocking when trying to send data, which consumes your worker threads. This typically hasn’t been something we’ve worried about in the past—our TCP/IP stacks negotiate this for us, and there’s very little ability to adjust how this works in terms of normal socket calls. Unfortunately, some people have written specialized apps to cause everyone trouble.

The defense is to always check returns on send calls. This is good practice in general; I’ve seen connections get closed between the initial connect and the first send. It’s also possible under ordinary conditions for a server to need to transmit data slowly. Consider a fast Web server on a gigabit link transmitting to a system on a modem link. If a client takes an inordinate amount of time to process what you’ve been sending them, it might be best to do an abortive close and shutdown of the socket.

Choosing Server Interfaces

When I’m trying to configure a system to expose directly to the Internet, one of my first tasks is to reduce the number of services that are exposed to the outside world to a bare minimum. If the system has only one IP address and one network interface, doing so is a little easier: I can just turn off services until the ports I’m worried about aren’t listening. If the system is part of a large Internet site, it’s probably multihomed—that is, it has at least two network cards. Now things start to get tricky. I can’t just turn off the service in many cases; I might want it available on the back end. If I have no control over which network interfaces or IP addresses the service listens on, I’m faced with using some form of filtering on the host or depending on a router or firewall to protect me. People can and do misconfigure IP filters; routers can sometimes fail in various ways; and if the system right next to me gets hacked, the hacker can probably attack me without going through the router. Additionally, if my server is highly loaded, the extra overhead of a host-based filter might be significant. When a programmer takes the time to give me a service that can be configured, it makes my job as a security operations person much easier. Any IP service should be configurable at one of three levels:

  • Which network interface is listening

  • Which IP address or addresses it will listen on, and preferably which port it will listen on

  • Which clients can connect to the service

Enumerating interfaces and attaching IP addresses to those interfaces was fairly tedious under Windows NT 4. You would look in the registry to find which adapters were bound and then go look up more registry keys to find the individual adapter.

Accepting Connections

The Windows Sockets 2.0 (Winsock) API gives you a number of options to use when deciding whether to process data coming from a specific client. If you’re dealing with a connectionless protocol such as UDP, the process is simple: you obtain the IP address and port associated with the client and then decide whether to process the request. If you don’t want to accept the request, you normally just drop the packet and don’t send a reply. A reply consumes your resources and gives your attacker information.

When dealing with a connection-based protocol such as TCP, the situation becomes a lot more complicated. First let’s look at how a TCP connection is established from the point of view of the server. The first step is for the client to attempt to connect by sending us a SYN packet. If we decide we want to talk to this client— assuming our port is listening—we reply with a SYN-ACK packet, and the client completes the connection by sending us an ACK packet. Now we can send data in both directions. If the client decides to terminate the connection, we’re sent a FIN packet. We respond with a FIN-ACK packet and notify our application. We will typically send any remaining data, send the client a FIN, and wait up to twice the maximum segment lifetime (MSL) for a FIN-ACK reply.

Note

MSL represents the amount of time a packet can exist on the network before it is discarded.

Here’s how an old-style connection using accept would be processed—see AcceptConnection.cpp with the book’s sample files in the folder Secureco2Chapter15AcceptConnection for the whole application:

void OldStyleListen(SOCKET sock)
{
    //Now we’re bound. Let’s listen on the port.
    //Use this as a connection counter.
    int conns = 0;

    while(1)
    {
        //Use maximum backlog allowed.
        if(listen(sock, SOMAXCONN) == 0)
        {
            SOCKET sock2;
            sockaddr_in from;
            int size;

            //Someone tried to connect - call accept to find out who.
            conns++;

            size = sizeof(sockaddr_in);
            sock2 = accept(sock, (sockaddr*)&from, &size);

            if(sock2 == INVALID_SOCKET)
            {
                printf("Error accepting connection -  %d
", 
                       GetLastError());
            }
            else
            {
                //NOTE -  in the real world, we’d probably want to 
                //hand this socket off to a worker thread.

                printf("Accepted connection from %s
",  
                       inet_ntoa(from.sin_addr));
                //Now decide what to do with the connection;
                //really silly decision criteria -  we’ll just take
                //every other one.
                if(conns % 2 == 0)
                {
                    printf("We like this client.
");
                    // Pretend to do some processing here.
                }
                else
                {
                    printf("Go away!
");
                }
                closesocket(sock2);
            }
        }
        else
        {
            //Error
            printf("Listen failed -  err = %d
", GetLastError());
            break;
        }

        //Insert your own code here to decide when to shut down 
        //the server.
        if(conns > 10)
        {
            break;
        }
    }
}

I’ve written some time-honored, pretty standard sockets code. But what’s wrong with this code? First, even if we immediately drop the connection, the attacker knows that some service is listening on that port. No matter if it won’t talk to the attacker—it must be doing something. We’re also going to exchange a total of seven packets in the process of telling the client to go away. Finally, if the attacker is truly obnoxious, he might have hacked his IP stack to never send the FIN-ACK in response to our FIN. If that’s the case, we’ll wait two segment lifetimes for a reply. Assuming that a good server can process several hundred connections per second, it isn’t hard to see how an attacker could consume even a large pool of workers. A partial solution to this problem is to use the setsockopt function to set SO_LINGER to either 0 or a very small number before calling the closesocket function. Setting SO_LINGER causes the operating system to clean up sockets more rapidly.

Now let’s examine another way to do the same thing: by using the WSAAccept function. When we combine its use with setting the SO_CONDITIONAL_ACCEPT socket option, this function allows us to make decisions about whether we want to accept the connection before responding. Here’s the code:

int CALLBACK AcceptCondition(
    IN LPWSABUF lpCallerId,
    IN LPWSABUF lpCallerData,
    IN OUT LPQOS lpSQOS,
    IN OUT LPQOS lpGQOS,
    IN LPWSABUF lpCalleeId,
    OUT LPWSABUF lpCalleeData,
    OUT GROUP FAR *g,
    IN DWORD dwCallbackData
)
{
    sockaddr_in* pCaller;
    sockaddr_in* pCallee;

    pCaller = (sockaddr_in*)lpCallerId->buf;
    pCallee = (sockaddr_in*)lpCalleeId->buf;

    printf("Attempted connection from %s
", 
       inet_ntoa(pCaller->sin_addr));

    //If you need this to work under Windows 98, see Q193919.
    if(lpSQOS != NULL)
    {
        //You could negotiate QOS here.
    }

    //Now decide what to return - 
    //let’s not take connections from ourselves.
    if(pCaller->sin_addr.S_un.S_addr == inet_addr(MyIpAddr))
    {
        return CF_REJECT;
    }
    else
    {
        return CF_ACCEPT;
    }

    //Note - we could also return CF_DEFER - 
    //this function needs to run in the same thread as the caller.
    //A possible use for this would be to do a DNS lookup on  
    //the caller and then try again once we know who they are.
}

void NewStyleListen(SOCKET sock)
{
    //Now we’re bound, let’s listen on the port.
    //Use this as a connection counter.
    int conns = 0;

    //First set an option.
    BOOL val = TRUE;
  
    if(setsockopt(sock, 
                  SOL_SOCKET, 
                  SO_CONDITIONAL_ACCEPT, 
                  (const char*)&val, sizeof(val)) != 0)
    {
        printf("Cannot set SO_CONDITIONAL_ACCEPT -  err = %d
", 
               GetLastError());
        return;
    }

    while(1)
    {
        //Use maximum backlog allowed.
        if(listen(sock, SOMAXCONN) == 0)
        {
            SOCKET sock2;
            sockaddr_in from;
            int size;

            //Someone tried to connect -  call accept to find out who.
            conns++;

            size = sizeof(sockaddr_in);

            //This is where things get different.
            sock2 = WSAAccept(sock,
                              (sockaddr*)&from,
                              &size,
                              AcceptCondition,
                              conns); //Use conns as extra callback data.

            if(sock2 == INVALID_SOCKET)
            {
                printf("Error accepting connection - %d
", 
                  GetLastError());
            }
            else
            {
                //NOTE - in the real world, we’d probably 
                // want to hand this socket off to a worker thread.

                printf("Accepted connection from %s
",   
                       inet_ntoa(from.sin_addr));
                //Pretend to do some processing here.
                closesocket(sock2);
            }
        }
        else
        {
            //Error
            printf("Listen failed -  err = %d
", GetLastError());
            break;
        }

        // Insert your own code here to decide 
        // when to shut down t he server.
        if(conns > 10)
        {
            break;
        }
    }
}

As you can see, this is mostly the same code as the older version except that I’ve written a callback function that’s used to decide whether to accept the connection. Let’s take a look at the results of using a port scanner I wrote:

[d:]PortScan.exe -v -p 8765 192.168.0.1
Port 192.168.0.1:8765:0 timed out

Now let’s see what happened from the point of view of the server:

[d:]AcceptConnection.exe
Socket bound
Attempted connection from 192.168.0.1
Error accepting connection - 10061
Attempted connection from 192.168.0.1
Error accepting connection - 10061
Attempted connection from 192.168.0.1
Error accepting connection – 10061

Depending on how the client application is written, a default TCP connection will try three times to obtain a completed connection. Normal behavior is to send the SYN packet and wait for the reply. If no response comes, we send another SYN packet and wait twice as long as previously. If still no response comes, we try again and again double the wait time. If the client has implemented a timeout that is shorter than normal, you might see only two connection attempts. This new code has one very desirable behavior from a security standpoint: the attacker is getting timeouts and doesn’t know whether the timeouts are because port filtering is enabled or because the application doesn’t want to talk to her. The obvious downside is the extra overhead the server incurs as it refuses all three attempts to connect. However, the extra overhead should be minimal, depending on the amount of processing that your callback function does.

One significant downside to using WSAAccept is that it is incompatible with the operating system’s SYN flood protection. It also might not be appropriate for high-performance applications using overlapped I/O that would normally call AcceptEx.

Writing Firewall-Friendly Applications

People often complain that firewalls get in the way and won’t let their applications work. News flash! Firewalls are supposed to get in the way! It’s their job. If they were supposed to just pass everything along, they’d be called routers, although some routers do have firewall functionality. Firewalls are also normally administered by grumpy people who don’t want to change anything. At least the firewalls most likely to protect you from attackers are administered by this sort of person. Firewall administrators aren’t likely to open new ports to allow some application they don’t understand, and this goes double if your application needs to allow several ports to be open in both directions. If you write your application correctly, you’ll find that firewalls don’t get in the way nearly so often. I predict that there will be many more firewalls in the future; most hosts will have some form of firewalling installed. In addition to an ordinary firewall at the perimeter, there could be firewalls at any point in the network. It will become even more important to design applications that work well with firewalls.

Here are some rules to follow:

  • Use one connection to do the job.

  • Don’t make connections back to the client from the server.

  • Connection-based protocols are easier to secure.

  • Don’t try to multiplex your application over another protocol.

  • Don’t embed host IP addresses in application-layer data.

  • Configure your client and server to customize the port used.

Let’s examine the reasons for each of these rules.

Use One Connection to Do the Job

If an application needs to create more than one connection, it is a sign of inefficient design. Sockets are designed for two-way communication on one connection, so it would be rare to truly require more than one connection. One possible reason might be that the application needs a control channel in addition to a data channel, but provisions for this exist in TCP. Additionally, you can easily work around this if you design your protocol well—many protocols provide information in the header that specifies what type of data is contained in the packet. If you think you need more than one connection, consider your design a little bit longer. IP filters are most efficient the fewer rules are implemented. If an application requires only one connection, that’s one set of rules and fewer ways to misconfigure the firewall.

Don’t Require the Server to Connect Back to the Client

A good example of a firewall-unfriendly application is FTP. FTP has the server listening on TCP port 21, and the client will immediately tell the server to connect back on a high port (with a port number greater than 1024) from TCP port 20. If a firewall administrator is foolish enough to allow this, an attacker can set his source port to 20 and attack any server that listens on a high port. Notable examples of servers that an attacker might like to try to hack in this manner are Microsoft SQL Server at port 1433, Microsoft Terminal Server at port 3389, and X Window clients—the client and server relationship is reversed from the usual on the X Window system—on port 6000. If the firewall administrator sets the firewall just to deny external connections to these servers, inevitably some type of server will show up that wasn’t anticipated and will cause security problems. Don’t require your server to connect back to the client. This also complicates peer-to-peer communication. If the system my application is running on has a personal firewall, I’ll have a hard time establishing communications in both directions. It’s better if an application just listens on a single port and you invite others to connect.

Use Connection-Based Protocols

A connection-based protocol such as TCP is easier to secure than a connectionless protocol such as UDP. A good firewall or router can make rules based on whether the connection is established, and this property allows connections to be made from internal networks out to external hosts but never allows connections to originate from an external host to the internal network. A router rule that would let Domain Name System (DNS) clients function might look like this:

Allow internal UDP high port to external UDP port 53
Allow external UDP port 53 to internal UDP high port

This rule would also let an attacker set a source port of 53 and attack any other UDP service that is running on high ports on the internal network. A firewall administrator can properly deal with this problem in two ways. The first way would be to proxy the protocol across the firewall, and the second would be to use a stateful inspection firewall. As you might imagine from the name, a stateful inspection firewall maintains state. If it sees a request originate from the internal network, it expects a reply from a specific server on a specific port and will relay only the expected responses back to the internal network. There are sometimes good reasons to use connectionless protocols—under some conditions, better performance can be achieved— but if you have a choice, a connection-based protocol is easier to secure.

Don’t Multiplex Your Application over Another Protocol

Multiplexing another application on top of an established protocol doesn’t help security. Doing so makes your application difficult to regulate and can lead to security issues on both the client and the server as your application interacts in unexpected ways with the existing software. Usually, the rationale for multiplexing goes something like this: those nasty firewall administrators just don’t want to let your application run freely, so you’ll just run on top of some other application-layer protocol that is allowed. First of all, a good firewall administrator can still shut you down with content-level filters. You’ll find that in general, a properly written application will be allowed through a firewall. If you follow the rules presented here, you shouldn’t need to multiplex over an existing protocol. This is not to say that extending existing protocols is always a mistake. For example, if two Web servers are going to communicate with each other, it is entirely natural that they should do so over port 80 TCP.

Don’t Embed Host IP Addresses in Application-Layer Data

Until IPv6 becomes widely implemented, network address translation (NAT) and proxies are going to continue to be common and will probably be seen more often as the shortage of IPv4 addresses becomes more severe. If you embed host IP addresses in your application layer, your protocol is almost certainly going to break when someone tries to use it from behind a NAT device or proxy. The message here is simple: don’t embed host addresses in your protocol. Another good reason not to embed transport-layer information is that your application will break once your users move to IPv6.

Make Your Application Configurable

For various reasons, some customers will need to run your application on a port other than the one you thought should be the default. If you make your client and server both configurable, you give your customers the ability to flexibly deploy your software. It is possible that your port assignment will conflict with some other application. Some people practice security through obscurity—which generally doesn’t get them very far—security and obscurity is a more robust practice—and these people might think that running your service on an unusual port will help them be more secure.

Spoofing and Host-Based and Port-Based Trust

Spoofing describes an attack that involves three hosts: an attacker, a victim, and an innocent third party. The attacker would like to make the victim believe that a connection, information, or a request originated from the innocent system. Spoofing is trivially accomplished with connectionless protocols—all the attacker need do is identify a good host to use as the third party, tinker with the source address of the packets, and send the packets on their way.

One good example of a protocol that is vulnerable to spoofing is syslog. Syslog is commonly found on UNIX and UNIX-like systems and occasionally on Windows systems. It depends on UDP and can be configured to accept logs only from certain hosts. If an attacker can determine one of the preconfigured hosts, he can fill the logs with any information he likes.

Connection-based protocols are also vulnerable to spoofing attacks to some extent. A famous example of this is Kevin Mitnick’s use of rsh spoofing to hack Tsutomu Shimomura. Although most current operating systems are much more resistant to TCP spoofing than those in use several years ago, basing trust on information about the originating host isn’t a good idea. Another variant on host spoofing is DNS corruption. If DNS information can be corrupted, which isn’t too hard, and if you base trust in your application on thinking that the connection has come from somehost.niceguys.org, don’t be terribly surprised if one day you discover that the connection is really coming from destruction.evilhackers.org.

Important

If your application has a strong need to be sure who the client is, prove it with a shared secret, a certificate, or some other cryptographically strong method. Don’t assume, based on IP address or DNS name, that a host is who it claims to be.

A related problem is that of port-based trusts. A good example of this would be rsh, which depends on the fact that on UNIX systems, only high-level users—typically root—can bind to ports less than 1024. The thinking here is that if I trust a host and the connection is coming from a low port, the commands must be coming from the administrator of that system, whom I trust, so I’ll execute those commands. As it turns out, this scheme can fall apart in a number of different ways. The operating system on the other host might need some patches, so the user sending the requests might not be who we think the user should be. If some other operating system turns up that doesn’t restrict which ports normal users can use, that’s another way around this security method.

Unfortunately, it isn’t only older protocols that have fallen out of favor which use these flawed methods. I’ve seen current applications entrusted with sensitive data making many of the mistakes detailed in this section. Don’t make the same mistakes yourself. If it’s important to know who your clients or your servers are, force each to prove to the other who it really is in your application.

IPv6 Is Coming!

IPv6 is a new version of the IP protocol that fixes many of the problems we’ve encountered with the original implementation of the Internet Protocol (IPv4). One of the most noticeable features of IPv6 is that the address space is 128 bits wide. This will allow us to assign IP addresses to everything we have (and then some) without running out of address space. There are many interesting features of IPv6 and several good books on the subject, so I’ll just briefly cover a few issues here. IPv6 won’t change many of the issues I’ve covered in this chapter. It will give us an address space that is large enough that we should be able to have globally addressable IP addresses on just about every device we can think of. A thorough treatment of the new features of the IPv6 protocol is beyond the scope of this book, but if you’re already familiar with IPv4, check out IPv6: The New Internet Protocol, Second Edition (Prentice Hall PTR, 1998), by Christian Huitema. Christian was formerly Chair of the Internet Activities Board for the IETF and now works at Microsoft. IPv6 will ship as part of Microsoft Windows.NET Server 2003 and was included for Windows XP in Service Pack 1. Following are some items of interest.

A system running IPv6 might have several IP addresses at any given time. Included in IPv6 is the notion of anonymous IP addresses, which might come and go transiently. Thus, basing any notion of trust on an IP address in an IPv6 world isn’t going to work very well.

IPv6 addresses fit into one of three scopes—link local, site local, and global. If you intend for your application to be available only on a local subnet, you’ll be able to bind specifically to a link local IP address. Site local IP addresses are meant to be routed only within a given site or enterprise and cannot be routed globally. A new socket option will be available that will allow you to set the scope of a bound socket—something I think will be very cool.

IPv6 implementations must support Internet Protocol Security (IPSec). You can count on IPSec always being present when you’re dealing with IPv6. You still have various infrastructure issues to deal with, like how you want to negotiate a key, but instead of having to create your own packet privacy and integrity system, you have the option of configuring IPSec at install time. One of the possibilities that Christian mentions in his book is that vendors might create new options that would enable IPv6 on a socket at run time. I feel like this is a very good idea, but I’m not aware of any plans for Microsoft or anyone else to deliver it in the near future—perhaps this will change.

IPv6 will change the picture with respect to attackers. At the moment, I can scan the entire IPv4 Internet in a matter of days using a reasonably small number of systems. It’s not feasible to scan even the lower 64-bit local portion of a IPv6 address space in a reasonable amount of time given current bandwidth and packet-rate constraints. Likewise, keeping a hash table of client addresses could get very large and even subject you to denial of service attacks.

Summary

We’ve covered how to bind your server sockets in order to avoid local hijacking attacks. Look for things to get easier in this area once Windows .NET Server 2003 ships. When designing a server application, spend some time deciding how you’ll let users determine which network interfaces your server listens on and whether your application should use conditional accept.

One of the most important topics of this chapter concerns writing an application to deal properly with firewalls. It is my opinion that we’re going to see a proliferation of firewalls, especially personal firewalls. If your application works well with firewalls, you’ll be ready for this trend.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.68.18