Exploring Cybersecurity: Building a Secure TCP Chat Server in Python

 

Introduction:

In my ongoing cybersecurity discovery through hands-on projects, I've been delving into fundamental concepts of security to deepen my understanding and enhance my skills. So far, I have created a basic TCP and UPD server. Let's now dive further into the world of network security by building a TCP chat server in Python. My goal is to not only create a functional chat server but also to gradually fortify it with advanced security measures, transforming it into a secure communication platform.

 

Conception:

Each project that we create in this series is a stepping stone towards deeper understanding of the fundamentals of cybersecurity, and building a TCP chat server in Python is the next logical progression. It's a chance to explore network communication, encryption, and authentication in a more practical way. Not to mention that it gives us a perfect sandbox to find exploitative vulnerabilities and how to prevent them.

This project began with laying the groundwork for our chat server. I started by implementing a basic server-client architecture where multiple clients could connect to the server and exchange messages.

 

The Code:

Let's take a closer look at the code for our TCP chat server:

server.py

#!/usr/bin/python3

import socket
import threading

clients = []
addresses = {}

# Handle client connections
def handle_client(client_socket, client_address):
    # Add client to list
    clients.append(client_socket)

    # Receive and broadcast msgs
    while True:
        try:
            message = client_socket.recv(1024).decode('utf-8')
            if message:
                broadcast(message, client_socket)
            else:
                print(f"Client {client_address} disconnected.")
                break
        except Exception as e:
            print(f"Error: {e}")
            client_socket.close()
            clients.remove(client_socket)
            print(f"Client {client_address} disconnected.")
            break

# Broadcast messages to all clients
def broadcast(message, sender_socket):
    for client_socket in clients:
        if client_socket != sender_socket:
            try:
                client_socket.send(message.encode('utf-8'))
            except Exception as e:
                print(f"Error: {e}")
                client_socket.close()
                clients.remove(client_socket)

def main():
    try:
        # Set up server as a socket
        server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server_socket.bind(('127.0.0.1', 8010))
        server_socket.listen(5)
        print("Chat server started...")

        # Accept client connections
        while True:
            if server_socket._closed:
                break
            client_socket, client_address = server_socket.accept()
            print(f"Connection from {client_address}")
            client_handler = threading.Thread(target=handle_client, args=(client_socket, client_address))
            client_handler.start()
    except KeyboardInterrupt:
        print("\nServer shutting down...")
        for client_socket in clients:
            client_socket.close()
        server_socket.close()

if __name__ == "__main__":
    main()

 

  • - The server code sets up a TCP socket using socket.socket() with AF_INET family for communicating over the internet using IPv4 addresses and SOCK_STREAM type to create a stream socket which is used for TCP.
  • - It binds the socket to the localhost ('127.0.0.1') on port 8010 using server_socket.bind().
  • - The server listens for up to five incoming connections using server_socket.listen(5).
  • - In the handle_client function, each connected client is added to a list of clients.
  • - Messages from clients are received in a loop with client_socket.recv().
  • - Received messages are broadcasted to all other connected clients using the broadcast function.
  • - A separate thread is created for each client using threading. This allows clients to not have to take turns when performing operations.
  • - The main() function continuously accepts incoming connections.

 

Additionally, we have client scripts that allow users to connect to the server and exchange messages. Here is the client code:

client1.py

#!/etc/bin/python3

import socket
import threading

# Receive message from server
def receive_messages(client_socket):
    try:
        while True:
            message = client_socket.recv(1024).decode('utf-8')
            print("\r" + message + "\n > ", end="", flush=True)
    except Exception as e:
        print(f"Error: {e}")
    finally:
        client_socket.close()

def main():
    try:
        # Connect to server
        client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        client_socket.connect(('127.0.0.1', 8010))

        # Receive messages in thread
        receive_thread = threading.Thread(target=receive_messages, args=(client_socket,))
        receive_thread.start()

        # Send messages to server
        while True:
            message = input(" > ")
            client_socket.send(message.encode('utf-8'))

    except KeyboardInterrupt:
        print("\nClient shutting down, press ctrl + c to close...")
        client_socket.close()
        try:
            receive_thread.join()
        except KeyboardInterrupt:
            print("\nClient closed")
            pass

if __name__ == "__main__":
    main()

 

  • - The client code also sets up a TCP socket using socket.socket() with AF_INET family and SOCK_STREAM type.
  • - It connects to the server at localhost ('127.0.0.1') on port 8010 using client_socket.connect().
  • - A separate thread is created to receive messages from the server continuously in the receive_messages function.
  • - Messages received from the server are printed to the console.
  • - The main thread continuously prompts the user for input and sends messages to the server using client_socket.send().

 

TCP Chat Server Code

 

Example Run:

Let's walk through a sample interaction between the server and clients. For the sake of this example, I have two separate client files with the same contents called client1.py and client2.py:

# Example run from server perspective:
Chat server started...
Connection from ('127.0.0.1', 51072)
Connection from ('127.0.0.1', 51082)

# Example run from client1 perspective:
> Hello from client 1.
Hello from client 2!

# Example run from client2 perspective:
Hello from client 1.
> Hello from client 2!

 

Future Plans:

While our chat server currently allows communication between clients, it lacks robust security measures. Moving forward, we aim to address this by:

  

  • Exploring Message Interception: Investigating potential vulnerabilities and methods of discreetly intercepting messages from an attacker's perspective.
  • Implementing Encryption: Integrating encryption mechanisms such as SSL/TLS to ensure confidentiality.
  • Establishing Secure Chat Rooms: Creating secure chat rooms with authentication and authorization mechanisms.
  • Continuously Improving Security: Iteratively enhancing our chat server to stay ahead of emerging threats and vulnerabilities.

 

Conclusion:

As I continue to learn and experiment, I'm excited to share progress and insights. Stay tuned for future blog posts where I will delve deeper into each aspect of securing our TCP chat server. Together, let's continue on this journey towards building a robust and secure communication platform, developing and learning about future security-related projects, and learning as much about the fundamentals of cybersecurity as we can. Happy coding!