Add setup.py, clean long lines, strip python2 code
This commit is contained in:
parent
3cfa9426f3
commit
e35f70292e
|
|
@ -1,359 +0,0 @@
|
|||
import random
|
||||
import string
|
||||
import asyncio
|
||||
import functools
|
||||
import os
|
||||
import uvloop
|
||||
import aiodns
|
||||
import click
|
||||
import socket
|
||||
import sys
|
||||
from tqdm import tqdm
|
||||
from aiodnsbrute.logger import ConsoleLogger
|
||||
|
||||
|
||||
class aioDNSBrute(object):
|
||||
"""aiodnsbrute implements fast domain name brute forcing using Python's asyncio module."""
|
||||
|
||||
def __init__(self, verbosity=0, max_tasks=512):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
verbosity: set output verbosity: 0 (default) is none, 3 is debug
|
||||
max_tasks: the maximum number of tasks asyncio will queue (default 512)
|
||||
"""
|
||||
self.tasks = []
|
||||
self.errors = []
|
||||
self.fqdn = []
|
||||
self.ignore_hosts = []
|
||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||
self.loop = asyncio.get_event_loop()
|
||||
self.resolver = aiodns.DNSResolver(loop=self.loop, rotate=True)
|
||||
self.sem = asyncio.BoundedSemaphore(max_tasks)
|
||||
self.max_tasks = max_tasks
|
||||
self.verbosity = verbosity
|
||||
self.logger = ConsoleLogger(verbosity)
|
||||
|
||||
async def _dns_lookup(self, name):
|
||||
"""Performs a DNS request using aiodns, self.lookup_type is set by the run function.
|
||||
A query for A record returns <ares_query_a_result> which does not return metadata about
|
||||
when a CNAME was resolved (just host and ttl attributes) however it should be faster.
|
||||
The <ares_host_result> returned by gethostbyname contains name, aliases, and addresses, if
|
||||
name is different in response we can surmise that the original domain was a CNAME entry.
|
||||
|
||||
Args:
|
||||
name: the domain name to resolve
|
||||
|
||||
Returns:
|
||||
object: <ares_query_a_result> if query, <ares_host_result> if gethostbyname
|
||||
"""
|
||||
if self.lookup_type == "query":
|
||||
return await self.resolver.query(name, "A")
|
||||
elif self.lookup_type == "gethostbyname":
|
||||
return await self.resolver.gethostbyname(name, socket.AF_INET)
|
||||
|
||||
def _dns_result_callback(self, name, future):
|
||||
"""Handles the pycares object passed by the _dns_lookup function. We expect an errror to
|
||||
be present in the returned object because most lookups will be for names that don't exist.
|
||||
c-ares errors are passed through directly, error types can be identified in ares_strerror.c
|
||||
|
||||
Args:
|
||||
name: original lookup name (because the query_result object doesn't contain it)
|
||||
future: the completed future (pycares dns result)
|
||||
"""
|
||||
# Record processed we can now release the lock
|
||||
self.sem.release()
|
||||
# Handle known exceptions, barf on other ones
|
||||
if future.exception() is not None:
|
||||
try:
|
||||
err_number = future.exception().args[0]
|
||||
err_text = future.exception().args[1]
|
||||
except IndexError:
|
||||
self.logger.error(f"Couldn't parse exception: {future.exception()}")
|
||||
# handle the DNS errors we expect to receive, show user unexpected errors
|
||||
if err_number == 4:
|
||||
# This is domain name not found, ignore it
|
||||
pass
|
||||
# elif err_number == 12:
|
||||
# Timeout from DNS server
|
||||
#self.logger.warn(f"Timeout for {name}")
|
||||
elif err_number == 1:
|
||||
# Server answered with no data
|
||||
pass
|
||||
#else:
|
||||
#self.logger.error(
|
||||
# f"{name} generated an unexpected exception: {future.exception()}"
|
||||
#)
|
||||
# for debugging/troubleshoooting keep a list of errors
|
||||
# self.errors.append({'hostname': name, 'error': err_text})
|
||||
|
||||
# parse and output and store results.
|
||||
else:
|
||||
if self.lookup_type == "query":
|
||||
ips = [ip.host for ip in future.result()]
|
||||
cname = False
|
||||
row = f"{name:<30}\t{ips}"
|
||||
elif self.lookup_type == "gethostbyname":
|
||||
r = future.result()
|
||||
ips = [ip for ip in r.addresses]
|
||||
if name == r.name:
|
||||
cname = False
|
||||
n = f"""{name:<30}\t{f"{'':<35}" if self.verbosity >= 2 else ""}"""
|
||||
else:
|
||||
cname = True
|
||||
# format the name based on verbosity - this is kluge
|
||||
short_cname = f"{r.name[:28]}.." if len(r.name) > 30 else r.name
|
||||
n = f'{name}{"**" if self.verbosity <= 1 else ""}'
|
||||
n = f'''{n:<30}\t{f"CNAME {short_cname:<30}" if self.verbosity >= 2 else ""}'''
|
||||
row = f"{n:<30}\t{ips}"
|
||||
# store the result
|
||||
if set(ips) != set(self.ignore_hosts):
|
||||
#self.logger.success(row)
|
||||
dns_lookup_result = {"domain": name, "ip": ips}
|
||||
if self.lookup_type == "gethostbyname" and cname:
|
||||
dns_lookup_result["cname"] = r.name
|
||||
dns_lookup_result["aliases"] = r.aliases
|
||||
self.fqdn.append(dns_lookup_result)
|
||||
self.logger.debug(future.result())
|
||||
self.tasks.remove(future)
|
||||
if self.verbosity >= 1:
|
||||
self.pbar.update()
|
||||
|
||||
|
||||
async def _queue_lookups(self, wordlist, domain):
|
||||
"""Takes a list of words and adds them to the async loop also passing the original
|
||||
lookup domain name; then attaches the processing callback to deal with the result.
|
||||
|
||||
Args:
|
||||
wordlist: a list of names to perform lookups for
|
||||
domain: the base domain to perform brute force against
|
||||
"""
|
||||
for word in wordlist:
|
||||
# Wait on the semaphore before adding more tasks
|
||||
await self.sem.acquire()
|
||||
host = f"{word.strip()}.{domain}"
|
||||
task = asyncio.ensure_future(self._dns_lookup(host))
|
||||
task.add_done_callback(functools.partial(self._dns_result_callback, host))
|
||||
self.tasks.append(task)
|
||||
await asyncio.gather(*self.tasks, return_exceptions=True)
|
||||
|
||||
def bruteforce_domain(target, resolvers=None, wordlist="subdomains-top1million-110000.txt", wildcard=True, verify=True, found_subdomains=[], thread_count=7000, query=True):
|
||||
subdomains_list = []
|
||||
names_list = []
|
||||
verbosity=1
|
||||
if resolvers:
|
||||
resolverfile = open(resolvers,'r')
|
||||
lines = resolverfile.read().splitlines()
|
||||
resolvers = [x.strip() for x in lines if (x and not x.startswith("#"))]
|
||||
bf = aioDNSBrute(verbosity=verbosity, max_tasks=thread_count)
|
||||
subdomains_list = bf.run(wordlist, target, resolvers, wildcard, verify, query)
|
||||
resolverfile.close()
|
||||
for r in range(1, len(subdomains_list)):
|
||||
names_list.append(subdomains_list[r]['domain'])
|
||||
|
||||
return names_list
|
||||
|
||||
def run(
|
||||
self, wordlist, domain, resolvers=None, wildcard=True, verify=True, query=True
|
||||
):
|
||||
"""
|
||||
Sets up the bruteforce job, does domain verification, sets resolvers, checks for wildcard
|
||||
response to lookups, and sets the query type to be used. After all this, open the wordlist
|
||||
file and start the brute force - with ^C handling to cleanup nicely.
|
||||
|
||||
Args:
|
||||
wordlist: a string containing a path to a filename to be used as a wordlist
|
||||
domain: the base domain name to be used for lookups
|
||||
resolvers: a list of DNS resolvers to be used (default None, uses system resolvers)
|
||||
wildcard: bool, do wildcard dns detection (default true)
|
||||
verify: bool, check if domain exists (default true)
|
||||
query: bool, use query to do lookups (default true), false means gethostbyname is used.
|
||||
|
||||
Returns:
|
||||
dict containing result of lookups
|
||||
"""
|
||||
self.logger.info(
|
||||
f"Brute forcing {domain} with a maximum of {self.max_tasks} concurrent tasks..."
|
||||
)
|
||||
if verify:
|
||||
#self.logger.info(f"Using local resolver to verify {domain} exists.")
|
||||
try:
|
||||
socket.gethostbyname(domain)
|
||||
except socket.gaierror as err:
|
||||
self.logger.error(
|
||||
f"Couldn't resolve {domain}, use the --no-verify switch to ignore this error."
|
||||
)
|
||||
raise SystemExit(
|
||||
self.logger.error(f"Error from host lookup: {err}")
|
||||
)
|
||||
else:
|
||||
self.logger.warn("Skipping domain verification. YOLO!")
|
||||
if resolvers:
|
||||
self.resolver.nameservers = resolvers
|
||||
self.logger.info(
|
||||
f"Using recursive DNS with {len(self.resolver.nameservers)} nameservers"
|
||||
)
|
||||
|
||||
if wildcard:
|
||||
# 63 chars is the max allowed segment length, there is practically no chance that it will be a legit record
|
||||
random_sld = (
|
||||
lambda: f'{"".join(random.choice(string.ascii_lowercase + string.digits) for i in range(63))}'
|
||||
)
|
||||
try:
|
||||
self.lookup_type = "query"
|
||||
wc_check = self.loop.run_until_complete(
|
||||
self._dns_lookup(f"{random_sld()}.{domain}")
|
||||
)
|
||||
except aiodns.error.DNSError as err:
|
||||
# we expect that the record will not exist and error 4 will be thrown
|
||||
#self.logger.info(
|
||||
# f"No wildcard response was detected for this domain."
|
||||
#)
|
||||
wc_check = None
|
||||
finally:
|
||||
if wc_check is not None:
|
||||
self.ignore_hosts = [host.host for host in wc_check]
|
||||
self.logger.warn(
|
||||
f"Wildcard response detected, ignoring answers containing {self.ignore_hosts}"
|
||||
)
|
||||
else:
|
||||
self.logger.warn("Wildcard detection is disabled")
|
||||
|
||||
if query:
|
||||
#self.logger.info(
|
||||
# "Using pycares `query` function to perform lookups, CNAMEs cannot be identified"
|
||||
#)
|
||||
self.lookup_type = "query"
|
||||
else:
|
||||
self.logger.info(
|
||||
"Using pycares `gethostbyname` function to perform lookups, CNAME data will be appended to results (** denotes CNAME, show actual name with -vv)"
|
||||
)
|
||||
self.lookup_type = "gethostbyname"
|
||||
|
||||
with open(wordlist, encoding="utf-8", errors="ignore") as words:
|
||||
w = words.read().splitlines()
|
||||
self.logger.info(f"Wordlist loaded, proceeding with {len(w)} DNS requests")
|
||||
try:
|
||||
if self.verbosity >= 1:
|
||||
self.pbar = tqdm(
|
||||
total=len(w), unit="rec", maxinterval=0.1, mininterval=0
|
||||
)
|
||||
self.loop.run_until_complete(self._queue_lookups(w, domain))
|
||||
except KeyboardInterrupt:
|
||||
self.logger.warn("Caught keyboard interrupt, cleaning up...")
|
||||
asyncio.gather(*asyncio.Task.all_tasks()).cancel()
|
||||
self.loop.stop()
|
||||
finally:
|
||||
self.loop.close()
|
||||
if self.verbosity >= 1:
|
||||
self.pbar.close()
|
||||
self.logger.info(f"Bruteforcing Complete")
|
||||
return self.fqdn
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option(
|
||||
"--wordlist",
|
||||
"-w",
|
||||
help="Wordlist to use for brute force.",
|
||||
default=f"{os.path.dirname(os.path.realpath(__file__))}/wordlists/bitquark_20160227_subdomains_popular_1000",
|
||||
)
|
||||
@click.option(
|
||||
"--max-tasks",
|
||||
"-t",
|
||||
default=512,
|
||||
help="Maximum number of tasks to run asynchronosly.",
|
||||
)
|
||||
@click.option(
|
||||
"--resolver-file",
|
||||
"-r",
|
||||
type=click.File("r"),
|
||||
default=None,
|
||||
help="A text file containing a list of DNS resolvers to use, one per line, comments start with #. Default: use system resolvers",
|
||||
)
|
||||
@click.option(
|
||||
"--verbosity", "-v", count=True, default=1, help="Increase output verbosity"
|
||||
)
|
||||
@click.option(
|
||||
"--output",
|
||||
"-o",
|
||||
type=click.Choice(["csv", "json", "off"]),
|
||||
default="off",
|
||||
help="Output results to DOMAIN.csv/json (extension automatically appended when not using -f).",
|
||||
)
|
||||
@click.option(
|
||||
"--outfile",
|
||||
"-f",
|
||||
type=click.File("w"),
|
||||
help="Output filename. Use '-f -' to send file output to stdout overriding normal output.",
|
||||
)
|
||||
@click.option(
|
||||
"--query/--gethostbyname",
|
||||
default=True,
|
||||
help="DNS lookup type to use query (default) should be faster, but won't return CNAME information.",
|
||||
)
|
||||
@click.option(
|
||||
"--wildcard/--no-wildcard",
|
||||
default=True,
|
||||
help="Wildcard detection, enabled by default",
|
||||
)
|
||||
@click.option(
|
||||
"--verify/--no-verify",
|
||||
default=True,
|
||||
help="Verify domain name is sane before beginning, enabled by default",
|
||||
)
|
||||
@click.version_option("0.3.2")
|
||||
@click.argument("domain", required=True)
|
||||
def main(**kwargs):
|
||||
"""aiodnsbrute is a command line tool for brute forcing domain names utilizing Python's asyncio module.
|
||||
|
||||
credit: blark (@markbaseggio)
|
||||
"""
|
||||
output = kwargs.get("output")
|
||||
verbosity = kwargs.get("verbosity")
|
||||
resolvers = kwargs.get("resolver_file")
|
||||
if output != "off":
|
||||
outfile = kwargs.get("outfile")
|
||||
# turn off output if we want JSON/CSV to stdout, hacky
|
||||
if outfile.__class__.__name__ == "TextIOWrapper":
|
||||
verbosity = 0
|
||||
if outfile is None:
|
||||
# wasn't specified on command line
|
||||
outfile = open(f'{kwargs["domain"]}.{output}', "w")
|
||||
if resolvers:
|
||||
lines = resolvers.read().splitlines()
|
||||
resolvers = [x.strip() for x in lines if (x and not x.startswith("#"))]
|
||||
|
||||
bf = aioDNSBrute(verbosity=verbosity, max_tasks=kwargs.get("max_tasks"))
|
||||
results = bf.run(
|
||||
wordlist=kwargs.get("wordlist"),
|
||||
domain=kwargs.get("domain"),
|
||||
resolvers=resolvers,
|
||||
wildcard=kwargs.get("wildcard"),
|
||||
verify=kwargs.get("verify"),
|
||||
query=kwargs.get("query"),
|
||||
)
|
||||
|
||||
if output in ("json"):
|
||||
import json
|
||||
json.dump(results, outfile)
|
||||
|
||||
if output in ("csv"):
|
||||
import csv
|
||||
writer = csv.writer(outfile)
|
||||
writer.writerow(["Hostname", "IPs", "CNAME", "Aliases"])
|
||||
[
|
||||
writer.writerow(
|
||||
[
|
||||
r.get("domain"),
|
||||
r.get("ip", [""])[0],
|
||||
r.get("cname"),
|
||||
r.get("aliases", [""])[0],
|
||||
]
|
||||
)
|
||||
for r in results
|
||||
]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
from tqdm import tqdm
|
||||
from click import style
|
||||
|
||||
|
||||
class ConsoleLogger(object):
|
||||
"""A quick and dirty metasploit style console output logger that doesn't mess up tqdm output."""
|
||||
|
||||
def __init__(self, verbosity):
|
||||
self.verbosity = verbosity
|
||||
self.msg_type = {
|
||||
"info": ("[*]", "blue", 1),
|
||||
"success": ("[+]", "green", 1),
|
||||
"error": ("[-]", "red", 1),
|
||||
"warn": ("[!]", "yellow", 1),
|
||||
"debug": ("[D]", "cyan", 3),
|
||||
}
|
||||
|
||||
def __getattr__(self, attr):
|
||||
try:
|
||||
decorator = style(
|
||||
f"{self.msg_type[attr][0]} ", fg=self.msg_type[attr][1], bold=True
|
||||
)
|
||||
msg_verbosity = self.msg_type[attr][2]
|
||||
except KeyError:
|
||||
decorator = ""
|
||||
msg_verbosity = 1
|
||||
finally:
|
||||
if self.verbosity >= msg_verbosity:
|
||||
return lambda msg: tqdm.write(f"{decorator}{msg}")
|
||||
else:
|
||||
return lambda msg: None
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
from tqdm import tqdm
|
||||
from click import style
|
||||
|
||||
|
||||
class ConsoleLogger(object):
|
||||
"""A quick and dirty metasploit style console output logger that doesn't mess up tqdm output."""
|
||||
|
||||
def __init__(self, verbosity):
|
||||
self.verbosity = verbosity
|
||||
self.msg_type = {
|
||||
"info": ("[*]", "blue", 1),
|
||||
"success": ("[+]", "green", 1),
|
||||
"error": ("[-]", "red", 1),
|
||||
"warn": ("[!]", "yellow", 1),
|
||||
"debug": ("[D]", "cyan", 3),
|
||||
}
|
||||
|
||||
def __getattr__(self, attr):
|
||||
try:
|
||||
decorator = style(
|
||||
f"{self.msg_type[attr][0]} ", fg=self.msg_type[attr][1], bold=True
|
||||
)
|
||||
msg_verbosity = self.msg_type[attr][2]
|
||||
except KeyError:
|
||||
decorator = ""
|
||||
msg_verbosity = 1
|
||||
finally:
|
||||
if self.verbosity >= msg_verbosity:
|
||||
return lambda msg: tqdm.write(f"{decorator}{msg}")
|
||||
else:
|
||||
return lambda msg: None
|
||||
226
sublist3r2.py
226
sublist3r2.py
|
|
@ -2,41 +2,25 @@
|
|||
# coding: utf-8
|
||||
# Sublist3r2 v1.0
|
||||
|
||||
|
||||
# modules in standard library
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
# Builtin imports
|
||||
import argparse
|
||||
import time
|
||||
import hashlib
|
||||
import random
|
||||
import multiprocessing
|
||||
import threading
|
||||
import socket
|
||||
import json
|
||||
import multiprocessing
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import socket
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import urllib.parse as urlparse
|
||||
from collections import Counter
|
||||
|
||||
# external modules
|
||||
# External imports
|
||||
import dns.resolver
|
||||
import requests
|
||||
from aiodnsbrute.cli import aioDNSBrute
|
||||
|
||||
# Python 2.x and 3.x compatiablity
|
||||
if sys.version > '3':
|
||||
import urllib.parse as urlparse
|
||||
import urllib.parse as urllib
|
||||
else:
|
||||
import urlparse
|
||||
import urllib
|
||||
|
||||
# In case you cannot install some of the required development packages
|
||||
# there's also an option to disable the SSL warning:
|
||||
try:
|
||||
import requests.packages.urllib3
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
except:
|
||||
pass
|
||||
from aiodnsbrute.cli import aioDNSBrute
|
||||
|
||||
# Check if we are running this on windows platform
|
||||
is_windows = sys.platform.startswith('win')
|
||||
|
|
@ -50,15 +34,14 @@ if is_windows:
|
|||
R = '\033[91m' # red
|
||||
W = '\033[0m' # white
|
||||
try:
|
||||
import win_unicode_console , colorama
|
||||
import colorama
|
||||
import win_unicode_console
|
||||
win_unicode_console.enable()
|
||||
colorama.init()
|
||||
#Now the unicode will work ^_^
|
||||
except:
|
||||
# Now the unicode will work ^_^
|
||||
except Exception:
|
||||
print("[!] Error: Coloring libraries not installed, no coloring will be used [Check the readme]")
|
||||
G = Y = B = R = W = G = Y = B = R = W = ''
|
||||
|
||||
|
||||
G = Y = B = R = W = ''
|
||||
else:
|
||||
G = '\033[92m' # green
|
||||
Y = '\033[93m' # yellow
|
||||
|
|
@ -66,6 +49,7 @@ else:
|
|||
R = '\033[91m' # red
|
||||
W = '\033[0m' # white
|
||||
|
||||
|
||||
def no_color():
|
||||
global G, Y, B, R, W
|
||||
G = Y = B = R = W = ''
|
||||
|
|
@ -73,13 +57,13 @@ def no_color():
|
|||
|
||||
def banner():
|
||||
print("""%s
|
||||
____ _ _ _ _ _____ _ ____
|
||||
____ _ _ _ _ _____ ______
|
||||
/ ___| _ _| |__ | (_)___| |_|___ / _ __\ __ | Sublist3r2 v1.0
|
||||
\___ \| | | | '_ \| | / __| __| |_ \| '__| / / a subdomains enum tool originally by @aboul3la
|
||||
___) | |_| | |_) | | \__ \ |_ ___) | | / /_ maintained by Ronin Nakomoto
|
||||
|____/ \__,_|_.__/|_|_|___/\__|____/|_| /____|%s%s https://github.com/RoninNakomoto/Sublist3r2
|
||||
|____/ \__,_|_.__/|_|_|___/\__|____/|_| /____|%s https://github.com/RoninNakomoto/Sublist3r2
|
||||
|
||||
""" % (R, W, Y))
|
||||
""" % (R, Y))
|
||||
|
||||
|
||||
def parser_error(errmsg):
|
||||
|
|
@ -258,7 +242,10 @@ class enumratorBase(object):
|
|||
class enumratorBaseThreaded(multiprocessing.Process, enumratorBase):
|
||||
def __init__(self, base_url, engine_name, domain, subdomains=None, q=None, silent=False, verbose=True):
|
||||
subdomains = subdomains or []
|
||||
enumratorBase.__init__(self, base_url, engine_name, domain, subdomains, silent=silent, verbose=verbose)
|
||||
enumratorBase.__init__(
|
||||
self, base_url, engine_name, domain, subdomains,
|
||||
silent=silent, verbose=verbose
|
||||
)
|
||||
multiprocessing.Process.__init__(self)
|
||||
self.q = q
|
||||
return
|
||||
|
|
@ -276,13 +263,16 @@ class GoogleEnum(enumratorBaseThreaded):
|
|||
self.engine_name = "Google"
|
||||
self.MAX_DOMAINS = 11
|
||||
self.MAX_PAGES = 200
|
||||
super(GoogleEnum, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose)
|
||||
super(GoogleEnum, self).__init__(
|
||||
base_url, self.engine_name, domain, subdomains,
|
||||
q=q, silent=silent, verbose=verbose
|
||||
)
|
||||
self.q = q
|
||||
return
|
||||
|
||||
def extract_domains(self, resp):
|
||||
links_list = list()
|
||||
link_regx = re.compile('<cite.*?>(.*?)<\/cite>')
|
||||
link_regx = re.compile(r'<cite.*?>(.*?)<\/cite>')
|
||||
try:
|
||||
links_list = link_regx.findall(resp)
|
||||
for link in links_list:
|
||||
|
|
@ -299,7 +289,7 @@ class GoogleEnum(enumratorBaseThreaded):
|
|||
return links_list
|
||||
|
||||
def check_response_errors(self, resp):
|
||||
if (type(resp) is str or type(resp) is unicode) and 'Our systems have detected unusual traffic' in resp:
|
||||
if (type(resp) is str) and 'Our systems have detected unusual traffic' in resp:
|
||||
self.print_(R + "[!] Error: Google probably now is blocking our requests" + W)
|
||||
self.print_(R + "[~] Finished now the Google Enumeration ..." + W)
|
||||
return False
|
||||
|
|
@ -326,20 +316,23 @@ class YahooEnum(enumratorBaseThreaded):
|
|||
self.engine_name = "Yahoo"
|
||||
self.MAX_DOMAINS = 10
|
||||
self.MAX_PAGES = 0
|
||||
super(YahooEnum, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose)
|
||||
super(YahooEnum, self).__init__(
|
||||
base_url, self.engine_name, domain, subdomains,
|
||||
q=q, silent=silent, verbose=verbose
|
||||
)
|
||||
self.q = q
|
||||
return
|
||||
|
||||
def extract_domains(self, resp):
|
||||
link_regx2 = re.compile('<span class=" fz-.*? fw-m fc-12th wr-bw.*?">(.*?)</span>')
|
||||
link_regx = re.compile('<span class="txt"><span class=" cite fw-xl fz-15px">(.*?)</span>')
|
||||
link_regx2 = re.compile(r'<span class=" fz-.*? fw-m fc-12th wr-bw.*?">(.*?)</span>')
|
||||
link_regx = re.compile(r'<span class="txt"><span class=" cite fw-xl fz-15px">(.*?)</span>')
|
||||
links_list = []
|
||||
try:
|
||||
links = link_regx.findall(resp)
|
||||
links2 = link_regx2.findall(resp)
|
||||
links_list = links + links2
|
||||
for link in links_list:
|
||||
link = re.sub("<(\/)?b>", "", link)
|
||||
link = re.sub(r'<(\/)?b>', '', link)
|
||||
if not link.startswith('http'):
|
||||
link = "http://" + link
|
||||
subdomain = urlparse.urlparse(link).netloc
|
||||
|
|
@ -377,13 +370,16 @@ class AskEnum(enumratorBaseThreaded):
|
|||
self.engine_name = "Ask"
|
||||
self.MAX_DOMAINS = 11
|
||||
self.MAX_PAGES = 0
|
||||
enumratorBaseThreaded.__init__(self, base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose)
|
||||
enumratorBaseThreaded.__init__(
|
||||
self, base_url, self.engine_name, domain, subdomains,
|
||||
q=q, silent=silent, verbose=verbose
|
||||
)
|
||||
self.q = q
|
||||
return
|
||||
|
||||
def extract_domains(self, resp):
|
||||
links_list = list()
|
||||
link_regx = re.compile('<p class="web-result-url">(.*?)</p>')
|
||||
link_regx = re.compile(r'<p class="web-result-url">(.*?)</p>')
|
||||
try:
|
||||
links_list = link_regx.findall(resp)
|
||||
for link in links_list:
|
||||
|
|
@ -420,22 +416,25 @@ class BingEnum(enumratorBaseThreaded):
|
|||
self.engine_name = "Bing"
|
||||
self.MAX_DOMAINS = 30
|
||||
self.MAX_PAGES = 0
|
||||
enumratorBaseThreaded.__init__(self, base_url, self.engine_name, domain, subdomains, q=q, silent=silent)
|
||||
enumratorBaseThreaded.__init__(
|
||||
self, base_url, self.engine_name, domain, subdomains,
|
||||
q=q, silent=silent
|
||||
)
|
||||
self.q = q
|
||||
self.verbose = verbose
|
||||
return
|
||||
|
||||
def extract_domains(self, resp):
|
||||
links_list = list()
|
||||
link_regx = re.compile('<li class="b_algo"><h2><a href="(.*?)"')
|
||||
link_regx2 = re.compile('<div class="b_title"><h2><a href="(.*?)"')
|
||||
link_regx = re.compile(r'<li class="b_algo"><h2><a href="(.*?)"')
|
||||
link_regx2 = re.compile(r'<div class="b_title"><h2><a href="(.*?)"')
|
||||
try:
|
||||
links = link_regx.findall(resp)
|
||||
links2 = link_regx2.findall(resp)
|
||||
links_list = links + links2
|
||||
|
||||
for link in links_list:
|
||||
link = re.sub('<(\/)?strong>|<span.*?>|<|>', '', link)
|
||||
link = re.sub(r'<(\/)?strong>|<span.*?>|<|>', '', link)
|
||||
if not link.startswith('http'):
|
||||
link = "http://" + link
|
||||
subdomain = urlparse.urlparse(link).netloc
|
||||
|
|
@ -465,7 +464,10 @@ class BaiduEnum(enumratorBaseThreaded):
|
|||
self.engine_name = "Baidu"
|
||||
self.MAX_DOMAINS = 2
|
||||
self.MAX_PAGES = 760
|
||||
enumratorBaseThreaded.__init__(self, base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose)
|
||||
enumratorBaseThreaded.__init__(
|
||||
self, base_url, self.engine_name, domain, subdomains,
|
||||
q=q, silent=silent, verbose=verbose
|
||||
)
|
||||
self.querydomain = self.domain
|
||||
self.q = q
|
||||
return
|
||||
|
|
@ -474,11 +476,11 @@ class BaiduEnum(enumratorBaseThreaded):
|
|||
links = list()
|
||||
found_newdomain = False
|
||||
subdomain_list = []
|
||||
link_regx = re.compile('<a.*?class="c-showurl".*?>(.*?)</a>')
|
||||
link_regx = re.compile(r'<a.*?class="c-showurl".*?>(.*?)</a>')
|
||||
try:
|
||||
links = link_regx.findall(resp)
|
||||
for link in links:
|
||||
link = re.sub('<.*?>|>|<| ', '', link)
|
||||
link = re.sub(r'<.*?>|>|<| ', '', link)
|
||||
if not link.startswith('http'):
|
||||
link = "http://" + link
|
||||
subdomain = urlparse.urlparse(link).netloc
|
||||
|
|
@ -523,7 +525,10 @@ class NetcraftEnum(enumratorBaseThreaded):
|
|||
subdomains = subdomains or []
|
||||
self.base_url = 'https://searchdns.netcraft.com/?restriction=site+ends+with&host={domain}'
|
||||
self.engine_name = "Netcraft"
|
||||
super(NetcraftEnum, self).__init__(self.base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose)
|
||||
super(NetcraftEnum, self).__init__(
|
||||
self.base_url, self.engine_name, domain, subdomains,
|
||||
q=q, silent=silent, verbose=verbose
|
||||
)
|
||||
self.q = q
|
||||
return
|
||||
|
||||
|
|
@ -541,7 +546,7 @@ class NetcraftEnum(enumratorBaseThreaded):
|
|||
return
|
||||
|
||||
def get_next(self, resp):
|
||||
link_regx = re.compile('<a.*?href="(.*?)">Next Page')
|
||||
link_regx = re.compile(r'<a.*?href="(.*?)">Next Page')
|
||||
link = link_regx.findall(resp)
|
||||
url = 'http://searchdns.netcraft.com' + link[0]
|
||||
return url
|
||||
|
|
@ -551,7 +556,9 @@ class NetcraftEnum(enumratorBaseThreaded):
|
|||
cookies_list = cookie[0:cookie.find(';')].split("=")
|
||||
cookies[cookies_list[0]] = cookies_list[1]
|
||||
# hashlib.sha1 requires utf-8 encoded str
|
||||
cookies['netcraft_js_verification_response'] = hashlib.sha1(urllib.unquote(cookies_list[1]).encode('utf-8')).hexdigest()
|
||||
cookies['netcraft_js_verification_response'] = hashlib.sha1(
|
||||
urlparse.unquote(cookies_list[1]).encode('utf-8')
|
||||
).hexdigest()
|
||||
return cookies
|
||||
|
||||
def get_cookies(self, headers):
|
||||
|
|
@ -577,7 +584,7 @@ class NetcraftEnum(enumratorBaseThreaded):
|
|||
|
||||
def extract_domains(self, resp):
|
||||
links_list = list()
|
||||
link_regx = re.compile('<a class="results-table__host" href="(.*?)"')
|
||||
link_regx = re.compile(r'<a class="results-table__host" href="(.*?)"')
|
||||
try:
|
||||
links_list = link_regx.findall(resp)
|
||||
for link in links_list:
|
||||
|
|
@ -601,7 +608,10 @@ class DNSdumpster(enumratorBaseThreaded):
|
|||
self.engine_name = "DNSdumpster"
|
||||
self.q = q
|
||||
self.lock = None
|
||||
super(DNSdumpster, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose)
|
||||
super(DNSdumpster, self).__init__(
|
||||
base_url, self.engine_name, domain, subdomains,
|
||||
q=q, silent=silent, verbose=verbose
|
||||
)
|
||||
return
|
||||
|
||||
def check_host(self, host):
|
||||
|
|
@ -616,7 +626,7 @@ class DNSdumpster(enumratorBaseThreaded):
|
|||
self.print_("%s%s: %s%s" % (R, self.engine_name, W, host))
|
||||
is_valid = True
|
||||
self.live_subdomains.append(host)
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
self.lock.release()
|
||||
return is_valid
|
||||
|
|
@ -636,7 +646,7 @@ class DNSdumpster(enumratorBaseThreaded):
|
|||
return self.get_response(resp)
|
||||
|
||||
def get_csrftoken(self, resp):
|
||||
csrf_regex = re.compile('<input type="hidden" name="csrfmiddlewaretoken" value="(.*?)">', re.S)
|
||||
csrf_regex = re.compile(r'<input type="hidden" name="csrfmiddlewaretoken" value="(.*?)">', re.S)
|
||||
token = csrf_regex.findall(resp)[0]
|
||||
return token.strip()
|
||||
|
||||
|
|
@ -654,8 +664,8 @@ class DNSdumpster(enumratorBaseThreaded):
|
|||
return self.live_subdomains
|
||||
|
||||
def extract_domains(self, resp):
|
||||
tbl_regex = re.compile('<a name="hostanchor"><\/a>Host Records.*?<table.*?>(.*?)</table>', re.S)
|
||||
link_regex = re.compile('<td class="col-md-4">(.*?)<br>', re.S)
|
||||
tbl_regex = re.compile(r'<a name="hostanchor"><\/a>Host Records.*?<table.*?>(.*?)</table>', re.S)
|
||||
link_regex = re.compile(r'<td class="col-md-4">(.*?)<br>', re.S)
|
||||
links = []
|
||||
try:
|
||||
results_tbl = tbl_regex.findall(resp)[0]
|
||||
|
|
@ -678,23 +688,26 @@ class Virustotal(enumratorBaseThreaded):
|
|||
base_url = 'https://www.virustotal.com/api/v3/domains/{domain}/subdomains'
|
||||
self.engine_name = "Virustotal"
|
||||
if os.getenv("VT_APIKEY") is None:
|
||||
VT_APIKEY=input(B + "[+] Enter VirusTotal API key, press Enter for none: " + W)
|
||||
VT_APIKEY=VT_APIKEY.strip()
|
||||
VT_APIKEY = input(B + "[+] Enter VirusTotal API key, press Enter for none: " + W)
|
||||
VT_APIKEY = VT_APIKEY.strip()
|
||||
if VT_APIKEY != "":
|
||||
os.environ["VT_APIKEY"]=(VT_APIKEY)
|
||||
os.environ["VT_APIKEY"] = (VT_APIKEY)
|
||||
else:
|
||||
VT_APIKEY = os.getenv("VT_APIKEY")
|
||||
os.environ["VT_APIKEY"]=(VT_APIKEY)
|
||||
os.environ["VT_APIKEY"] = (VT_APIKEY)
|
||||
self.apikey = os.getenv('VT_APIKEY', None)
|
||||
self.q = q
|
||||
super(Virustotal, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose)
|
||||
super(Virustotal, self).__init__(
|
||||
base_url, self.engine_name, domain, subdomains,
|
||||
q=q, silent=silent, verbose=verbose
|
||||
)
|
||||
self.url = self.base_url.format(domain=self.domain)
|
||||
return
|
||||
|
||||
# the main send_req need to be rewritten
|
||||
def send_req(self, url):
|
||||
try:
|
||||
self.headers.update({'X-ApiKey':self.apikey})
|
||||
self.headers.update({'X-ApiKey': self.apikey})
|
||||
resp = self.session.get(url, headers=self.headers, timeout=self.timeout)
|
||||
except Exception as e:
|
||||
self.print_(e)
|
||||
|
|
@ -705,11 +718,10 @@ class Virustotal(enumratorBaseThreaded):
|
|||
def enumerate(self):
|
||||
if self.apikey:
|
||||
while self.url != '':
|
||||
#try:
|
||||
resp = self.send_req(self.url)
|
||||
resp = json.loads(resp)
|
||||
if 'error' in resp:
|
||||
self.print_(R + "Error Code: {}".format(resp['error']["code"]) +W)
|
||||
self.print_(R + "Error Code: {}".format(resp['error']["code"]) + W)
|
||||
self.print_(R + "Virus Total Server Message: {}".format(resp['error']["message"]) + W)
|
||||
break
|
||||
if 'links' in resp and 'next' in resp['links']:
|
||||
|
|
@ -719,8 +731,8 @@ class Virustotal(enumratorBaseThreaded):
|
|||
self.extract_domains(resp)
|
||||
else:
|
||||
self.print_(R + "[!] Error: VirusTotal API key environment variable not found. Skipping" + W)
|
||||
self.print_(R + "[!] set VT_APIKEY to your virus total API key using: export VT_APIKEY=Your_VT_API_KEY_VALUE" + W)
|
||||
self.print_(B + "[!] To get a VT APIKEY, register at https://www.virustotal.com/gui/join-us" +W)
|
||||
self.print_(R + "[!] set VT_APIKEY to your virus total API key using: `export VT_APIKEY=Your_VT_API_KEY_VALUE`" + W)
|
||||
self.print_(B + "[!] To get a VT APIKEY, register at https://www.virustotal.com/gui/join-us" + W)
|
||||
return self.subdomains
|
||||
|
||||
def extract_domains(self, resp):
|
||||
|
|
@ -745,7 +757,10 @@ class ThreatCrowd(enumratorBaseThreaded):
|
|||
base_url = 'https://www.threatcrowd.org/searchApi/v2/domain/report/?domain={domain}'
|
||||
self.engine_name = "ThreatCrowd"
|
||||
self.q = q
|
||||
super(ThreatCrowd, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose)
|
||||
super(ThreatCrowd, self).__init__(
|
||||
base_url, self.engine_name, domain, subdomains,
|
||||
q=q, silent=silent, verbose=verbose
|
||||
)
|
||||
return
|
||||
|
||||
def req(self, url):
|
||||
|
|
@ -783,7 +798,10 @@ class CrtSearch(enumratorBaseThreaded):
|
|||
base_url = 'https://crt.sh/?q=%25.{domain}'
|
||||
self.engine_name = "SSL Certificates"
|
||||
self.q = q
|
||||
super(CrtSearch, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose)
|
||||
super(CrtSearch, self).__init__(
|
||||
base_url, self.engine_name, domain, subdomains,
|
||||
q=q, silent=silent, verbose=verbose
|
||||
)
|
||||
return
|
||||
|
||||
def req(self, url):
|
||||
|
|
@ -802,7 +820,7 @@ class CrtSearch(enumratorBaseThreaded):
|
|||
return self.subdomains
|
||||
|
||||
def extract_domains(self, resp):
|
||||
link_regx = re.compile('<TD>(.*?)</TD>')
|
||||
link_regx = re.compile(r'<TD>(.*?)</TD>')
|
||||
try:
|
||||
links = link_regx.findall(resp)
|
||||
for link in links:
|
||||
|
|
@ -818,7 +836,7 @@ class CrtSearch(enumratorBaseThreaded):
|
|||
continue
|
||||
|
||||
if '@' in subdomain:
|
||||
subdomain = subdomain[subdomain.find('@')+1:]
|
||||
subdomain = subdomain[subdomain.find('@') + 1:]
|
||||
|
||||
if subdomain not in self.subdomains and subdomain != self.domain:
|
||||
if self.verbose:
|
||||
|
|
@ -828,13 +846,17 @@ class CrtSearch(enumratorBaseThreaded):
|
|||
print(e)
|
||||
pass
|
||||
|
||||
|
||||
class PassiveDNS(enumratorBaseThreaded):
|
||||
def __init__(self, domain, subdomains=None, q=None, silent=False, verbose=True):
|
||||
subdomains = subdomains or []
|
||||
base_url = 'https://api.sublist3r.com/search.php?domain={domain}'
|
||||
self.engine_name = "PassiveDNS"
|
||||
self.q = q
|
||||
super(PassiveDNS, self).__init__(base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose)
|
||||
super(PassiveDNS, self).__init__(
|
||||
base_url, self.engine_name, domain, subdomains,
|
||||
q=q, silent=silent, verbose=verbose
|
||||
)
|
||||
return
|
||||
|
||||
def req(self, url):
|
||||
|
|
@ -910,7 +932,7 @@ def main(domain, threads, savefile, ports, silent, verbose, enable_bruteforce, e
|
|||
enable_bruteforce = True
|
||||
|
||||
# Validate domain
|
||||
domain_check = re.compile("^(http|https)?[a-zA-Z0-9]+([\-\.]{1}[a-zA-Z0-9]+)*\.[a-zA-Z]{2,}$")
|
||||
domain_check = re.compile(r'^(http|https)?[a-zA-Z0-9]+([\-\.]{1}[a-zA-Z0-9]+)*\.[a-zA-Z]{2,}$')
|
||||
if not domain_check.match(domain):
|
||||
if not silent:
|
||||
print(R + "Error: Please enter a valid domain" + W)
|
||||
|
|
@ -927,18 +949,19 @@ def main(domain, threads, savefile, ports, silent, verbose, enable_bruteforce, e
|
|||
if verbose and not silent:
|
||||
print(Y + "[-] verbosity is enabled, will show the subdomains results in realtime" + W)
|
||||
|
||||
supported_engines = {'baidu': BaiduEnum,
|
||||
'yahoo': YahooEnum,
|
||||
'google': GoogleEnum,
|
||||
'bing': BingEnum,
|
||||
'ask': AskEnum,
|
||||
'netcraft': NetcraftEnum,
|
||||
'dnsdumpster': DNSdumpster,
|
||||
'virustotal': Virustotal,
|
||||
'threatcrowd': ThreatCrowd,
|
||||
'ssl': CrtSearch,
|
||||
'passivedns': PassiveDNS
|
||||
}
|
||||
supported_engines = {
|
||||
'baidu': BaiduEnum,
|
||||
'yahoo': YahooEnum,
|
||||
'google': GoogleEnum,
|
||||
'bing': BingEnum,
|
||||
'ask': AskEnum,
|
||||
'netcraft': NetcraftEnum,
|
||||
'dnsdumpster': DNSdumpster,
|
||||
'virustotal': Virustotal,
|
||||
'threatcrowd': ThreatCrowd,
|
||||
'ssl': CrtSearch,
|
||||
'passivedns': PassiveDNS
|
||||
}
|
||||
|
||||
chosenEnums = []
|
||||
|
||||
|
|
@ -968,15 +991,16 @@ def main(domain, threads, savefile, ports, silent, verbose, enable_bruteforce, e
|
|||
if enable_bruteforce:
|
||||
if not silent:
|
||||
print(G + "[-] Starting bruteforce module now using aiodnsbrute.." + W)
|
||||
record_type = False
|
||||
path_to_file = os.path.dirname(os.path.realpath(__file__))
|
||||
subs = os.path.join(path_to_file, 'aiodnsbrute', 'subdomains-top1million-110000.txt')
|
||||
resolvers = os.path.join(path_to_file, 'aiodnsbrute', 'resolvers.txt')
|
||||
wildcard=True
|
||||
verify=True
|
||||
query=True
|
||||
subs = os.path.join(path_to_file, 'data', 'subdomains-top1million-110000.txt')
|
||||
resolvers = os.path.join(path_to_file, 'data', 'resolvers.txt')
|
||||
wildcard = True
|
||||
verify = True
|
||||
query = True
|
||||
thread_count = threads
|
||||
bruteforce_list = aioDNSBrute.bruteforce_domain(parsed_domain.netloc, resolvers, subs, wildcard, verify, search_list, thread_count, query)
|
||||
bruteforce_list = aioDNSBrute.bruteforce_domain(
|
||||
parsed_domain.netloc, resolvers, subs, wildcard, verify, search_list, thread_count, query
|
||||
)
|
||||
subdomains = search_list.union(bruteforce_list)
|
||||
|
||||
if subdomains:
|
||||
|
|
@ -1015,7 +1039,11 @@ def interactive():
|
|||
if args.no_color:
|
||||
no_color()
|
||||
banner()
|
||||
res = main(domain, threads, savefile, ports, silent=False, verbose=verbose, enable_bruteforce=enable_bruteforce, engines=engines)
|
||||
main(
|
||||
domain, threads, savefile, ports,
|
||||
silent=False, verbose=verbose, enable_bruteforce=enable_bruteforce, engines=engines
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
interactive()
|
||||
|
|
|
|||
Loading…
Reference in New Issue