Accepting Connections

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 on the companion CD in the folder Secureco\Chapter 9\ AcceptConnection 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\n", 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\n", inet_ntoa(from.sin_addr)); //Now decide what to do with the connection; //really lame decision criteria - we'll just take //every other one. if(conns % 2 == 0) { printf("We like this client.\n"); //Pretend to do some processing here. } else { printf("Go away!\n"); } closesocket(sock2); } } else { //Error printf("Listen failed - err = %d\n", 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.

Now let s examine another way to do the same thing: by using the WSAAccept function. When combined 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\n", 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\n", 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\n", 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\n", inet_ntoa(from.sin_addr)); //Pretend to do some processing here. closesocket(sock2); } } else { //Error printf("Listen failed - err = %d\n", GetLastError()); break; } //Insert your own code here to decide when to shut down the 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.



Writing Secure Code
Writing Secure Code, Second Edition
ISBN: 0735617228
EAN: 2147483647
Year: 2005
Pages: 153

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