Skip to content

Instantly share code, notes, and snippets.

@ndavison
Last active November 1, 2024 20:00
Show Gist options
  • Save ndavison/298d11b3a77b97c908d63a345d3c624d to your computer and use it in GitHub Desktop.
Save ndavison/298d11b3a77b97c908d63a345d3c624d to your computer and use it in GitHub Desktop.
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