Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added ratelimit - not tested #39

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion VHostScan.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def main():
parser.add_argument("-b", dest="base_host", required=False, help="Set host to be used during substitution in wordlist (default to TARGET).", default=False)
parser.add_argument("-p", dest="port", required=False, help="Set the port to use (default 80).", default=80)
parser.add_argument("-r", dest="real_port", required=False, help="The real port of the webserver to use in headers when not 80 (see RFC2616 14.23), useful when pivoting through ssh/nc etc (default to PORT).", default=False)
parser.add_argument("-rl", dest="ratelimit", required=False, help="Set ratelimit per second. Ex 5 per second, default = 1")

parser.add_argument('--ignore-http-codes', dest='ignore_http_codes', type=str, help='Comma separated list of http codes to ignore with virtual host scans (default 404).', default='404')
parser.add_argument('--ignore-content-length', dest='ignore_content_length', type=int, help='Ignore content lengths of specificed amount (default 0).', default=0)
Expand Down Expand Up @@ -77,7 +78,7 @@ def main():
if(arguments.ignore_content_length > 0):
print("[>] Ignoring Content length: %s" % (arguments.ignore_content_length))

scanner = virtual_host_scanner( arguments.target_hosts, arguments.base_host, wordlist, arguments.port, arguments.real_port, arguments.ssl,
scanner = virtual_host_scanner( arguments.target_hosts, arguments.base_host, wordlist, arguments.port, arguments.real_port, arguments.ssl, arguments.ratelimit,
arguments.unique_depth, arguments.ignore_http_codes, arguments.ignore_content_length, arguments.fuzzy_logic, arguments.add_waf_bypass_headers)

scanner.scan()
Expand Down
66 changes: 65 additions & 1 deletion lib/core/virtual_host_scanner.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import os
import requests
import hashlib
from math import floor
import time
import threading
import pandas as pd
from lib.core.discovered_host import *

Expand All @@ -20,14 +23,15 @@ class virtual_host_scanner(object):
output: folder to write output file to
"""

def __init__(self, target, base_host, wordlist, port=80, real_port=80, ssl=False, unique_depth=1, ignore_http_codes='404', ignore_content_length=0, fuzzy_logic=False, add_waf_bypass_headers=False):
def __init__(self, target, base_host, wordlist,ratelimit, port=80, real_port=80, ssl=False, unique_depth=1, ignore_http_codes='404', ignore_content_length=0, fuzzy_logic=False, add_waf_bypass_headers=False):
self.target = target
self.base_host = base_host
self.port = int(port)
self.real_port = int(real_port)
self.ignore_http_codes = list(map(int, ignore_http_codes.replace(' ', '').split(',')))
self.ignore_content_length = ignore_content_length
self.wordlist = wordlist
self.ratelimit = ratelimit
self.unique_depth = unique_depth
self.ssl = ssl
self.fuzzy_logic = fuzzy_logic
Expand All @@ -42,6 +46,65 @@ def __init__(self, target, base_host, wordlist, port=80, real_port=80, ssl=False
# store associated data for discovered hosts in array for oN, oJ, etc'
self.hosts = []

def rate_limited(period=1, every=1.0):
'''
Prevent a method from being called
if it was previously called before
a time widows has elapsed.
:param int period: Maximum method invocations within a period. Must be greater than 0.
:param float every: A dampening factor (in seconds). Can be any number greater than 0.
:return: Decorated function that will forward method invocations if the time window has elapsed.
:rtype: function
'''
frequency = abs(every) / float(clamp(period))

def decorator(func):
'''
Extend the behaviour of the following
function, forwarding method invocations
if the time window hes elapsed.
:param function func: The function to decorate.
:return: Decorated function.
:rtype: function
'''

# To get around issues with function local scope
# and reassigning variables, we wrap the time
# within a list. When updating the value we're
# not reassigning `last_called`, which would not
# work, but instead reassigning the value at a
# particular index.
last_called = [0.0]

# Add thread safety
lock = threading.RLock()

def wrapper(*args, **kargs):
'''Decorator wrapper function'''
with lock:
elapsed = time.time() - last_called[0]
left_to_wait = frequency - elapsed
if left_to_wait > 0:
time.sleep(left_to_wait)
last_called[0] = time.time()
return func(*args, **kargs)

return wrapper

return decorator

def clamp(value):
'''
Clamp integer between 1 and max
There must be at least 1 method invocation
made over the time period. Make sure the
value passed is at least 1 and is not a
fraction of an invocation.
:param float value: The number of method invocations.
:return: Clamped number of invocations.
:rtype: int
'''
return max(1, min(sys.maxsize, floor(value)))

def scan(self):
if not self.base_host:
Expand Down Expand Up @@ -72,6 +135,7 @@ def scan(self):

dest_url = '{}://{}:{}/'.format('https' if self.ssl else 'http', self.target, self.port)

rate_limited(self.ratelimit)
try:
res = requests.get(dest_url, headers=headers, verify=False)
except requests.exceptions.RequestException:
Expand Down