├── urls └── .gitkeep ├── xml └── .gitkeep ├── words ├── blank ├── ASP ├── ASPX ├── HTML └── PHP ├── requirements.txt ├── showRange.py ├── xmltourl.py ├── README.md ├── netscan.py └── scantastic.py /urls/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /xml/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /words/blank: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /words/ASP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maK-/scantastic-tool/HEAD/words/ASP -------------------------------------------------------------------------------- /words/ASPX: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maK-/scantastic-tool/HEAD/words/ASPX -------------------------------------------------------------------------------- /words/HTML: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maK-/scantastic-tool/HEAD/words/HTML -------------------------------------------------------------------------------- /words/PHP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maK-/scantastic-tool/HEAD/words/PHP -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | elasticsearch==1.3.0 2 | requests==2.20.0 3 | netaddr==0.7.13 4 | xmltodict==0.9.2 5 | numpy==1.9.1 6 | -------------------------------------------------------------------------------- /showRange.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #./showRange.py [127.0.0.0/RANGE] 3 | import sys 4 | from netaddr import * 5 | 6 | addr=sys.argv[1] 7 | 8 | ip = IPNetwork(addr) 9 | print "from: ",str(ip.network) 10 | print "to: ",str(ip.broadcast) 11 | 12 | -------------------------------------------------------------------------------- /xmltourl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Generate URLS from scanfile 3 | # =============================== 4 | 5 | import xmltodict 6 | 7 | 8 | class Xml2urls: 9 | def __init__(self, xmlfile): 10 | self.xmlf = xmlfile 11 | self.data = '' 12 | try: 13 | with open('xml/' + self.xmlf) as myf: 14 | self.data = myf.read().replace('\n', '') 15 | except IOError: 16 | print 'File IO Error' 17 | self.xml = xmltodict.parse(self.data) 18 | 19 | 20 | def run(self): 21 | nmaprun = self.xml['nmaprun'] 22 | host = nmaprun['host'] 23 | 24 | for entry in host: 25 | port = entry['ports']['port'] 26 | if int(port['@portid']) == 80: 27 | name = entry['address']['@addr'] 28 | print 'http://' + name + '/' 29 | elif int(port['@portid']) == 443: 30 | name = entry['address']['@addr'] 31 | print 'https://' + name + '/' 32 | elif int(port['@portid']) == 21: 33 | name = entry['address']['@addr'] 34 | print 'ftp://' + name + '/' 35 | else: 36 | name = entry['address']['@addr'] 37 | print 'http://' + name + ':' + str(port['@portid']) + '/' 38 | 39 | class Xml2urls2: 40 | def __init__(self, xmlfile): 41 | self.xmlf = xmlfile 42 | self.data = '' 43 | try: 44 | with open('xml/' + self.xmlf) as myf: 45 | self.data = myf.read().replace('\n', '') 46 | except IOError: 47 | print 'File IO Error' 48 | self.xml = xmltodict.parse(self.data) 49 | 50 | def run(self): 51 | nmaprun = self.xml['nmaprun'] 52 | scanhost = nmaprun['host'] 53 | for i in scanhost: 54 | address = i['address'][0]['@addr'] 55 | port1 = dict(i) 56 | try: 57 | if int(port1['ports']['port']['@portid']) > 0: 58 | port2 = port1['ports']['port']['@portid'] 59 | if port2 == '80': 60 | print 'http://'+address+'/' 61 | elif port2 == '443': 62 | print 'https://'+address+'/' 63 | else: 64 | print 'http://'+address+':'+port2+'/' 65 | except: 66 | port2 = i['ports']['port'] 67 | for z in port2: 68 | x = z['@portid'] 69 | if x == '80': 70 | print 'http://'+address+'/' 71 | elif x == '443': 72 | print 'https://'+address+'/' 73 | else: 74 | print 'http://'+address+':'+x+'/' 75 | 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scantastic-tool 2 | 3 | ## It's bloody scantastic 4 | 5 | If you like this and are feeling a bit(coin) generous - 1JdSGqg2zGTbpFMJPLbWoXg7Nng3z1Qp58 6 | 7 | It works for me: http://makthepla.net/scantastichax.png 8 | 9 | - Dependencies: (DIY - I ain't supportin shit) 10 | - Masscan - https://github.com/robertdavidgraham/masscan 11 | - Nmap - https://nmap.org/download.html 12 | - ElasticSearch - http://www.elasticsearch.org/guide/en/elasticsearch/guide/current/_installing_elasticsearch.html 13 | - Kibana - http://www.elasticsearch.org/overview/kibana/installation/ 14 | 15 | 16 | This tool can be used to store masscan or nmap data in elasticsearch, 17 | (the scantastic plugin in the image is not here) 18 | 19 | It allows performs distributed directory brute-forcing. 20 | 21 | All your base are belong to us. I might maintain or improve this over time. MIGHT. 22 | 23 | ## Quickstart 24 | 25 | ### Example usage 26 | 27 | Run and import a scan of home /24 network 28 | 29 | ``` 30 | ./scantastic.py -s -H 192.168.1.0/24 -p 80,443 -x homescan.xml (with masscan) 31 | ./scantastic.py -ns -H 192.168.1.0/24 -p 80,443 -x homescan.xml (with nmap) 32 | ``` 33 | 34 | Export homescan to a list of urls 35 | 36 | ``` 37 | ./scantastic.py -eurl -x homescan.xml > urlist (with masscan) 38 | ./scantastic.py -nurl -x homescan.xml > urlist (with nmap) 39 | ``` 40 | 41 | Brute force the url list using wordlist and put results into index homescan 42 | using 10 threads (By default it uses 1 thread) 43 | 44 | ``` 45 | ./scantastic.py -d -u urlist -w some_wordlist -i homescan -t 10 46 | ``` 47 | 48 | ``` 49 | root@ubuntu:~/scantastic-tool# ./scantastic.py -h 50 | usage: scantastic.py [-h] [-v] [-d] [-s] [-noes] [-sl] [-in] [-e] [-eurl] 51 | [-del] [-H HOST] [-p PORTS] [-x XML] [-w WORDS] [-u URLS] 52 | [-t THREADS] [-esh ESHOST] [-esp PORT] [-i INDEX] 53 | [-a AGENT] 54 | 55 | optional arguments: 56 | -h, --help show this help message and exit 57 | -v, --version Version information 58 | -d, --dirb Run directory brute force. Requires --urls & --words 59 | -s, --scan Run masscan on single range. Specify --host & --ports 60 | & --xml 61 | -ns, --nmap Run Nmap on a single range specify -H & -p 62 | -noes, --noelastics Run scan without elasticsearch insertion 63 | -sl, --scanlist Run masscan on a list of ranges. Requires --host & 64 | --ports & --xml 65 | -nsl, --nmaplist Run Nmap on a list of ranges -H & -p & -x 66 | -in, --noinsert Perform a scan without inserting to elasticsearch 67 | -e, --export Export a scan XML into elasticsearch. Requires --xml 68 | -eurl, --exporturl Export urls to scan from XML file. Requires --xml 69 | -nurl, --exportnmap Export urls from nmap XML, requires -x 70 | -del, --delete Specify an index to delete. 71 | -H HOST, --host HOST Scan this host or list of hosts 72 | -p PORTS, --ports PORTS 73 | Specify ports in masscan format. (ie.0-1000 or 74 | 80,443...) 75 | -x XML, --xml XML Specify an XML file to store output in 76 | -w WORDS, --words WORDS 77 | Wordlist to be used with --dirb 78 | -u URLS, --urls URLS List of Urls to be used with --dirb 79 | -t THREADS, --threads THREADS 80 | Specify the number of threads to use. 81 | -esh ESHOST, --eshost ESHOST 82 | Specify the elasticsearch host 83 | -esp PORT, --port PORT 84 | Specify ElasticSearch port 85 | -i INDEX, --index INDEX 86 | Specify the ElasticSearch index 87 | -a AGENT, --agent AGENT 88 | Specify a User Agent for requests 89 | ``` 90 | 91 | Use -noes and -in scans to not import scans by default upon completion of a scan 92 | -------------------------------------------------------------------------------- /netscan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # A class to run masscan and import the results to ES 3 | 4 | import subprocess 5 | import socket 6 | import xmltodict 7 | from elasticsearch import Elasticsearch 8 | from datetime import datetime 9 | 10 | 11 | class Masscan: 12 | # Initialize with range, output, ports 13 | 14 | def __init__(self, ip_r, xml_o, ps): 15 | self.ip_range = ip_r 16 | self.xml_output = xml_o 17 | self.ports = ps 18 | 19 | def run(self): 20 | self.args = ("masscan", "-sS", "-Pn", self.ip_range, 21 | "-oX", self.xml_output, "--rate=15000", "-p", 22 | self.ports, "--open") 23 | popen = subprocess.Popen(self.args, stdout=subprocess.PIPE) 24 | popen.wait() 25 | self.output = popen.stdout.read() 26 | print "Scan completed!" 27 | 28 | def runfile(self): 29 | self.args = ("masscan", "-sS", "-Pn", "-iL", self.ip_range, 30 | "-oX", self.xml_output, "--rate=15000", "-p", self.ports, 31 | "--open") 32 | popen = subprocess.Popen(self.args, stdout=subprocess.PIPE) 33 | popen.wait() 34 | self.output = popen.stdout.read() 35 | print "Scan completed!" 36 | 37 | def import_es(self, es_index, host, port): 38 | es = Elasticsearch([{u'host': host, u'port': port}]) 39 | try: 40 | with open(self.xml_output, "r") as xmlfile: 41 | data = xmlfile.read().replace('\n', '') 42 | xml = xmltodict.parse(data) 43 | nmaprun = xml['nmaprun'] 44 | host = nmaprun['host'] 45 | except: 46 | print "IO Error" 47 | for entry in host: 48 | port = entry['ports']['port'] 49 | try: 50 | name, alias, addrlist = socket.gethostbyaddr(entry['address']['@addr']) 51 | except socket.herror: 52 | name = entry['address']['@addr'] 53 | dataentry = { 54 | 'timestamp': datetime.now(), 55 | 'ip': entry['address']['@addr'], 56 | 'port': port['@portid'], 57 | 'name': name, 58 | 'link': 'http://' + name + '/' 59 | } 60 | result = es.index(index=es_index, doc_type='hax', body=dataentry) 61 | 62 | 63 | class Nmap: 64 | # Initialize with range, output, ports 65 | 66 | def __init__(self, ip_r, xml_o, ps): 67 | self.ip_range = ip_r 68 | self.xml_output = xml_o 69 | self.ports = ps 70 | 71 | def run(self): 72 | self.args = ("nmap", "-sS", "-Pn", self.ip_range, 73 | "-oX", self.xml_output, "-p", self.ports, "--open") 74 | popen = subprocess.Popen(self.args, stdout=subprocess.PIPE) 75 | popen.wait() 76 | self.output = popen.stdout.read() 77 | print "Scan completed!" 78 | 79 | def runfile(self): 80 | self.args = ("nmap", "-sS", "-Pn", "-iL", self.ip_range, 81 | "-oX", self.xml_output, "-p", self.ports, "--open") 82 | popen = subprocess.Popen(self.args, stdout=subprocess.PIPE) 83 | popen.wait() 84 | self.output = popen.stdout.read() 85 | print "Scan completed!" 86 | 87 | def toES(address, ports, es_index, host, port): 88 | es = Elasticsearch([{u'host': host, u'port': port}]) 89 | try: 90 | name, alias, addrlist = socket.gethostbyaddr(address) 91 | 92 | except socket.herror: 93 | name = address 94 | dataentry = { 95 | 'timestamp': datetime.now(), 96 | 'ip': address, 97 | 'port': ports, 98 | 'name': name, 99 | 'link': 'http://' + name + '/' 100 | } 101 | print dataentry 102 | 103 | def import_es(self, es_index, host, port): 104 | try: 105 | with open(self.xml_output, "r") as xmlfile: 106 | data = xmlfile.read().replace('\n', '') 107 | xml = xmltodict.parse(data) 108 | nmaprun = xml['nmaprun'] 109 | scanhost = nmaprun['host'] 110 | for i in scanhost: 111 | address = i['address'][0]['@addr'] 112 | port1 = dict(i) 113 | try: #if one result 114 | if int(port1['ports']['port']['@portid']) > 0: 115 | port2 = port1['ports']['port']['@portid'] 116 | toES(address, str(port2), es_index, host, port) 117 | except: #if multiple 118 | port2 = i['ports']['port']#[0]['@portid'] 119 | for z in port2: 120 | x = z['@portid'] 121 | toES(address, str(x), es_index, host, port) 122 | except IOError, e: 123 | print e 124 | 125 | -------------------------------------------------------------------------------- /scantastic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import multiprocessing 4 | import argparse 5 | import sys 6 | import requests 7 | import string 8 | from datetime import datetime 9 | from time import sleep 10 | from elasticsearch import Elasticsearch 11 | from netscan import Masscan 12 | from netscan import Nmap 13 | from xmltourl import Xml2urls 14 | from xmltourl import Xml2urls2 15 | from numpy import array_split 16 | 17 | requests.packages.urllib3.disable_warnings() 18 | 19 | def version_info(): 20 | VERSION_INFO = 'Scantastic v2.0' 21 | AUTHOR_INFO = 'Author: Ciaran McNally - https://makthepla.net' 22 | print ' _ _ _' 23 | print ' ___ ___ ___ ___| |_ ___ ___| |_|_|___' 24 | print '|_ -| _| .\'| | _| .\'|_ -| _| | _|' 25 | print '|___|___|__,|_|_|_| |__,|___|_| |_|___|' 26 | print '=======================================' 27 | print VERSION_INFO 28 | print AUTHOR_INFO 29 | 30 | 31 | # Split the list of urls into chunks for threading 32 | def split_urls(u, t): 33 | print 'Number of URLS: ' + str(len(u)) 34 | print 'Threads: ' + str(t) 35 | print 'URLS in each split: ' + str(len(u) / t) 36 | print '=========================' 37 | sleep(1) 38 | return array_split(u, t) 39 | 40 | 41 | def returnIPaddr(u): 42 | ip = "" 43 | if u.startswith('http://'): 44 | remainhttp = u[7:] 45 | ip = string.split(remainhttp, '/')[0] 46 | if u.startswith('https://'): 47 | remainhttps = u[8:] 48 | ip = string.split(remainhttps, '/')[0] 49 | return ip 50 | 51 | 52 | def returnTitle(content): 53 | t1 = '' 54 | t2 = '' 55 | if '