diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..85e7c1d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.idea/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..7a7d32d --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# Student Instructions + +This project is a tool for scanning open (or closed) ports for a given IP address. In its current state, it is +incomplete, and you are tasked with aiding in the completion of the project. + +In order for you to complete this project, you will need to learn some new things along the way. Most notably, you will +need to know the following: + +- Basic Python +- Virtual Environments +- PyDoc +- PyTest +- Basic Linux Command Line Input (CLI) + +## Instructions + +For a detailed overview on what is expected to be undertaken for this project, please follow the instructions at the +following URL: + +- [Project: Port Scanner](https://github.coventry.ac.uk/pages/CUEH/4061CEM/labs/projects/port_scanner/) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..abcb504 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +wheel +colored +pytest +pdoc3 \ No newline at end of file diff --git a/src/scanner.py b/src/scanner.py new file mode 100644 index 0000000..206e5e7 --- /dev/null +++ b/src/scanner.py @@ -0,0 +1,30 @@ +#!python +"""Simple program for scanning ports on a given host and acting on results""" + +from sockets import test_port +import colored + +if __name__ == "__main__": + target = "172.17.0.2" + + for p in range(1, 100): + + message = colored.fg("red") + "Closed" + + result = test_port(target, p) + + if not result is None: + + message = colored.fg("green") + "Open" + + if len(result) > 0: + message += colored.fg("yellow") + message += " - Data received" + + for i in result: + message += "\n" + i + + message += "\n" + + message += colored.attr('reset') + print(f"{p}: {message}") diff --git a/src/sockets.py b/src/sockets.py new file mode 100644 index 0000000..b1d111d --- /dev/null +++ b/src/sockets.py @@ -0,0 +1,56 @@ +import socket +import select +import time + + +def check_status(sock): + """ Return the number of bytes ready to be read from a socket + + Args: + sock (socket): the socket to test for data + + Returns: + int: the number of bytes ready to be read. + """ + ready_to_read, ready_to_write, in_error = select.select([sock, ], [sock, ], [], 5) + return len(ready_to_read) + + +def test_port(host: str, portnum: int, poke=None): + """Given a host (IP or name) and a port number, connect if possible and return any data transmitted. + + Args: + host (string): the host to scan. Can be an IP address or hostname + portnum (int): the port number, between 0 and 65535 + poke (string): if given, the string to send to the server upon connection. + + Returns: + list of strings or None: the data returned by the connection, or None if the connection failed. If a list is returned, it represents the sequence of responses. The first element is the reponse recieved immediately, the second is the response after sending any given data. + """ + response = [] + + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + result = sock.connect_ex((host, portnum)) + if result == 0: # 0 means no error + time.sleep(0.1) # Give the server time to send + if check_status(sock) > 0: + rcv = sock.recv(1024) + response.append(rcv.decode("utf-8", "ignore")) + if poke != None: + sock.sendall(str.encode(poke)) + time.sleep(1) # Give the server time to send + ready_to_read, ready_to_write, in_error = select.select([sock, ], [sock, ], [], 5) + if check_status(sock) > 0: + rcv = sock.recv(1024) + response.append(rcv.decode("utf-8", "ignore")) + + sock.shutdown(socket.SHUT_RDWR) + sock.close() + + return response + else: + return None + except socket.error: + return None if response is [] else response + return None diff --git a/src/web_scan.py b/src/web_scan.py new file mode 100644 index 0000000..dca178c --- /dev/null +++ b/src/web_scan.py @@ -0,0 +1,33 @@ +#!python +"""A simple scanner for HTTP servers""" + +from sockets import test_port +import colored + +if __name__ == "__main__": + target = "172.17.0.2" + + # Ckeck a range of ports + maxPort = 85 + minPort = 75 + + # How many characters to display from the response + maxResponse = 500 + for p in range(minPort, maxPort): + message = "Closed" + + result = test_port(target, p, poke="GET /index.html HTTP/1.0\r\n\r\n") + if not result is None: + message = "Open" + if len(result) > 0: + message += f": (response follows)\n" + response = "" + for i in result: + response += colored.fg("green") + i + "\n" + if len(response) > maxResponse: + # Trim it down if it's too long + response = response[:maxResponse] + response += colored.attr("reset") + message += response + + print(f"{p}: {message}")