Effective Python Penetration Testing
上QQ阅读APP看书,第一时间看更新

Sockets modules

Network sockets is a way to talk to other computers using standard Unix file descriptors, which allow communication between two different processes on the same or different machines. A socket is almost similar to a low-level file descriptor, because commands such as read() and write() also work with sockets as they do with files.

Python has two basic sockets modules:

  • Socket: The standard BSD sockets API.
  • SocketServer: A server-centric module that defines classes for handling synchronous network requests that simplify the development of network servers.

Socket

The socket module has almost everything you need to build a socket server or client. In the case of Python, the socket returns an object to which the socket methods can be applied.

Methods in socket module

The socket module has the following class methods:

  • socket.socket(family, type): Create and return a new socket object
  • socket.getfqdn(name): Convert a string IP address to a fully qualified domain name
  • socket.gethostbyname(hostname): Resolve a hostname to an IP address

Instance methods require a socket instance returned from socket. The socket module has the following instance methods:

  • sock.bind( (address, port) ): Bind the socket to the address and port
  • sock.accept(): Return a client socket with peer address information
  • sock.listen(backlog): Place the socket into the listening state
  • sock.connect( (address, port) ): Connect the socket to the defined host and port
  • sock.recv( bufferLength[, flags] ): Receive data from the socket, up to buflen (maximum bytes to receive) bytes
  • sock.recvfrom( bufferLength[, flags] ): Receive data from the socket, up to buflen bytes, also returning the remote host and port from which the data came
  • sock.send( data[, flags] ): Send data through the socket
  • sock.sendall( data[, flags] ): Send data through the socket, and continues to send data until either all data has been sent or an error occurred
  • sock.close(): Close the socket
  • sock.getsockopt( lvl, optname ): Get the value for the specified socket option
  • sock.setsockopt( lvl, optname, val ): Set the value for the specified socket option

Creating a socket

A socket can be created by making a call to the class method socket() in the socket module. This will return a socket in the domain specified. The parameters to the method are as follows:

  • Address family: Python supports three address families.
    • AF_INET: Used for IP version 4 or IPv4 Internet addressing.
    • AF_INET6: Used for IPv6 Internet addressing.
    • AF_UNIX: Used for UNIX domain sockets (UDS).
  • Socket type: Usually, socket type can be either SOCK_DGRAM for User Datagram Protocol (UDP) or SOCK_STREAM for Transmission Control Protocol (TCP). SOCK_RAW is used to create raw sockets.
  • Protocol: Generally left at the default value. Default value is 0.

The following is an example for creating a socket:

import socket #Imported sockets module 
import sys 
try: 
   #Create an AF_INET (IPv4), STREAM socket (TCP) 
   tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
except socket.error, e: 
   print 'Error occurred while creating socket. Error code: ' + str(e[0]) + ' , Error message : ' + e[1] 
   sys.exit(); 
print 'Success!' 

Connecting to a server and sending data

The socket created can be used in both server-side or client-side.

The connect() method of socket object is used to connect the client to a host. This instance method accepts either the host name or a tuple, which contains the host name/address and port number as a parameter.

We can rewrite the preceding code to send a message to the server as follows:

import socket #Imported sockets module  
import sys  
  
TCP_IP = '127.0.0.1'  
TCP_PORT = 8090 #Reserve a port  
BUFFER_SIZE = 1024  
MESSAGE_TO_SERVER = "Hello, World!"  
 
try:  
    #Create an AF_INET (IPv4), STREAM socket (TCP)  
    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
except socket.error, e:  
    print 'Error occurred while creating socket. Error code: ' + str(e[0]) + ' , Error message : ' + e[1] 
    sys.exit();  
 
tcp_socket.connect((TCP_IP, TCP_PORT))  
 
try :  
    #Sending message  
    tcp_socket.send(MESSAGE_TO_SERVER)  
except socket.error, e: 
    print 'Error occurred while sending data to server. Error code: ' + str(e[0]) + ' , Error message : ' + e[1] 
    sys.exit()  
 
print 'Message to the server send successfully' 

Receiving data

We need a server to receive data. To use a socket on the server side, the bind() method of the socket object binds a socket to an address. It takes a tuple as the input parameter, which contains the address to the socket and the port to listen for incoming requests. The listen() method puts the socket into listening mode and the method accept() waits for an incoming connection. The listen() method accepts a parameter representing the maximum number of queued connections. So by specifying this parameter to 3, it means that if three connections are waiting to process, then the fourth connection will be rejected:

import socket #Imported sockets module 
 
TCP_IP = '127.0.0.1' 
TCP_PORT = 8090 
BUFFER_SIZE = 1024 #Normally use 1024, to get fast response from the server use small size 
 
try: 
   #Create an AF_INET (IPv4), STREAM socket (TCP) 
   tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
except socket.error, e: 
   print 'Error occurred while creating socket. Error code: ' + str(e[0]) + ' , Error message : ' + e[1] 
   sys.exit(); 
 
tcp_socket.bind((TCP_IP, TCP_PORT)) 
# Listen for incoming connections  (max queued connections: 2) 
tcp_socket.listen(2) 
print 'Listening..' 
 
#Waits for incoming connection (blocking call) 
connection, address = tcp_socket.accept() 
print 'Connected with:', address 

Method accept() will return an active connection between the server and client. Data can be read from the connection using the recv() method, and can be transmitted using sendall():

data = connection.recv(BUFFER_SIZE) 
print "Message from client:", data 
 
connection.sendall("Thanks for connecting")  # response for the message from client 
connection.close() 

It would be better to keep the server live by putting socket_accept in a loop, as follows:

#keep server alive  
while True:  
   connection, address = tcp_socket.accept()  
   print 'Client connected:', address  
 
   data = connection.recv(BUFFER_SIZE)  
   print "Message from client:", data  
 
   connection.sendall("Thanks for connecting")  #Echo the message from client  

Save this to server.py and start the server as follows in a terminal:

 $ python server.py

Then server terminal might look like the following:

Now we can modify the client script to receive a response from the server:

import socket #Imported sockets module  
import sys  
 
TCP_IP = '127.0.0.1'  
TCP_PORT = 8090 # Reserve a port  
BUFFER_SIZE = 1024  
MESSAGE_TO_SERVER = "Hello, World!"  
 
try:  
    #Create an AF_INET (IPv4), STREAM socket (TCP)  
    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
except socket.error,  e:  
    print 'Error occured while creating socket. Error code: ' + str(e[0]) + ' , Error message : ' + e[1] 
    sys.exit();  
 
tcp_socket.connect((TCP_IP, TCP_PORT))  
 
try :  
    #Sending message  
    tcp_socket.send(MESSAGE_TO_SERVER)  
except socket.error, e: 
    print 'Error occurred while sending data to server. Error code: ' + str(e[0]) + ' , Error message : ' + e[1] 
    sys.exit() 
 
print 'Message to the server send successfully'  
data = tcp_socket.recv(BUFFER_SIZE)  
tcp_socket.close() #Close the socket when done  
print "Response from server:", data 

Save this to client.py and run. Please make sure the server script is running. The client-side terminal might look like the following:

Handling multiple connections

In the previous example, we used the while loop to handle different clients; this can only interact with one client at a time. To make the server interact with multiple clients, we have to use multi-threading. When the main program accepts a connection, it creates a new thread to handle communication for this connection, and then goes back to accept more connections.

We can use the threads module to create thread handlers for each connection that the server accepts.

start_new_thread() takes two arguments:

  • A function name to be run
  • A tuple of arguments to that function

Let's see how we can rewrite the preceding example with threads:

import socket #Imported sockets module  
import sys  
from thread import *  
 
TCP_IP = '127.0.0.1'  
TCP_PORT = 8090 # Reserve a port  
 
try:  
    #create an AF_INET (IPv4), STREAM socket (TCP)  
    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
except socket.error, e:  
    print 'Error occured while creating socket. Error code: ' + str(e[0]) + ' , Error message : ' + e[1] 
    sys.exit();  
 
#Bind socket to host and port  
tcp_socket.bind((TCP_IP, TCP_PORT))  
tcp_socket.listen(10)  
print 'Listening..'  
 
#Function for handling connections. Used to create threads  
def ClientConnectionHandler(connection):  
    BUFFER_SIZE = 1024  
    #Sending message to client  
    connection.send('Welcome to the server')  
         
    #infinite loop to keep the thread alive.  
    while True:  
        #Receiving data from client  
        data = connection.recv(BUFFER_SIZE)  
        reply = 'Data received:' + data  
        if not data:  
            break  
        connection.sendall(reply)  
 
    #Exiting loop  
    connection.close()  
  
#keep server alive always (infinite loop)  
while True:  
    connection, address = tcp_socket.accept()  
    print 'Client connected:', address  
    start_new_thread(ClientConnectionHandler ,(connection,))  
 
tcp_socket.close() 

Tip

For more details on socket modules, go to https://docs.python.org/2.7/library/socket.html.

SocketServer

SocketServer is an interesting module, which is a framework for creating network servers. It has pre-defined classes for handling synchronous requests using TCP, UDP, UNIX streams, and UNIX datagrams. We can also create forking and threading versions of each type of server using the mix-in classes. In many cases, you can simply use one of the existing server classes. Five different server classes defined in SocketServer module are as follows:

  • BaseServer: Defines the API, not used directly
  • TCPServer: Uses TCP/IP sockets
  • UDPServer: Uses datagram sockets
  • UnixStreamServer: Unix-domain stream sockets
  • UnixDatagramServer: Unix-domain datagram sockets

To construct a server with this module, we have to pass the address to listen (a tuple consisting of the address and port number) and a request handler class. Request handlers will receive incoming requests and decide what action to take. This class must have a method, which overrides any of the following RequestHandler methods; mostly, we can simply override a handle() method. A new instance of this class is created for each and every request:

  • setup(): Called before the handle() method to prepare the request handler for the request
  • handle(): Parses the incoming requests, processes the data, and responds to the requests
  • finish(): Called after the handle() method to clean up anything created during setup()

Simple server with the SocketServer module

The following script shows how we can use SocketServer to create a simple echo server:

import SocketServer #Imported SocketServer module  
 
#The RequestHandler class for our server.  
class TCPRequestHandler( SocketServer.StreamRequestHandler ):  
  def handle( self ):  
   self.data = self.request.recv(1024).strip()  
   print "{} wrote:".format(self.client_address[0])  
   print self.data  
   #Sending the same data  
   self.request.sendall(self.data)  
 
#Create the server, binding to localhost on port 8090  
server = SocketServer.TCPServer( ("", 8090), TCPRequestHandler ) 
#Activate the server; this will keep running untile we interrupt 
server.serve_forever() 

The first line of the script imports the SocketServer module:

import SocketServer 

Then we created a request handler that inherits the SocketServer.StreamRequestHandler class and overrides the handle() method to handle the requests for the server. The method handle() receives the data, prints it, and then responds the same to the client:

class TCPRequestHandler( SocketServer.StreamRequestHandler ):  
  def handle( self ):  
   self.data = self.request.recv(1024).strip()  
   print "{} wrote:".format(self.client_address[0])  
   print self.data  
   # sending the same data  
   self.request.sendall(self.data) 

This request handler class is instantiated for every request to the server. This server is created using the SocketServer.TCPServer class, where we provide the address to which the server will be bound and request the handler class. It will return a TCPServer object. Finally, we called the serve_forever() method to start the server and handle requests until we send an explicit shutdown() request (keyboard interrupt):

tcp_server = SocketServer.TCPServer( ("", 8090), TCPRequestHandler )  
tcp_server.serve_forever() 

Tip

For more details on Socket module, go to http://xahlee.info/python_doc_2.7.6/library/socketserver.html.