From 5da7e57474d2d486b2b11864a18daccc1ac71e7c Mon Sep 17 00:00:00 2001 From: Tib3rius <48113936+Tib3rius@users.noreply.github.com> Date: Fri, 1 Mar 2019 18:58:19 -0500 Subject: [PATCH] Initial commit --- README.md | 354 ++++++++++++++++++++++- autorecon.py | 604 ++++++++++++++++++++++++++++++++++++++++ port-scan-profiles.toml | 39 +++ requirements.txt | 2 + service-scans.toml | 427 ++++++++++++++++++++++++++++ 5 files changed, 1425 insertions(+), 1 deletion(-) create mode 100644 autorecon.py create mode 100644 port-scan-profiles.toml create mode 100644 requirements.txt create mode 100644 service-scans.toml diff --git a/README.md b/README.md index 6392abd..2679731 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,354 @@ # AutoRecon -AutoRecon is a multi-threaded network reconnaissance tool which performs automated enumeration of services. + +AutoRecon is a multi-threaded network reconnaissance tool which performs automated enumeration of services. It is intended as a time-saving tool for use in CTFs and other penetration testing environments (e.g. OSCP). It may also be useful in real-world engagements. + +The tool works by firstly performing port scans / service detection scans. From those initial results, the tool will launch further enumeration scans of those services using a number of different tools. For example, if HTTP is found, nikto will be launched (as well as many others). + +Everything in the tool is highly configurable. The default configuration performs **no automated exploitation** to keep the tool in line with OSCP exam rules. If you wish to add automatic exploit tools to the configuration, you do so at your own risk. The author will not be held responsible for negative actions that result from the mis-use of this tool. + +## Origin + +AutoRecon was inspired by three tools which the author used during the OSCP labs: [Reconnoitre](https://github.com/codingo/Reconnoitre), [ReconScan](https://github.com/RoliSoft/ReconScan), and [bscan](https://github.com/welchbj/bscan). While all three tools were useful, none of the three alone had the functionality desired. AutoRecon combines the best features of the aforementioned tools while also implementing many new features to help testers with enumeration of multiple targets. + +## Features + +* Supports multiple targets in the form of IP addresses, IP ranges (CIDR notation), and resolvable hostnames. +* Can scan targets concurrently, utilizing multiple processors. +* Customizable port scanning profiles for flexibility in your initial scans. +* Customizable service enumeration commands and suggested manual follow-up commands. +* An intuitive directory structure for results gathering. +* Full logging of commands that were run. + +## Requirements + +* Python 3 +* colorama +* toml + +Once Python 3 is installed, pip3 can be used to install the other requirements: + +```bash +$ pip3 install -r requirements.txt +``` + +Several commands used in AutoRecon reference the SecLists project, in the directory /usr/share/seclists/. You can either manually download the SecLists project to this directory (https://github.com/danielmiessler/SecLists), or if you are using Kali Linux (**highly recommended**) you can run the following: + +```bash +$ sudo apt install seclists +``` + +AutoRecon will still run if you do not install SecLists, though several commands may fail, and some manual commands may not run either. + +## Usage + +AutoRecon uses Python 3 specific functionality and does not support Python 2. + +``` +usage: autorecon.py [-h] [-ct ] [-cs ] [--profile PROFILE] + [-v] [-o OUTPUT] [--disable-sanity-checks] + targets [targets ...] + +Network reconnaissance tool to port scan and automatically enumerate services +found on multiple targets. + +positional arguments: + targets IP addresses (e.g. 10.0.0.1), CIDR notation (e.g. + 10.0.0.1/24), or resolvable hostnames (e.g. foo.bar) + to scan. + +optional arguments: + -h, --help show this help message and exit + -ct , --concurrent-targets + The maximum number of target hosts to scan + concurrently. Default: 5 + -cs , --concurrent-scans + The maximum number of scans to perform per target + host. Default: 10 + --profile PROFILE The port scanning profile to use (defined in port- + scan-profiles.toml). + -v, --verbose enable verbose output, repeat for more verbosity + -o OUTPUT, --output OUTPUT + output directory for the results + --disable-sanity-checks + Disable sanity checks that would otherwise prevent the + scans from running. +``` + +### Examples + +**Scanning a single target:** + +``` +python3 autorecon.py 127.0.0.1 +[*] Scanning target 127.0.0.1 +[*] Running service detection nmap-full-tcp on 127.0.0.1 +[*] Running service detection nmap-top-20-udp on 127.0.0.1 +[*] Running service detection nmap-quick on 127.0.0.1 +[*] Service detection nmap-quick on 127.0.0.1 finished successfully +[*] [127.0.0.1] ssh found on tcp/22 +[*] [127.0.0.1] http found on tcp/80 +[*] [127.0.0.1] rpcbind found on tcp/111 +[*] [127.0.0.1] postgresql found on tcp/5432 +[*] Running task tcp/22/nmap-ssh on 127.0.0.1 +[*] Running task tcp/80/nmap-http on 127.0.0.1 +[*] Running task tcp/80/curl-index on 127.0.0.1 +[*] Running task tcp/80/curl-robots on 127.0.0.1 +[*] Running task tcp/80/whatweb on 127.0.0.1 +[*] Running task tcp/80/nikto on 127.0.0.1 +[*] Running task tcp/111/nmap-nfs on 127.0.0.1 +[*] Task tcp/80/curl-index on 127.0.0.1 finished successfully +[*] Task tcp/80/curl-robots on 127.0.0.1 finished successfully +[*] Task tcp/22/nmap-ssh on 127.0.0.1 finished successfully +[*] Task tcp/80/whatweb on 127.0.0.1 finished successfully +[*] Task tcp/111/nmap-nfs on 127.0.0.1 finished successfully +[*] Task tcp/80/nmap-http on 127.0.0.1 finished successfully +[*] Task tcp/80/nikto on 127.0.0.1 finished successfully +[*] Service detection nmap-top-20-udp on 127.0.0.1 finished successfully +[*] Service detection nmap-full-tcp on 127.0.0.1 finished successfully +[*] [127.0.0.1] http found on tcp/5984 +[*] [127.0.0.1] rtsp found on tcp/5985 +[*] Running task tcp/5984/nmap-http on 127.0.0.1 +[*] Running task tcp/5984/curl-index on 127.0.0.1 +[*] Running task tcp/5984/curl-robots on 127.0.0.1 +[*] Running task tcp/5984/whatweb on 127.0.0.1 +[*] Running task tcp/5984/nikto on 127.0.0.1 +[*] Task tcp/5984/curl-index on 127.0.0.1 finished successfully +[*] Task tcp/5984/curl-robots on 127.0.0.1 finished successfully +[*] Task tcp/5984/whatweb on 127.0.0.1 finished successfully +[*] Task tcp/5984/nikto on 127.0.0.1 finished successfully +[*] Task tcp/5984/nmap-http on 127.0.0.1 finished successfully +[*] Finished scanning target 127.0.0.1 +``` + +The default port scan profile first performs a full TCP port scan, a top 20 UDP port scan, and a top 1000 TCP port scan. You may ask why AutoRecon scans the top 1000 TCP ports at the same time as a full TCP port scan (which also scans those ports). The reason is simple: most open ports will generally be in the top 1000, and we want to start enumerating services quickly, rather than wait for Nmap to scan every single port. As you can see, all the service enumeration scans actually finish before the full TCP port scan is done. While there is a slight duplication of efforts, it pays off by getting actual enumeration results back to the tester quicker. + +Note that the actual command line output will be colorized if your terminal supports it. + +**Scanning multiple targets** + +``` +python3 autorecon.py 192.168.1.100 192.168.1.1/30 localhost +[*] Scanning target 192.168.1.100 +[*] Scanning target 192.168.1.1 +[*] Scanning target 192.168.1.2 +[*] Scanning target localhost +[*] Running service detection nmap-quick on 192.168.1.100 +[*] Running service detection nmap-quick on localhost +[*] Running service detection nmap-top-20-udp on 192.168.1.100 +[*] Running service detection nmap-quick on 192.168.1.1 +[*] Running service detection nmap-quick on 192.168.1.2 +[*] Running service detection nmap-top-20-udp on 192.168.1.1 +[*] Running service detection nmap-full-tcp on 192.168.1.100 +[*] Running service detection nmap-top-20-udp on localhost +[*] Running service detection nmap-top-20-udp on 192.168.1.2 +[*] Running service detection nmap-full-tcp on localhost +[*] Running service detection nmap-full-tcp on 192.168.1.1 +[*] Running service detection nmap-full-tcp on 192.168.1.2 +... +``` + +AutoRecon supports multiple targets per scan, and will expand IP ranges provided in CIDR notation. By default, only 5 targets will be scanned at a time, with 10 scans per target. + +**Scanning multiple targets with advanced options** + +``` +python3 autorecon.py -ct 2 -cs 2 -v -o outputdir 192.168.1.100 192.168.1.1/30 localhost +[*] Scanning target 192.168.1.100 +[*] Scanning target 192.168.1.1 +[*] Running service detection nmap-quick on 192.168.1.100 with nmap -vv --reason -Pn -sV -sC --version-all -oN "/root/outputdir/192.168.1.100/scans/_quick_tcp_nmap.txt" -oX "/root/outputdir/192.168.1.100/scans/_quick_tcp_nmap.xml" 192.168.1.100 +[*] Running service detection nmap-quick on 192.168.1.1 with nmap -vv --reason -Pn -sV -sC --version-all -oN "/root/outputdir/192.168.1.1/scans/_quick_tcp_nmap.txt" -oX "/root/outputdir/192.168.1.1/scans/_quick_tcp_nmap.xml" 192.168.1.1 +[*] Running service detection nmap-top-20-udp on 192.168.1.100 with nmap -vv --reason -Pn -sU -A --top-ports=20 --version-all -oN "/root/outputdir/192.168.1.100/scans/_top_20_udp_nmap.txt" -oX "/root/outputdir/192.168.1.100/scans/_top_20_udp_nmap.xml" 192.168.1.100 +[*] Running service detection nmap-top-20-udp on 192.168.1.1 with nmap -vv --reason -Pn -sU -A --top-ports=20 --version-all -oN "/root/outputdir/192.168.1.1/scans/_top_20_udp_nmap.txt" -oX "/root/outputdir/192.168.1.1/scans/_top_20_udp_nmap.xml" 192.168.1.1 +[-] [192.168.1.1 nmap-quick] Starting Nmap 7.70 ( https://nmap.org ) at 2019-03-01 17:25 EST +[-] [192.168.1.100 nmap-quick] Starting Nmap 7.70 ( https://nmap.org ) at 2019-03-01 17:25 EST +[-] [192.168.1.100 nmap-top-20-udp] Starting Nmap 7.70 ( https://nmap.org ) at 2019-03-01 17:25 EST +[-] [192.168.1.1 nmap-top-20-udp] Starting Nmap 7.70 ( https://nmap.org ) at 2019-03-01 17:25 EST +[-] [192.168.1.1 nmap-quick] NSE: Loaded 148 scripts for scanning. +[-] [192.168.1.1 nmap-quick] NSE: Script Pre-scanning. +[-] [192.168.1.1 nmap-quick] NSE: Starting runlevel 1 (of 2) scan. +[-] [192.168.1.1 nmap-quick] Initiating NSE at 17:25 +[-] [192.168.1.1 nmap-quick] Completed NSE at 17:25, 0.00s elapsed +[-] [192.168.1.1 nmap-quick] NSE: Starting runlevel 2 (of 2) scan. +[-] [192.168.1.1 nmap-quick] Initiating NSE at 17:25 +[-] [192.168.1.1 nmap-quick] Completed NSE at 17:25, 0.00s elapsed +[-] [192.168.1.1 nmap-quick] Initiating ARP Ping Scan at 17:25 +[-] [192.168.1.100 nmap-quick] NSE: Loaded 148 scripts for scanning. +[-] [192.168.1.100 nmap-quick] NSE: Script Pre-scanning. +[-] [192.168.1.100 nmap-quick] NSE: Starting runlevel 1 (of 2) scan. +[-] [192.168.1.100 nmap-quick] Initiating NSE at 17:25 +[-] [192.168.1.100 nmap-quick] Completed NSE at 17:25, 0.00s elapsed +[-] [192.168.1.100 nmap-quick] NSE: Starting runlevel 2 (of 2) scan. +[-] [192.168.1.100 nmap-quick] Initiating NSE at 17:25 +[-] [192.168.1.100 nmap-quick] Completed NSE at 17:25, 0.00s elapsed +[-] [192.168.1.100 nmap-quick] Initiating ARP Ping Scan at 17:25 +... +``` + +In this example, the -ct option limits the number of concurrent targets to 2, and the -cs option limits the number of concurrent scans per target to 2. The -v option makes the output verbose, showing the output of every scan being run. The -o option sets a custom output directory for scan results to be saved. + +### Results + +By default, results will be stored in the ./results directory. A new sub directory is created for every target. The structure of this sub directory is: + +``` +. +├── exploit/ +├── loot/ +├── report/ +│   ├── local.txt +│   ├── notes.txt +│   ├── proof.txt +│   └── screenshots/ +└── scans/ + ├── _commands.log + └── _manual_commands.txt +``` + +The exploit directory is intended to contain any exploit code you download / write for the target. + +The loot directory is intended to contain any loot (e.g. hashes, interesting files) you find on the target. + +The report directory contains some auto-generated files and directories that are useful for reporting: +* local.txt can be used to store the local.txt flag found on targets. +* notes.txt should contain a basic template where you can write notes for each service discovered. +* proof.txt can be used to store the proof.txt flag found on targets. +* The screenshots directory is intended to contain the screenshots you use to document the exploitation of the target. + +The scans directory is where all results from scans performed by AutoRecon will go. This includes port scans / service detection scans, as well as any service enumeration scans. It also contains two other files: +* \_commands.log contains a list of every command AutoRecon ran against the target. This is useful if one of the commands fails and you want to run it again with modifications. +* \_manual_commands.txt contains any commands that are deemed "too dangerous" to run automatically, either because they are too intrusive, require modification based on human analysis, or just work better when there is a human monitoring them. + +If a scan results in an error, a file called \_errors.log will also appear in the scans directory with some details to alert the user. + +### Port Scan profiles + +The port-scan-profiles.toml file is where you can define the initial port scans / service detection commands. The configuration file uses the TOML format, which is explained here: https://github.com/toml-lang/toml + +Here is an example profile called "quick": + +```toml +[quick] + + [quick.nmap-quick] + + [quick.nmap-quick.service-detection] + command = 'nmap -vv --reason -Pn -sV --version-all -oN "{scandir}/_quick_tcp_nmap.txt" -oX "{scandir}/_quick_tcp_nmap.xml" {address}' + pattern = '^(?P\d+)\/(?P(tcp|udp))(.*)open(\s*)(?P[\w\-\/]+)(\s*)(.*)$' +``` + +Note that indentation is optional, it is used here purely for aesthetics. The "quick" profile defines a scan called "nmap-quick". This scan has a service-detection command which uses nmap to scan the top 1000 TCP ports. The command uses two references: {scandir} is the location of the scans directory for the target, and {address} is the address of the target. + +A regex pattern is defined which matches three named groups (port, protocol, and service) in the output. Every service-detection command must have a corresponding pattern that matches all three of those groups. AutoRecon will attempt to do some checks and refuse to scan if any of these groups are missing. + +Here is a more complicated example: + +```toml +[udp] + + [udp.top20] + + [udp.top20.port-scan] + command = 'unicornscan -mU -p 631,161,137,123,138,1434,445,135,67,53,139,500,68,520,1900,4500,514,49152,162,69 {address} 2&>1 | tee "{scandir}/_udp_top20_unicornscan.txt"' + pattern = '^UDP open\s*[\w-]+\[\s*(?P\d+)\].*$' + + [udp.top20.service-detection] + command = 'nmap -vv --reason -Pn -sU -A -p {ports} --version-all -oN "{scandir}/_udp_top20_nmap.txt" -oX "{scandir}/_udp_top20_nmap.xml" {address}' + pattern = '^(?P\d+)\/(?P(udp)(.*)open(\s*)(?P[\w\-\/]+)(\s*)(.*)$' +``` + +In this example, a profile called "udp" defines a scan called "top20". This scan has two commands, one is a port-scan and the other is a service-detection. When a port-scan command is defined, it will always be run first. The corresponding pattern must match a named group "port" which extracts the port number from the output. + +The service-detection will be run after the port-scan command has finished, and uses a new reference: {ports}. This reference is a comma-separated string of all the ports extracted by the port-scan command. Note that the same three named groups (port, protocol, and service) are defined in the service-detection pattern. + +Both the port-scan and the service-detection commands use the {scandir} and {address} references. + +Note that if a port-scan command is defined without a corresponding service-detection command, AutoRecon will refuse to scan. + +This more complicated example is only really useful if you want to use unicornscan's speed in conjuction with nmap's service detection abilities. If you are content with using Nmap for both port scanning and service detection, you do not need to use this setup. + +### Service Scans + +The service-scans.toml file is where you can define service enumeration scans and other manual commands associated with certain services. + +Here is an example of a simple configuration: + +``` +[ftp] + +service-names = [ + '^ftp', + '^ftp\-data' +] + + [ftp.scans] + + [ftp.scans.nmap-ftp] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script="(ftp* or ssl*) and not (brute or broadcast or dos or external or fuzzer)" -oN "{scandir}/{protocol}_{port}_ftp_nmap.txt" -oX "{scandir}/{protocol}_{port}_ftp_nmap.xml" {address}' + + [ftp.manual] + + [ftp.manual.bruteforce] + description = 'Bruteforce logins:' + commands = [ + 'hydra -L "{username_wordlist}" -P "{password_wordlist}" -e nsr -s {port} -o "{scandir}/{protocol}_{port}_ftp_hydra.txt" ftp://{address}', + 'medusa -U "{username_wordlist}" -P "{password_wordlist}" -e ns -n {port} -O "{scandir}/{protocol}_{port}_ftp_medusa.txt" -M ftp -h {address}' + ] +``` + +Note that indentation is optional, it is used here purely for aesthetics. The service "ftp" is defined here. The service-names array contains regex strings which should match the service name from the service-detection scans. Regex is used to be as flexible as possible. The service-names array works on a whitelist basis; as long as one of the regex strings matches, the service will get scanned. + +An optional ignore-service-names array can also be defined, if you want to blacklist certain regex strings from matching. + +The ftp.scans section defines a single scan, named nmap-ftp. This scan defines a command which runs nmap with several ftp-related scripts. Several references are used here: {nmap_extra} will be blank unless the port is UDP, at which point it will be set to -sU, {port} is the port that the service is running on, {scandir} is the location of the scans directory for the target, {protocol} is the protocol being used (either tcp or udp), and {address} is the address of the target. + +The ftp.manual section defines a group of manual commands called "bruteforce". This group contains a description for the user, and a commands array which contains the commands that a user can run. Two new references are defined here: {username_wordlist} and {password_wordlist} which are configured at the very top of the service-scans.toml file, and default to a username and password wordlist provided by SecLists. + +Here is a more complicated configuration: + +``` +[smb] + +service-names = [ + '^smb', + '^microsoft\-ds', + '^netbios' +] + + [smb.scans] + + [smb.scans.nmap-smb] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script="(nbstat or smb* or ssl*) and not (brute or broadcast or dos or external or fuzzer)" --script-args=unsafe=1 -oN "{scandir}/{protocol}_{port}_smb_nmap.txt" -oX "{scandir}/{protocol}_{port}_smb_nmap.xml" {address}' + + [smb.scans.enum4linux] + command = 'enum4linux -a -M -l -d {address} | tee "{scandir}/enum4linux.txt"' + run_once = true + ports.tcp = [139, 389, 445] + ports.udp = [137] + + [smb.scans.nbtscan] + command = 'nbtscan -rvh {address} | tee "{scandir}/nbtscan.txt"' + run_once = true + ports.udp = [137] + + [smb.scans.smbclient] + command = 'smbclient -L\\ -N -I {address} | tee "{scandir}/smbclient.txt"' + run_once = true + ports.tcp = [139, 445] +``` + +The main difference here is that several scans have some new settings: + +* The ports.tcp array defines a whitelist of TCP ports which the command can be run against. If the service is detected on a port that is not in the whitelist, the command will not be run against it. +* The ports.udp array defines a whitelist of UDP ports which the command can be run against. It operates in the same way as the ports.tcp array. + +Why do these settings even exist? Well, some commands will only run against specific ports, and can't be told to run against any other ports. enum4linux for example, will only run against TCP ports 139, 389, and 445, and UDP port 137. + +In fact, enum4linux will always try these ports when it is run. So if the SMB service is found on TCP ports 139 and 445, AutoRecon may attempt to run enum4linux twice for no reason. This is why the third setting exists: + +* If run_once is set to true, the command will only ever run once for that target, even if the SMB service is found on multiple ports. + +## Testimonials + +> AutoRecon was invaluable during my OSCP exam, in that it saved me from the tedium of executing my active information gathering commands myself. I was able to start on a target with all of the information I needed clearly laid in front of me. I would strongly recommend this utility for anyone in the PWK labs, the OSCP exam, or other environments such as VulnHub or HTB. It is a great tool for both people just starting down their journey into OffSec and seasoned veterans alike. Just make sure that somewhere between those two points you take the time to learn what's going on "under the hood" and how / why it scans what it does. +> +>\- b0ats diff --git a/autorecon.py b/autorecon.py new file mode 100644 index 0000000..65a359a --- /dev/null +++ b/autorecon.py @@ -0,0 +1,604 @@ +#!/usr/bin/env python3 +# +# AutoRecon is a multi-threaded network reconnaissance tool which performs automated enumeration of services. +# +# This program can be redistributed and/or modified under the terms of the +# GNU General Public License, either version 3 of the License, or (at your +# option) any later version. +# + +import argparse +import asyncio +from colorama import init, Fore, Back, Style +from concurrent.futures import ProcessPoolExecutor, as_completed, FIRST_COMPLETED +import ipaddress +import os +import re +import socket +import string +import sys +import toml + +verbose = 0 +nmap = '' +srvname = '' +port_scan_profile = None + +port_scan_profiles_config = None +service_scans_config = None + +username_wordlist = '/usr/share/seclists/Usernames/top-usernames-shortlist.txt' +password_wordlist = '/usr/share/seclists/Passwords/darkweb2017-top100.txt' + +__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) + +def e(*args, frame_index=1, **kvargs): + frame = sys._getframe(frame_index) + + vals = {} + + vals.update(frame.f_globals) + vals.update(frame.f_locals) + vals.update(kvargs) + + return string.Formatter().vformat(' '.join(args), args, vals) + +def cprint(*args, color=Fore.RESET, char='*', sep=' ', end='\n', frame_index=1, file=sys.stdout, **kvargs): + frame = sys._getframe(frame_index) + + vals = { + 'bgreen': Fore.GREEN + Style.BRIGHT, + 'bred': Fore.RED + Style.BRIGHT, + 'bblue': Fore.BLUE + Style.BRIGHT, + 'byellow': Fore.YELLOW + Style.BRIGHT, + + 'green': Fore.GREEN, + 'red': Fore.RED, + 'blue': Fore.BLUE, + 'yellow': Fore.YELLOW, + + 'bright': Style.BRIGHT, + 'srst': Style.NORMAL, + 'crst': Fore.RESET, + 'rst': Style.NORMAL + Fore.RESET + } + + vals.update(frame.f_globals) + vals.update(frame.f_locals) + vals.update(kvargs) + + unfmt = '' + if char is not None: + unfmt += color + '[' + Style.BRIGHT + char + Style.NORMAL + ']' + Fore.RESET + sep + unfmt += sep.join(args) + + fmted = unfmt + + for attempt in range(10): + try: + fmted = string.Formatter().vformat(unfmt, args, vals) + break + except KeyError as err: + key = err.args[0] + unfmt = unfmt.replace('{' + key + '}', '{{' + key + '}}') + + print(fmted, sep=sep, end=end, file=file) + +def debug(*args, color=Fore.BLUE, sep=' ', end='\n', file=sys.stdout, **kvargs): + if verbose >= 1: + cprint(*args, color=color, char='-', sep=sep, end=end, file=file, frame_index=2, **kvargs) + +def info(*args, sep=' ', end='\n', file=sys.stdout, **kvargs): + cprint(*args, color=Fore.GREEN, char='*', sep=sep, end=end, file=file, frame_index=2, **kvargs) + +def warn(*args, sep=' ', end='\n', file=sys.stderr, **kvargs): + cprint(*args, color=Fore.YELLOW, char='!', sep=sep, end=end, file=file, frame_index=2, **kvargs) + +def error(*args, sep=' ', end='\n', file=sys.stderr, **kvargs): + cprint(*args, color=Fore.RED, char='!', sep=sep, end=end, file=file, frame_index=2, **kvargs) + +def fail(*args, sep=' ', end='\n', file=sys.stderr, **kvargs): + cprint(*args, color=Fore.RED, char='!', sep=sep, end=end, file=file, frame_index=2, **kvargs) + exit(-1) + +port_scan_profiles_config_file = 'port-scan-profiles.toml' +with open(os.path.join(__location__, port_scan_profiles_config_file), "r") as p: + try: + port_scan_profiles_config = toml.load(p) + + if len(port_scan_profiles_config) == 0: + fail('There do not appear to be any port scan profiles configured in the {port_scan_profiles_config_file} config file.') + + except toml.decoder.TomlDecodeError as e: + fail('Error: Couldn\'t parse {port_scan_profiles_config_file} config file. Check syntax and duplicate tags.') + +with open(os.path.join(__location__, "service-scans.toml"), "r") as c: + try: + service_scans_config = toml.load(c) + except toml.decoder.TomlDecodeError as e: + fail('Error: Couldn\'t parse service-scans.toml config file. Check syntax and duplicate tags.') + +if 'username_wordlist' in service_scans_config: + if isinstance(service_scans_config['username_wordlist'], str): + username_wordlist = service_scans_config['username_wordlist'] + +if 'password_wordlist' in service_scans_config: + if isinstance(service_scans_config['password_wordlist'], str): + password_wordlist = service_scans_config['password_wordlist'] + +async def read_stream(stream, address, tag='?', color=Fore.BLUE): + while True: + line = await stream.readline() + if line: + line = str(line.rstrip(), 'utf8', 'ignore') + debug(color + '[' + Style.BRIGHT + address + ' ' + tag + Style.NORMAL + '] ' + Fore.RESET + '{line}', color=color) + else: + break + +async def run_cmd(semaphore, cmd, target, tag='?'): + async with semaphore: + address = target.address + scandir = target.scandir + + info('Running task {bgreen}{tag}{rst} on {byellow}{address}{rst}' + (' with {bblue}{cmd}{rst}' if verbose >= 1 else '')) + + with open(os.path.join(scandir, '_commands.log'), 'a') as file: + file.writelines(e('{cmd}\n\n')) + + process = await asyncio.create_subprocess_shell(cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE) + + await asyncio.wait([ + read_stream(process.stdout, address, tag), + read_stream(process.stderr, address, tag, Fore.RED) + ]) + + await process.wait() + + if process.returncode != 0: + error('Task {bred}{tag}{rst} on {byellow}{address}{rst} returned non-zero exit code: {process.returncode}') + with open(os.path.join(scandir, '_errors.log'), 'a') as file: + file.writelines(e('[*] Task {tag} returned non-zero exit code: {process.returncode}. Command: {cmd}\n')) + else: + info('Task {bgreen}{tag}{rst} on {byellow}{address}{rst} finished successfully') + + return {'returncode': process.returncode, 'name': 'run_cmd'} + +async def parse_port_scan(stream, tag, address, pattern): + ports = [] + + while True: + line = await stream.readline() + if line: + line = str(line.rstrip(), 'utf8', 'ignore') + debug(Fore.BLUE + '[' + Style.BRIGHT + address + ' ' + tag + Style.NORMAL + '] ' + Fore.RESET + '{line}', color=Fore.BLUE) + + parse_match = re.search(pattern, line) + if parse_match: + ports.append(parse_match.group('port')) + else: + break + + return ports + +async def parse_service_detection(stream, tag, address, pattern): + services = [] + + while True: + line = await stream.readline() + if line: + line = str(line.rstrip(), 'utf8', 'ignore') + debug(Fore.BLUE + '[' + Style.BRIGHT + address + ' ' + tag + Style.NORMAL + '] ' + Fore.RESET + '{line}', color=Fore.BLUE) + + parse_match = re.search(pattern, line) + if parse_match: + services.append((parse_match.group('protocol').lower(), int(parse_match.group('port')), parse_match.group('service'))) + else: + break + + return services + +async def run_portscan(semaphore, tag, target, service_detection, port_scan=None): + async with semaphore: + + address = target.address + scandir = target.scandir + + ports = '' + if port_scan is not None: + command = e(port_scan[0]) + pattern = port_scan[1] + + info('Running port scan {bgreen}{tag}{rst} on {byellow}{address}{rst}' + (' with {bblue}{command}{rst}' if verbose >= 1 else '')) + + with open(os.path.join(scandir, '_commands.log'), 'a') as file: + file.writelines(e('{command}\n\n')) + + process = await asyncio.create_subprocess_shell(command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE) + + output = [ + parse_port_scan(process.stdout, tag, address, pattern), + read_stream(process.stderr, address, tag, Fore.RED) + ] + + results = await asyncio.gather(*output) + + await process.wait() + + if process.returncode != 0: + error('Port scan {bred}{tag}{rst} on {byellow}{address}{rst} returned non-zero exit code: {process.returncode}') + with open(os.path.join(scandir, '_errors.log'), 'a') as file: + file.writelines(e('[*] Port scan {tag} returned non-zero exit code: {process.returncode}. Command: {command}\n')) + return {'returncode': process.returncode} + else: + info('Port scan {bgreen}{tag}{rst} on {byellow}{address}{rst} finished successfully') + + ports = results[0] + if len(ports) == 0: + return {'returncode': -1} + + ports = ','.join(ports) + + command = e(service_detection[0]) + pattern = service_detection[1] + + info('Running service detection {bgreen}{tag}{rst} on {byellow}{address}{rst}' + (' with {bblue}{command}{rst}' if verbose >= 1 else '')) + + with open(os.path.join(scandir, '_commands.log'), 'a') as file: + file.writelines(e('{command}\n\n')) + + process = await asyncio.create_subprocess_shell(command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE) + + output = [ + parse_service_detection(process.stdout, tag, address, pattern), + read_stream(process.stderr, address, tag, Fore.RED) + ] + + results = await asyncio.gather(*output) + + await process.wait() + + if process.returncode != 0: + error('Service detection {bred}{tag}{rst} on {byellow}{address}{rst} returned non-zero exit code: {process.returncode}') + with open(os.path.join(scandir, '_errors.log'), 'a') as file: + file.writelines(e('[*] Service detection {tag} returned non-zero exit code: {process.returncode}. Command: {command}\n')) + else: + info('Service detection {bgreen}{tag}{rst} on {byellow}{address}{rst} finished successfully') + + services = results[0] + + return {'returncode': process.returncode, 'name': 'run_portscan', 'services': services} + +async def scan_services(loop, semaphore, target): + global nmap + address = target.address + scandir = target.scandir + pending = [] + + for profile in port_scan_profiles_config: + if profile == port_scan_profile: + for scan in port_scan_profiles_config[profile]: + service_detection = (port_scan_profiles_config[profile][scan]['service-detection']['command'], port_scan_profiles_config[profile][scan]['service-detection']['pattern']) + if 'port-scan' in port_scan_profiles_config[profile][scan]: + port_scan = (port_scan_profiles_config[profile][scan]['port-scan']['command'], port_scan_profiles_config[profile][scan]['port-scan']['pattern']) + pending.append(run_portscan(semaphore, scan, target, service_detection, port_scan)) + else: + pending.append(run_portscan(semaphore, scan, target, service_detection)) + break + + services = [] + + while True: + if not pending: + break + + done, pending = await asyncio.wait(pending, return_when=FIRST_COMPLETED) + + for task in done: + result = task.result() + + if result['returncode'] == 0: + if result['name'] == 'run_portscan': + for service_tuple in result['services']: + if service_tuple not in services: + services.append(service_tuple) + else: + continue + + protocol = service_tuple[0] + port = service_tuple[1] + service = service_tuple[2] + + info(Fore.BLUE + '[' + Style.BRIGHT + address + Style.NORMAL + '] {service} found on {protocol}/{port}' + Fore.RESET) + + with open(os.path.join(target.reportdir, 'notes.txt'), 'a') as file: + file.writelines(e('[*] {service} found on {protocol}/{port}.\n\n\n\n')) + + if protocol == 'udp': + nmap_extra = nmap + " -sU" + else: + nmap_extra = nmap + + secure = True if 'ssl' in service or 'tls' in service else False + + # Special cases for HTTP. + scheme = 'https' if 'https' in service or 'ssl' in service or 'tls' in service else 'http' + + if service.startswith('ssl/') or service.startswith('tls/'): + service = service[4:] + + for service_scan in service_scans_config: + # Skip over configurable variables since the python toml parser cannot iterate over tables only. + if service_scan in ['username_wordlist', 'password_wordlist']: + continue + + ignore_service = False + if 'ignore-service-names' in service_scans_config[service_scan]: + for ignore_service_name in service_scans_config[service_scan]['ignore-service-names']: + if re.search(ignore_service_name, service): + ignore_service = True + break + + if ignore_service: + continue + + matched_service = False + + if 'service-names' in service_scans_config[service_scan]: + for service_name in service_scans_config[service_scan]['service-names']: + if re.search(service_name, service): + matched_service = True + break + + if not matched_service: + continue + + if 'manual' in service_scans_config[service_scan]: + heading = False + with open(os.path.join(scandir, '_manual_commands.txt'), 'a') as file: + for manual in service_scans_config[service_scan]['manual']: + if 'description' in service_scans_config[service_scan]['manual'][manual]: + if not heading: + file.writelines(e('[*] {service} on {protocol}/{port}\n\n')) + heading = True + description = service_scans_config[service_scan]['manual'][manual]['description'] + file.writelines(e('[-] {description}\n\n')) + if 'commands' in service_scans_config[service_scan]['manual'][manual]: + if not heading: + file.writelines(e('[*] {service} on {protocol}/{port}\n\n')) + heading = True + for manual_command in service_scans_config[service_scan]['manual'][manual]['commands']: + manual_command = e(manual_command) + file.writelines(e('{manual_command}\n')) + file.writelines('\n') + if heading: + file.writelines('\n') + + if 'scans' in service_scans_config[service_scan]: + for scan in service_scans_config[service_scan]['scans']: + + if 'command' in service_scans_config[service_scan]['scans'][scan]: + tag = e('{protocol}/{port}/{scan}') + command = service_scans_config[service_scan]['scans'][scan]['command'] + + if 'ports' in service_scans_config[service_scan]['scans'][scan]: + port_match = False + + if protocol == 'tcp': + if 'tcp' in service_scans_config[service_scan]['scans'][scan]['ports']: + for tcp_port in service_scans_config[service_scan]['scans'][scan]['ports']['tcp']: + if port == tcp_port: + port_match = True + break + elif protocol == 'udp': + if 'udp' in service_scans_config[service_scan]['scans'][scan]['ports']: + for udp_port in service_scans_config[service_scan]['scans'][scan]['ports']['udp']: + if port == udp_port: + port_match = True + break + + if port_match == False: + warn(Fore.YELLOW + '[' + Style.BRIGHT + tag + Style.NORMAL + '] Scan cannot be run against {protocol} port {port}. Skipping.' + Fore.RESET) + continue + + #scan_tuple = (protocol, port, service, scan) + if 'run_once' in service_scans_config[service_scan]['scans'][scan] and service_scans_config[service_scan]['scans'][scan]['run_once'] == True: + scan_tuple = (service, scan) + if scan_tuple in target.scans: + warn(Fore.YELLOW + '[' + Style.BRIGHT + tag + ' on ' + address + Style.NORMAL + '] Scan should only be run once and it appears to have already been queued. Skipping.' + Fore.RESET) + continue + else: + target.scans.append(scan_tuple) + else: + scan_tuple = (protocol, port, service, scan) + if scan_tuple in target.scans: + warn(Fore.YELLOW + '[' + Style.BRIGHT + tag + ' on ' + address + Style.NORMAL + '] Scan appears to have already been queued, but it is not marked as run_once in service-scans.toml. Possible duplicate tag? Skipping.' + Fore.RESET) + continue + else: + target.scans.append(scan_tuple) + + pending.add(asyncio.ensure_future(run_cmd(semaphore, e(command), target, tag))) + + loop.stop() + +def scan_host(target, concurrent_scans): + info('Scanning target {byellow}{target.address}{rst}') + + basedir = os.path.abspath(os.path.join(outdir, target.address + srvname)) + target.basedir = basedir + os.makedirs(basedir, exist_ok=True) + + exploitdir = os.path.abspath(os.path.join(basedir, 'exploit')) + os.makedirs(exploitdir, exist_ok=True) + + lootdir = os.path.abspath(os.path.join(basedir, 'loot')) + os.makedirs(lootdir, exist_ok=True) + + reportdir = os.path.abspath(os.path.join(basedir, 'report')) + target.reportdir = reportdir + os.makedirs(reportdir, exist_ok=True) + + screenshotdir = os.path.abspath(os.path.join(reportdir, 'screenshots')) + os.makedirs(screenshotdir, exist_ok=True) + + scandir = os.path.abspath(os.path.join(basedir, 'scans')) + target.scandir = scandir + os.makedirs(scandir, exist_ok=True) + + open(os.path.abspath(os.path.join(reportdir, 'local.txt')), 'a').close() + open(os.path.abspath(os.path.join(reportdir, 'proof.txt')), 'a').close() + + # Get event loop for current process. + loop = asyncio.get_event_loop() + + # Create a semaphore to limit number of concurrent scans. + semaphore = asyncio.Semaphore(concurrent_scans) + + try: + loop.create_task(scan_services(loop, semaphore, target)) + loop.run_forever() + except KeyboardInterrupt: + pass + finally: + loop.close() + info('Finished scanning target {byellow}{target.address}{rst}') + return + +class Target: + def __init__(self, address): + self.address = address + self.basedir = '' + self.reportdir = '' + self.scandir = '' + self.scans = [] + +if __name__ == '__main__': + + parser = argparse.ArgumentParser(description='Network reconnaissance tool to port scan and automatically enumerate services found on multiple targets.') + parser.add_argument('targets', action='store', help='IP addresses (e.g. 10.0.0.1), CIDR notation (e.g. 10.0.0.1/24), or resolvable hostnames (e.g. foo.bar) to scan.', nargs="+") + parser.add_argument('-ct', '--concurrent-targets', action='store', metavar='', type=int, default=5, help='The maximum number of target hosts to scan concurrently. Default: %(default)s') + parser.add_argument('-cs', '--concurrent-scans', action='store', metavar='', type=int, default=10, help='The maximum number of scans to perform per target host. Default: %(default)s') + parser.add_argument('--profile', action='store', default='default', help='The port scanning profile to use (defined in port-scan-profiles.toml).') + parser.add_argument('-v', '--verbose', action='count', help='enable verbose output, repeat for more verbosity') + parser.add_argument('-o', '--output', action='store', default='results', help='output directory for the results') + parser.add_argument('--disable-sanity-checks', action='store_true', default=False, help='Disable sanity checks that would otherwise prevent the scans from running.') + parser.error = lambda s: fail(s[0].upper() + s[1:]) + args = parser.parse_args() + + errors = False + + if args.concurrent_targets <= 0: + error('Argument -ch/--concurrent-targets: must be greater or equal to 1.') + errors = True + + concurrent_scans = args.concurrent_scans + + if concurrent_scans <= 0: + error('Argument -ct/--concurrent-scans: must be greater or equal to 1.') + errors = True + + port_scan_profile = args.profile + + found_scan_profile = False + for profile in port_scan_profiles_config: + if profile == port_scan_profile: + found_scan_profile = True + for scan in port_scan_profiles_config[profile]: + if 'service-detection' not in port_scan_profiles_config[profile][scan]: + error('The {profile}.{scan} scan does not have a defined service-detection section. Every scan must at least have a service-detection section defined with a command and a corresponding pattern that extracts the protocol (TCP/UDP), port, and service from the result.') + errors = True + else: + if 'command' not in port_scan_profiles_config[profile][scan]['service-detection']: + error('The {profile}.{scan}.service-detection section does not have a command defined. Every service-detection section must have a command and a corresponding pattern that extracts the protocol (TCP/UDP), port, and service from the results.') + errors = True + else: + if '{ports}' in port_scan_profiles_config[profile][scan]['service-detection']['command'] and 'port-scan' not in port_scan_profiles_config[profile][scan]: + error('The {profile}.{scan}.service-detection command appears to reference a port list but there is no port-scan section defined in {profile}.{scan}. Define a port-scan section with a command and corresponding pattern that extracts port numbers from the result, or replace the reference with a static list of ports.') + errors = True + + if 'pattern' not in port_scan_profiles_config[profile][scan]['service-detection']: + error('The {profile}.{scan}.service-detection section does not have a pattern defined. Every service-detection section must have a command and a corresponding pattern that extracts the protocol (TCP/UDP), port, and service from the results.') + errors = True + else: + if not all(x in port_scan_profiles_config[profile][scan]['service-detection']['pattern'] for x in ['(?P', '(?P', '(?P']): + error('The {profile}.{scan}.service-detection pattern does not contain one or more of the following matching groups: port, protocol, service. Ensure that all three of these matching groups are defined and capture the relevant data, e.g. (?P\d+)') + errors = True + + if 'port-scan' in port_scan_profiles_config[profile][scan]: + if 'command' not in port_scan_profiles_config[profile][scan]['port-scan']: + error('The {profile}.{scan}.port-scan section does not have a command defined. Every port-scan section must have a command and a corresponding pattern that extracts the port from the results.') + errors = True + + if 'pattern' not in port_scan_profiles_config[profile][scan]['port-scan']: + error('The {profile}.{scan}.port-scan section does not have a pattern defined. Every port-scan section must have a command and a corresponding pattern that extracts the port from the results.') + errors = True + else: + if '(?P' not in port_scan_profiles_config[profile][scan]['port-scan']['pattern']: + error('The {profile}.{scan}.port-scan pattern does not contain a port matching group. Ensure that the port matching group is defined and captures the relevant data, e.g. (?P\d+)') + errors = True + break + + if not found_scan_profile: + error('Argument --profile: must reference a port scan profile defined in {port_scan_profiles_config_file}. No such profile found: {port_scan_profile}') + errors = True + + outdir = args.output + srvname = '' + verbose = args.verbose if args.verbose is not None else 0 + + if len(args.targets) == 0: + error('You must specify at least one target to scan!') + errors = True + + targets = [] + + for target in args.targets: + try: + ip = str(ipaddress.ip_address(target)) + + if ip not in targets: + targets.append(ip) + except ValueError: + + try: + target_range = ipaddress.ip_network(target, strict=False) + if not args.disable_sanity_checks and target_range.num_addresses > 256: + error(target + ' contains ' + str(target_range.num_addresses) + ' addresses. Check that your CIDR notation is correct. If it is, re-run with the --disable-sanity-checks option to suppress this check.') + errors = True + else: + for ip in target_range.hosts(): + ip = str(ip) + if ip not in targets: + targets.append(ip) + except ValueError: + + try: + ip = socket.gethostbyname(target) + + if target not in targets: + targets.append(target) + except: + error(target + ' does not appear to be a valid IP address, IP range, or resolvable hostname.') + errors = True + + if not args.disable_sanity_checks and len(targets) > 256: + error('A total of ' + str(len(targets)) + ' targets would be scanned. If this is correct, re-run with the --disable-sanity-checks option to suppress this check.') + errors = True + + if errors: + sys.exit(1) + + with ProcessPoolExecutor(max_workers=args.concurrent_targets) as executor: + futures = [] + + for address in targets: + target = Target(address) + futures.append(executor.submit(scan_host, target, concurrent_scans)) + + try: + for future in as_completed(futures): + future.result() + except KeyboardInterrupt: + for future in futures: + future.cancel() + executor.shutdown(wait=False) + sys.exit(1) diff --git a/port-scan-profiles.toml b/port-scan-profiles.toml new file mode 100644 index 0000000..d93eefe --- /dev/null +++ b/port-scan-profiles.toml @@ -0,0 +1,39 @@ +[default] + + [default.nmap-quick] + + [default.nmap-quick.service-detection] + command = 'nmap -vv --reason -Pn -sV -sC --version-all -oN "{scandir}/_quick_tcp_nmap.txt" -oX "{scandir}/_quick_tcp_nmap.xml" {address}' + pattern = '^(?P\d+)\/(?P(tcp|udp))(.*)open(\s*)(?P[\w\-\/]+)(\s*)(.*)$' + + [default.nmap-full-tcp] + + [default.nmap-full-tcp.service-detection] + command = 'nmap -vv --reason -Pn -A --osscan-guess --version-all -p- -oN "{scandir}/_full_tcp_nmap.txt" -oX "{scandir}/_full_tcp_nmap.xml" {address}' + pattern = '^(?P\d+)\/(?P(tcp|udp))(.*)open(\s*)(?P[\w\-\/]+)(\s*)(.*)$' + + [default.nmap-top-20-udp] + + [default.nmap-top-20-udp.service-detection] + command = 'nmap -vv --reason -Pn -sU -A --top-ports=20 --version-all -oN "{scandir}/_top_20_udp_nmap.txt" -oX "{scandir}/_top_20_udp_nmap.xml" {address}' + pattern = '^(?P\d+)\/(?P(tcp|udp))(.*)open(\s*)(?P[\w\-\/]+)(\s*)(.*)$' + +[quick] + + [quick.nmap-quick] + + [quick.nmap-quick.service-detection] + command = 'nmap -vv --reason -Pn -sV --version-all -oN "{scandir}/_quick_tcp_nmap.txt" -oX "{scandir}/_quick_tcp_nmap.xml" {address}' + pattern = '^(?P\d+)\/(?P(tcp|udp))(.*)open(\s*)(?P[\w\-\/]+)(\s*)(.*)$' + +[udp] + + [udp.udp-top-20] + + [udp.udp-top-20.port-scan] + command = 'unicornscan -mU -p 631,161,137,123,138,1434,445,135,67,53,139,500,68,520,1900,4500,514,49152,162,69 {address} 2>&1 | tee "{scandir}/_udp_top20_unicornscan.txt"' + pattern = '^UDP open\s*[\w-]+\[\s*(?P\d+)\].*$' + + [udp.udp-top-20.service-detection] + command = 'nmap -vv --reason -Pn -sU -A -p {ports} --version-all -oN "{scandir}/_udp_top20_nmap.txt" -oX "{scandir}/_udp_top20_nmap.xml" {address}' + pattern = '^(?P\d+)\/(?P(udp))(.*)open(\s*)(?P[\w\-\/]+)(\s*)(.*)$' diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..896910a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +colorama +toml diff --git a/service-scans.toml b/service-scans.toml new file mode 100644 index 0000000..26d13be --- /dev/null +++ b/service-scans.toml @@ -0,0 +1,427 @@ +# Configurable Variables +username_wordlist = '/usr/share/seclists/Usernames/top-usernames-shortlist.txt' +password_wordlist = '/usr/share/seclists/Passwords/darkweb2017-top100.txt' + +[cassandra] + +service-names = [ + '^apani1' +] + + [cassandra.scans] + + [cassandra.scans.nmap-cassandra] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script="(cassandra* or ssl*) and not (brute or broadcast or dos or external or fuzzer)" -oN "{scandir}/{protocol}_{port}_cassandra_nmap.txt" -oX "{scandir}/{protocol}_{port}_cassandra_nmap.xml" {address}' + +[cups] + +service-names = [ + '^ipp' +] + + [cups.scans] + + [cups.scans.nmap-cups] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script="(cups* or ssl*) and not (brute or broadcast or dos or external or fuzzer)" -oN "{scandir}/{protocol}_{port}_cups_nmap.txt" -oX "{scandir}/{protocol}_{port}_cups_nmap.xml" {address}' + +[dns] + +service-names = [ + '^domain', +] + + [dns.scans] + + [dns.scans.nmap-dns] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script="(dns* or ssl*) and not (brute or broadcast or dos or external or fuzzer)" -oN "{scandir}/{protocol}_{port}_dns_nmap.txt" -oX "{scandir}/{protocol}_{port}_dns_nmap.xml" {address}' + +[ftp] + +service-names = [ + '^ftp', + '^ftp\-data' +] + + [ftp.scans] + + [ftp.scans.nmap-ftp] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script="(ftp* or ssl*) and not (brute or broadcast or dos or external or fuzzer)" -oN "{scandir}/{protocol}_{port}_ftp_nmap.txt" -oX "{scandir}/{protocol}_{port}_ftp_nmap.xml" {address}' + + [ftp.manual] + + [ftp.manual.bruteforce] + description = 'Bruteforce logins:' + commands = [ + 'hydra -L "{username_wordlist}" -P "{password_wordlist}" -e nsr -s {port} -o "{scandir}/{protocol}_{port}_ftp_hydra.txt" ftp://{address}', + 'medusa -U "{username_wordlist}" -P "{password_wordlist}" -e ns -n {port} -O "{scandir}/{protocol}_{port}_ftp_medusa.txt" -M ftp -h {address}' + ] + +[http] + +service-names = [ + '^http', +] + +ignore-service-names = [ + '^nacn_http$' +] + + [http.scans] + + [http.scans.nmap-http] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script="(http* or ssl*) and not (broadcast or dos or external or http-slowloris* or fuzzer)" -oN "{scandir}/{protocol}_{port}_http_nmap.txt" -oX "{scandir}/{protocol}_{port}_{scheme}_nmap.xml" {address}' + + [http.scans.curl-index] + command = 'curl -sSik {scheme}://{address}:{port}/ -m 10 -o "{scandir}/{protocol}_{port}_{scheme}_index.html"' + + [http.scans.curl-robots] + command = 'curl -sSik {scheme}://{address}:{port}/robots.txt -m 10 -o "{scandir}/{protocol}_{port}_{scheme}_robots.txt"' + + [http.scans.whatweb] + command = 'whatweb --color=never --no-errors -a 3 -v {scheme}://{address}:{port} | tee "{scandir}/{protocol}_{port}_{scheme}_whatweb.txt"' + + [http.scans.nikto] + command = 'nikto -ask=no -h {scheme}://{address}:{port} | tee "{scandir}/{protocol}_{port}_{scheme}_nikto.txt"' + + [http.manual] + + [http.manual.dirb] + description = '(dirb) Recursive directory/file enumeration for web servers using various wordlists:' + commands = [ + 'dirb {scheme}://{address}:{port}/ -o "{scandir}/{protocol}_{port}_{scheme}_dirb_big.txt" /usr/share/seclists/Discovery/Web-Content/big.txt', + 'dirb {scheme}://{address}:{port}/ -o "{scandir}/{protocol}_{port}_{scheme}_dirb_common.txt" /usr/share/seclists/Discovery/Web-Content/common.txt', + 'dirb {scheme}://{address}:{port}/ -o "{scandir}/{protocol}_{port}_{scheme}_dirb_dirbuster.txt" /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt' + ] + + [http.manual.gobuster] + description = '(gobuster) Directory/file enumeration for web servers using various wordlists (same as dirb above, in case you prefer gobuster):' + commands = [ + 'gobuster -u {scheme}://{address}:{port}/ -o "{scandir}/{protocol}_{port}_{scheme}_gobuster_big.txt" -w /usr/share/seclists/Discovery/Web-Content/big.txt -s "200,204,301,302,307,403,500" -e', + 'gobuster -u {scheme}://{address}:{port}/ -o "{scandir}/{protocol}_{port}_{scheme}_gobuster_common.txt" -w /usr/share/seclists/Discovery/Web-Content/common.txt -s "200,204,301,302,307,403,500" -e', + 'gobuster -u {scheme}://{address}:{port}/ -o "{scandir}/{protocol}_{port}_{scheme}_gobuster_dirbuster.txt" -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -s "200,204,301,302,307,403,500" -e' + ] + + [http.manual.wpscan] + description = '(wpscan) WordPress Security Scanner (useful if WordPress is found):' + commands = [ + 'wpscan --url {scheme}://{address}:{port}/ --no-update -e vp,vt,tt,cb,dbe,u,m -f cli-no-color | tee "{scandir}/{protocol}_{port}_{scheme}_wpscan.txt"' + ] + + [http.manual.bruteforce] + description = "Credential bruteforcing commands (don't run these without modifying them):" + commands = [ + 'hydra -L "{username_wordlist}" -P "{password_wordlist}" -e nsr -s {port} -o "{scandir}/{protocol}_{port}_{scheme}_auth_hydra.txt" {scheme}-get://{address}/path/to/auth/area', + 'medusa -U "{username_wordlist}" -P "{password_wordlist}" -e ns -n {port} -O "{scandir}/{protocol}_{port}_{scheme}_auth_medusa.txt" -M http -h {address} -m DIR:/path/to/auth/area', + 'hydra -L "{username_wordlist}" -P "{password_wordlist}" -e nsr -s {port} -o "{scandir}/{protocol}_{port}_{scheme}_form_hydra.txt" {scheme}-post-form://{address}/path/to/login.php:username=^USER^&password=^PASS^:invalid-login-message', + 'medusa -U "{username_wordlist}" -P "{password_wordlist}" -e ns -n {port} -O "{scandir}/{protocol}_{port}_{scheme}_form_medusa.txt" -M web-form -h {address} -m FORM:/path/to/login.php -m FORM-DATA:"post?username=&password=" -m DENY-SIGNAL:"invalid login message"', + ] + +[imap] + +service-names = [ + '^imap' +] + + [imap.scans] + + [imap.scans.nmap-imap] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script="(imap* or ssl*) and not (brute or broadcast or dos or external or fuzzer)" -oN "{scandir}/{protocol}_{port}_imap_nmap.txt" -oX "{scandir}/{protocol}_{port}_imap_nmap.xml" {address}' + +[kerberos] + +service-names = [ + '^kerberos' +] + + [kerberos.scans] + + [kerberos.scans.nmap-kerberos] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script=krb5-enum-users -oN "{scandir}/{protocol}_{port}_kerberos_nmap.txt" -oX "{scandir}/{protocol}_{port}_kerberos_nmap.xml" {address}' + +[ldap] + +service-names = [ + '^ldap' +] + + [ldap.scans] + + [ldap.scans.nmap-ldap] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script="(ldap* or ssl*) and not (brute or broadcast or dos or external or fuzzer)" -oN "{scandir}/{protocol}_{port}_ldap_nmap.txt" -oX "{scandir}/{protocol}_{port}_ldap_nmap.xml" {address}' + + [ldap.scans.enum4linux] + command = 'enum4linux -a -M -l -d {address} | tee "{scandir}/enum4linux.txt"' + run_once = true + ports.tcp = [139, 389, 445] + ports.udp = [137] + +[mongodb] + +service-names = [ + '^mongod' +] + + [mongodb.scans] + + [mongodb.scans.nmap-mongodb] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script="mongodb*" -oN "{scandir}/{protocol}_{port}_mongodb_nmap.txt" -oX "{scandir}/{protocol}_{port}_mongodb_nmap.xml" {address}' + +[mssql] + +service-names = [ + '^mssql', + '^ms\-sql' +] + + [mssql.scans] + + [mssql.scans.nmap-mssql] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script="(ms-sql* or ssl*) and not (brute or broadcast or dos or external or fuzzer)" --script-args=mssql.instance-port={port},mssql.username=sa,mssql.password=sa -oN "{scandir}/{protocol}_{port}_mssql_nmap.txt" -oX "{scandir}/{protocol}_{port}_mssql_nmap.xml" {address}' + + [mssql.manual] + + [mssql.manual.sqsh] + description = '(sqsh) interactive database shell' + command = 'sqsh -U -P -S {address}:{port}' + +[mysql] + +service-names = [ + '^mysql' +] + + [mysql.scans] + + [mysql.scans.nmap-mysql] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script="(mysql* or ssl*) and not (brute or broadcast or dos or external or fuzzer)" -oN "{scandir}/{protocol}_{port}_mysql_nmap.txt" -oX "{scandir}/{protocol}_{port}_mysql_nmap.xml" {address}' + +[nfs] + +service-names = [ + '^nfs', + '^rpcbind' +] + + [nfs.scans] + + [nfs.scans.nmap-nfs] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script="(rpcinfo or nfs*) and not (brute or broadcast or dos or external or fuzzer)" -oN "{scandir}/{protocol}_{port}_nfs_nmap.txt" -oX "{scandir}/{protocol}_{port}_nfs_nmap.xml" {address}' + +[oracle] + +service-names = [ + '^oracle' +] + + [oracle.scans] + + [oracle.scans.nmap-oracle] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script="(oracle* or ssl*) and not (brute or broadcast or dos or external or fuzzer)" -oN "{scandir}/{protocol}_{port}_oracle_nmap.txt" -oX "{scandir}/{protocol}_{port}_oracle_nmap.xml" {address}' + +[pop3] + +service-names = [ + '^pop3' +] + + [pop3.scans] + + [pop3.scans.nmap-pop3] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script="(pop3* or ssl*) and not (brute or broadcast or dos or external or fuzzer)" -oN "{scandir}/{protocol}_{port}_pop3_nmap.txt" -oX "{scandir}/{protocol}_{port}_pop3_nmap.xml" {address}' + +[rdp] + +service-names = [ + '^rdp', + '^ms\-wbt\-server', + '^ms\-term\-serv' +] + + [rdp.scans] + + [rdp.scans.nmap-rdp] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script="(rdp* or ssl*) and not (brute or broadcast or dos or external or fuzzer)" -oN "{scandir}/{protocol}_{port}_rdp_nmap.txt" -oX "{scandir}/{protocol}_{port}_rdp_nmap.xml" {address}' + + [rdp.manual] + + [rdp.manual.bruteforce] + description = 'Bruteforce logins:' + commands = [ + 'hydra -L "{username_wordlist}" -P "{password_wordlist}" -e nsr -s {port} -o "{scandir}/{protocol}_{port}_rdp_hydra.txt" rdp://{address}', + 'medusa -U "{username_wordlist}" -P "{password_wordlist}" -e ns -n {port} -O "{scandir}/{protocol}_{port}_rdp_medusa.txt" -M rdp -h {address}' + ] +[rmi] + +service-names = [ + '^java\-rmi', + '^rmiregistry' +] + + [rmi.scans] + + [rmi.scans.nmap-rmi] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script=rmi-vuln-classloader,rmi-dumpregistry -oN "{scandir}/{protocol}_{port}_rmi_nmap.txt" -oX "{scandir}/{protocol}_{port}_rmi_nmap.xml" {address}' + +[rpc] + +service-names = [ + '^msrpc', + '^rpcbind', + '^erpc' +] + + [msrpc.scans] + + [msrpc.scans.nmap-msrpc] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script=msrpc-enum,rpc-grind,rpcinfo -oN "{scandir}/{protocol}_{port}_rpc_nmap.txt" -oX "{scandir}/{protocol}_{port}_rpc_nmap.xml" {address}' + +[ssh] + +service-names = [ + '^ssh' +] + + [ssh.scans] + + [ssh.scans.nmap-ssh] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script=ssh2-enum-algos,ssh-hostkey,ssh-auth-methods -oN "{scandir}/{protocol}_{port}_ssh_nmap.txt" -oX "{scandir}/{protocol}_{port}_ssh_nmap.xml" {address}' + + [ssh.manual] + + [ssh.manual.bruteforce] + description = 'Bruteforce logins:' + commands = [ + 'hydra -L "{username_wordlist}" -P "{password_wordlist}" -e nsr -s {port} -o "{scandir}/{protocol}_{port}_ssh_hydra.txt" ssh://{address}', + 'medusa -U "{username_wordlist}" -P "{password_wordlist}" -e ns -n {port} -O "{scandir}/{protocol}_{port}_ssh_medusa.txt" -M ssh -h {address}' + ] +[smb] + +service-names = [ + '^smb', + '^microsoft\-ds', + '^netbios' +] + + [smb.scans] + + [smb.scans.nmap-smb] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script="(nbstat or smb* or ssl*) and not (brute or broadcast or dos or external or fuzzer)" --script-args=unsafe=1 -oN "{scandir}/{protocol}_{port}_smb_nmap.txt" -oX "{scandir}/{protocol}_{port}_smb_nmap.xml" {address}' + + [smb.scans.enum4linux] + command = 'enum4linux -a -M -l -d {address} | tee "{scandir}/enum4linux.txt"' + run_once = true + ports.tcp = [139, 389, 445] + ports.udp = [137] + + [smb.scans.nbtscan] + command = 'nbtscan -rvh {address} | tee "{scandir}/nbtscan.txt"' + run_once = true + ports.udp = [137] + + [smb.scans.smbclient] + command = 'smbclient -L\\ -N -I {address} | tee "{scandir}/smbclient.txt"' + run_once = true + ports.tcp = [139, 445] + +[smtp] + +service-names = [ + '^smtp' +] + + [smtp.scans] + + [smtp.scans.nmap-smtp] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script="(smtp* or ssl*) and not (brute or broadcast or dos or external or fuzzer)" -oN "{scandir}/{protocol}_{port}_smtp_nmap.txt" -oX "{scandir}/{protocol}_{port}_smtp_nmap.xml" {address}' + + [smtp.scans.smtp-user-enum] + command = 'smtp-user-enum -M VRFY -U "{username_wordlist}" -t {address} -p {port} | tee "{scandir}/{protocol}_{port}_smtp_user-enum.txt"' + +[snmp] + +service-names = [ + '^snmp' +] + + [snmp.scans] + + [snmp.scans.nmap-snmp] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script="(snmp* or ssl*) and not (brute or broadcast or dos or external or fuzzer)" -oN "{scandir}/{protocol}_{port}_snmp-nmap.txt" -oX "{scandir}/{protocol}_{port}_snmp_nmap.xml" {address}' + + [snmp.scans.onesixtyone] + command = 'onesixtyone -c /usr/share/seclists/Discovery/SNMP/common-snmp-community-strings.txt -dd -o "{scandir}/{protocol}_{port}_snmp_onesixtyone.txt" {address}' + run_once = true + ports.udp = [161] + + [snmp.scans.snmpwalk] + command = 'snmpwalk -c public -v 1 {address} | tee "{scandir}/{protocol}_{port}_snmp_snmpwalk.txt"' + run_once = true + ports.udp = [161] + + [snmp.scans.snmpwalk-system-processes] + command = 'snmpwalk -c public -v 1 {address} 1.3.6.1.2.1.25.1.6.0 | tee "{scandir}/{protocol}_{port}_snmp_snmpwalk_system_processes.txt"' + run_once = true + ports.udp = [161] + + [snmp.scans.snmpwalk-running-processes] + command = 'snmpwalk -c public -v 1 {address} 1.3.6.1.2.1.25.4.2.1.2 | tee "{scandir}/{protocol}_{port}_snmp_snmpwalk_running_processes.txt"' + run_once = true + ports.udp = [161] + + [snmp.scans.snmpwalk-process-paths] + command = 'snmpwalk -c public -v 1 {address} 1.3.6.1.2.1.25.4.2.1.4 | tee "{scandir}/{protocol}_{port}_snmp_snmpwalk_process_paths.txt"' + run_once = true + ports.udp = [161] + + [snmp.scans.snmpwalk-storage-units] + command = 'snmpwalk -c public -v 1 {address} 1.3.6.1.2.1.25.2.3.1.4 | tee "{scandir}/{protocol}_{port}_snmp_snmpwalk_storage_units.txt"' + run_once = true + ports.udp = [161] + + [snmp.scans.snmpwalk-software-names] + command = 'snmpwalk -c public -v 1 {address} 1.3.6.1.2.1.25.6.3.1.2 | tee "{scandir}/{protocol}_{port}_snmp_snmpwalk_software_names.txt"' + run_once = true + ports.udp = [161] + + [snmp.scans.snmpwalk-user-accounts] + command = 'snmpwalk -c public -v 1 {address} 1.3.6.1.4.1.77.1.2.25 | tee "{scandir}/{protocol}_{port}_snmp_snmpwalk_user_accounts.txt"' + run_once = true + ports.udp = [161] + + [snmp.scans.snmpwalk-tcp-ports] + command = 'snmpwalk -c public -v 1 {address} 1.3.6.1.2.1.6.13.1.3 | tee "{scandir}/{protocol}_{port}_snmp_snmpwalk_tcp_ports.txt"' + run_once = true + ports.udp = [161] + +[telnet] + +service-names = [ + '^telnet' +] + + [telnet.scans] + + [telnet.scans.nmap-telnet] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script=telnet-encryption,telnet-ntlm-info -oN "{scandir}/{protocol}_{port}_telnet-nmap.txt" -oX "{scandir}/{protocol}_{port}_telnet_nmap.xml" {address}' + +[tftp] + +service-names = [ + '^tftp' +] + + [tftp.scans] + + [tftp.scans.nmap-tftp] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script=tftp-enum -oN "{scandir}/{protocol}_{port}_tftp-nmap.txt" -oX "{scandir}/{protocol}_{port}_tftp_nmap.xml" {address}' + +[vnc] + +service-names = [ + '^vnc' +] + + [vnc.scans] + + [vnc.scans.nmap-vnc] + command = 'nmap -vv --reason -Pn -sV {nmap_extra} -p {port} --script="(vnc* or realvnc* or ssl*) and not (brute or broadcast or dos or external or fuzzer)" --script-args=unsafe=1 -oN "{scandir}/{protocol}_{port}_vnc_nmap.txt" -oX "{scandir}/{protocol}_{port}_vnc_nmap.xml" {address}'