Attempts to find hop-by-hop header abuse potential against the provided URL.
# github.com/ndavison | |
import requests | |
import random | |
import string | |
from argparse import ArgumentParser | |
parser = ArgumentParser(description="Attempts to find hop-by-hop header abuse potential against the provided URL.") | |
parser.add_argument("-u", "--url", help="URL to target (without query string)") | |
parser.add_argument("-x", "--headers", default="X-Forwarded-For", help="A comma separated list of headers to add as hop-by-hop") | |
parser.add_argument("-c", "--cache-test", action="store_true", help="Test for cache poisoning") | |
parser.add_argument("-d", "--disable-size-check", action="store_true", help="Don't compare response size when detecting changes between normal and hop-by-hop requests") | |
parser.add_argument("-v", "--verbose", action="store_true", help="More output") | |
args = parser.parse_args() | |
if not args.url: | |
print('Must supply a URL to target') | |
exit(1) | |
letters = string.ascii_lowercase | |
headers = { | |
'Connection': 'keep-alive, %s' % args.headers | |
} | |
params1 = { | |
'cb': ''.join(random.choice(letters) for i in range(10)) | |
} | |
params2 = { | |
'cb': ''.join(random.choice(letters) for i in range(10)) | |
} | |
# try a normal request and one with the hop-by-hop headers (and a cache buster to avoid accidental cache poisoning) | |
try: | |
if args.verbose: | |
print "Trying %s?%s" % (args.url, params1['cb']) | |
res1 = requests.get(args.url, params=params1, allow_redirects=False) | |
if args.verbose: | |
print 'Trying %s?%s with hop-by-hop headers "%s"' % (args.url, params2['cb'], args.headers) | |
res2 = requests.get(args.url, headers=headers, params=params2, allow_redirects=False) | |
except requests.exceptions.ConnectionError as e: | |
print e | |
exit(1) | |
# did adding the HbH headers cause a different response? | |
if res1.status_code != res2.status_code or (not args.disable_size_check and len(res1.content) != len(res2.content)): | |
if res1.status_code != res2.status_code: | |
print '%s returns a %s, but returned a %s with the hop-by-hop headers of "%s"' % (args.url, res1.status_code, res2.status_code, args.headers) | |
if not args.disable_size_check and len(res1.content) != len(res2.content): | |
print '%s was %s in response size, but was %s with the hop-by-hop headers of "%s"' % (args.url, len(res1.content), len(res2.content), args.headers) | |
# if enabled, run the cache poison test by quering the HbH request's cache buster without the HbH headers and comparing status codes | |
if args.cache_test: | |
try: | |
res3 = requests.get(args.url, params=params2, allow_redirects=False) | |
except requests.exceptions.ConnectionError as e: | |
print e | |
exit(1) | |
if res3.status_code == res2.status_code: | |
print '%s?cb=%s poisoned?' % (args.url, params2['cb']) | |
else: | |
print 'No poisoning detected' | |
else: | |
if args.verbose: | |
print 'No change detected requesting "%s" with the hop-by-hop headers "%s"' % (args.url, args.headers) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment