Moebius

A place where you start at some point, and you have to go back to it in the end.

Recon

We start with an nmap scan:

There are two ports open:

  • 22 (SSH)

  • 80 (HTTP)

When we visit http://moebius.thm/, we're greeted with a cat-themed website that features links to /album.php. Each link includes a short_tag parameter, which changes based on the selected album—set to values like cute, smart, or fav.

We found a QR image, but it's just a Rick Roll with a velociraptor.

Inspecting http://moebius.thm/album.php, we observe that the images from the selected album are loaded through requests to the /image.php endpoint, which takes hash and path parameters to retrieve and display the images.

Lastly, checking http://moebius.thm/image.php with the variables set by album.php, we simply see the image being displayed.

It appears that image.php includes the file specified by the path parameter. However, the hash parameter is likely used as a form of validation—probably generated from the path—to prevent arbitrary file inclusion. Any attempt to modify either the path or hash results in an "Image not found" error, indicating that both values must match in a specific way.

Initial Access

At this point, attempting to guess how the hash is generated for a given path doesn't seem practical. There are countless possible hashing methods, and it's highly likely that the calculation involves a secret key or salt that's not exposed to us, making brute-forcing or reverse-engineering the hash function infeasible.

Instead, revisiting album.php and testing the short_tag parameter with a payload like smart', we observe a database error—indicating that the parameter is vulnerable to SQL injection.

When attempting a basic SQL injection payload like:

by visiting:

we're met with an error: "Hacking attempt."

This suggests that the application includes some input validation or filtering mechanisms aimed at detecting and blocking suspicious patterns or characters commonly used in SQL injection attacks.

We can use ffuf to fuzz for special characters and identify which ones are being filtered. Through this, we discover that both the ; and / characters are blocked by the application.

Fortunately, this doesn't pose a significant obstacle:

  • The ; character, commonly used to terminate SQL statements, is not strictly necessary in most injection payloads.

  • The / character, typically used in file paths, isn’t relevant for basic SQL injection.

So, we can safely work around these restrictions by crafting payloads that avoid these characters.

SQL Injection

Rather than manually enumerating the database, we can streamline the process using sqlmap. Running sqlmap against the vulnerable short_tag parameter quickly reveals two databases:

  • information_schema (the default system metadata database)

  • web (likely containing the application's data)

This allows us to focus our efforts on extracting relevant information from the web database.

Dumping the contents of the web database reveals two tables: images and albums. However, both appear to contain only standard data related to the site’s image and album functionality, with nothing immediately useful or sensitive for exploitation.

Since we also have access to the information_schema database, we can use sqlmap with the --sql-query or --sql-shell option to craft custom SQL queries and better understand the context of the injection. Specifically, by using the --sql-shell or the --statement flag, we can attempt to retrieve the actual SQL query being executed behind the scenes. This helps us determine how our input is incorporated into the query, making it easier to craft effective payloads or bypass filters.

Looking at the SQL statement SELECT id FROM albums WHERE short_tag = 'smart', we can see that the application is querying the albums table to retrieve the id associated with the album the user selects via the short_tag parameter. This confirms that our input is directly embedded in the WHERE clause, and thus, by manipulating short_tag, we can potentially extract or inject further data through this vulnerable query.

Nested SQL Injection

First, we know that the query we're injecting into — SELECT id FROM albums WHERE short_tag = '<short_tag>' — is used to retrieve the album ID from the albums table. However, by examining the output of album.php when provided with a valid short_tag, we can see that the page also displays image paths. This indicates that the application is not only querying the albums table but also fetching data from the images table, where the image paths are likely stored and linked to their corresponding album IDs.

So, it's very likely that after retrieving the album_id using the query:

the application then uses that result in a second query, something like:

If the album_id returned from the first query is not properly sanitized before being used in the second query, this could open up another SQL injection point—this time in the second query.

Secondly, upon examining the database, we don't see any stored hashes for the images. This suggests that the application likely calculates these hashes dynamically in album.php after fetching the image paths. If that's the case, then by injecting a custom path into the second query, we could potentially trick the application into computing the hash for a file path of our choosing. That would allow us to craft a valid request to image.php and include arbitrary files.

We can test this hypothesis by attempting to control the output of the initial query. For example, using the payload:

in the short_tag parameter like this:

we observe that we can influence the album_id used in the subsequent query. This confirms that we likely have a second point of SQL injection and may be able to leverage it to include arbitrary files through the image.php endpoint.

Now, instead of returning a static id, we can inject a payload like:

This causes the first query to return a string that evaluates as a conditional statement rather than a simple integer. So the full query becomes:

If our theory is correct, this returned string—0 OR 1=1-- -—is passed directly into the second query, resulting in something like:

This would cause the application to ignore the actual album_id filter and instead retrieve all images from the images table.

Testing this behavior confirms our assumption: all images are indeed displayed, validating both the existence of a second SQL injection point and the flow of unsanitized data between queries.

Next, we test whether we can control the path returned by the second query using a UNION-based SQL injection.

Using the payload:

we craft a query that injects into the short_tag parameter and forces the application to return a custom result set. This payload results in a query like:

Assuming the second query looks like:

this effectively becomes:

We observe that the third column (3 in our payload) is reflected as the path on the page. This confirms that the third column in the result set corresponds to the image path, and we now have control over it. With this, we can begin crafting payloads to include arbitrary files via /image.php by controlling both the path and triggering the application to generate the correct hash for it.

Next, we attempt to set the path to /etc/passwd in order to force album.php to calculate the corresponding hash and subsequently include the file via /image.php.

We use the following payload:

However, this results in the "Hacking attempt" error once again. This confirms that the / character is filtered by the application, preventing direct inclusion of file paths that contain slashes.

As a result, we'll need to explore alternative bypass techniques—such as encoding the path, using directory traversal tricks, or leveraging symbolic links—if we want to work around this restriction.

However, this isn't actually an issue, as we can easily bypass the filter by hex-encoding the /etc/passwd path. Using the following payload:

we successfully avoid the filter. The application processes the request, and we receive the calculated hash for /etc/passwd as:

This confirms that we can include arbitrary files by providing their hex-encoded paths.

Now, by visiting:

we confirm that the /etc/passwd file is successfully included and its contents are displayed, proving that the file inclusion vulnerability is working as intended.

At this stage, since we have the ability to include arbitrary files, one possible approach would be to attempt log poisoning in order to escalate the LFI (Local File Inclusion) to RCE (Remote Code Execution). However, after some investigation, we are unable to locate a suitable log file to poison.

Instead, we pivot to using a PHP wrapper, specifically:

This allows us to read and enumerate source code from application files in base64 format.

As a first step, we target album.php. We convert the string:

into its hexadecimal representation:

And then craft the following payload to use in the path parameter:

This payload allows us to base64-encode and retrieve the contents of album.php via the LFI vulnerability.

Using the calculated hash, we can successfully read the source code of album.php like this:

Examining the source code of album.php, we find that the application generates hashes using HMAC-SHA256:

However, the SECRET_KEY is not defined within album.php itself—since it includes dbconfig.php, it’s likely that the key is set there.

To access dbconfig.php, we apply the same technique: hex-encode the file path and craft a new payload:

This enables us to retrieve the hash for php://filter/convert.base64-encode/resource=dbconfig.php.

Reading the content of dbconfig.php gives:

Now that we’ve obtained the SECRET_KEY, we can generate valid HMAC-SHA256 hashes for any desired path. Here’s a straightforward Python script to automate this process:

Using this script, we can quickly generate the hash for any target file. For instance, to view the source code of image.php:

And the image.php source code confirms that, once the hash is verified as valid, the file at the specified path is directly included without further validation:

To escalate the LFI vulnerability to Remote Code Execution (RCE), another effective approach—besides log poisoning—is using PHP filter chains. This technique leverages PHP's stream wrappers to construct a chain of filters that ultimately decodes into valid PHP code, effectively allowing us to include and execute arbitrary payloads.

We can generate a suitable filter chain for this purpose using the php_filter_chain_generator tool by Synacktiv. This tool constructs complex filter chains that, when included via the LFI vulnerability, result in executable PHP code being evaluated on the server.

To streamline the exploitation process, we can create a simple Python script that automates the generation of a valid HMAC hash and crafts a request to execute arbitrary PHP code on the target via the LFI vulnerability.

However, attempting to use the system() function to gain remote code execution results in an error:

Reviewing the list of disabled PHP functions explains the issue — system and many other critical functions are disabled:

It appears that all major functions typically used for command execution are disabled. However, exploring bypass techniques reveals an interesting approach that leverages the putenv and mail functions.

This method involves using putenv to set the LD_PRELOAD environment variable, which allows a specified shared library to be loaded whenever a program is executed. By then invoking the mail function, the sendmail program is triggered, causing the shared library defined in LD_PRELOAD to be loaded and executed. This technique may provide a way to bypass the disabled functions and achieve command execution.

First, we create a shared library source file (shell.c) containing code to execute a reverse shell command.

We compile it and send it via a simple HTTP server.

Now, using the PHP code execution to download the library onto the target:

We can see the library being downloaded from our server:

We now set the LD_PRELOAD environment variable to point to the shared library we uploaded using the putenv function. Then, by invoking the mail function, we trigger the sendmail program to run, which in turn causes our library to be loaded and executed.

As a result, our reverse shell payload is triggered successfully, granting us a shell as the www-data user within the container.

Privilege Escalation

Inspecting the sudo privileges for the www-data user inside the container shows that it has full root access.

Escalating to root inside the container:

Next, we inspect the effective capabilities of the container:

Decoding this value confirms the container holds many capabilities:

With these privileges, there are several ways to escape the container. One of the easiest approaches is to mount the host’s root filesystem directly, given that we have access to the host’s block devices.

To leverage this filesystem access for a shell, we can add our SSH public key to the host’s /root/.ssh/authorized_keys file. First, we generate an SSH key pair:

Adding the public key to /mnt/root/.ssh/authorized_keys (which corresponds to /root/.ssh/authorized_keys on the host):

We can now use the private key to SSH into the host as the root user, giving us a shell and allowing us to read the user flag located at /root/user.txt.

From the dbconfig.php file, we already knew that the database was hosted on a separate machine named db. Examining the docker-compose.yml file located at /root/challenge/docker-compose.yml, we can confirm that this is indeed another container:

By checking the /root/challenge/db/db.env file, we can obtain the root password for the MySQL server:

By listing the running containers, we can identify which one is hosting the database:

We can get a shell inside the database container as follows:

Using the password found in the db.env file, we connect to the database and list the available databases. Besides the web database we already accessed, we discover another database named secret.

We can now get the root flag.

Last updated

Was this helpful?