← all walkthroughs

Lightweight

Linux· Medium· Credential Access· Privilege Escalation
owned
2026-07-08
time to own
20m6s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I scanned the target ($TARGET) and found three open services: SSH on 22, HTTP on 80, and LDAP on 389. An anonymous bind against the LDAP server — requiring no credentials — returned the full user directory including SHA-512-crypted password hashes stored in plain-sight userPassword attributes. A web page at /user.php automatically provisioned a working SSH account using the visitor's IP address as both username and password; a single HTTP request plus a 60-second wait produced an interactive shell with no brute force needed.

From that foothold, the dumped LDAP hashes were cracked offline, yielding ldapuser2's password, which was used to switch accounts and retrieve an encrypted archive (unlocked with the trivial password '[REDACTED: recovered credential]'). The tcpdump binary held Linux raw-socket capabilities, letting the low-privilege foothold user capture loopback traffic — where periodic LDAP simple-bind requests carried ldapuser1's password in cleartext. Logging in as ldapuser1 revealed that openssl held a cap_dac_read_search capability that bypasses all filesystem read-permission checks, allowing /root/root.txt to be read directly.

Full system compromise was achieved without a single brute-force attempt or CVE exploit.

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 ATTACKER_IP="<your-vpn-address>"
export PASSWORD="<a-password-you-choose>"
export PASSWORD2="<a-password-you-choose>"

Attack path — how the box was taken

1ReconnaissanceNetwork service and version discovery (T1046)
Scanned the target and identified three exposed services
An Nmap service-version scan identified three open TCP ports: 22 (OpenSSH 7.4), 80 (Apache 2.4.6 / PHP 5.4 on CentOS), and 389 (OpenLDAP). The combination of LDAP and SSH on a Linux host is a strong signal of centrally managed accounts, making LDAP the natural first pivot. The HTTP service was also flagged for web-application enumeration.
Exact commands 1
Service-version scan with LDAP root DSE and HTTP path enumeration scripts.
nmap -Pn -sV -p22,80,389 --script ldap-rootdse,http-enum $TARGET
2EnumerationAnonymous LDAP bind and directory enumeration (T1087.002)
Dumped all user accounts and password hashes via anonymous LDAP bind
The OpenLDAP service on port 389 accepted an anonymous (unauthenticated) bind and returned the complete directory tree for dc=lightweight,dc=htb. A subtree search for posixAccount objects exposed two user accounts — ldapuser1 and ldapuser2 — each with a userPassword attribute containing their SHA-512-crypt hash. Storing password material in LDAP attributes readable without any authentication hands my a ready-made offline cracking target.
Ldapsearch returned uid=ldapuser1 and uid=ldapuser2 entries with userPassword: {crypt}$6$... Hashes from dc=lightweight,dc=htb without supplying any bind credentials
Exact commands 2
Confirm the directory naming context without credentials.
ldapsearch -x -H ldap://$TARGET -s base -b '' namingContexts
Dump all user accounts and password hashes anonymously; no -D or -w needed.
ldapsearch -LLL -x -H ldap://$TARGET -b "ou=People,dc=lightweight,dc=htb" "(objectClass=posixAccount)" dn uid userPassword
FixDisable anonymous LDAP binds and restrict access to password-hash attributesCritical
WeaknessThe OpenLDAP server accepted connections without any credentials and returned every user account's password hash in the userPassword attribute, giving any network-reachable an unauthorised user a ready-made offline cracking target with zero authentication required.
FixIn slapd.conf (or cn=config), restrict the userPassword attribute: 'access to attrs=userPassword by self write by anonymous auth by * none' — this allows users to change their own password and allows bind-time password checks, but prevents any read access by anonymous or other authenticated users. Separately, disable anonymous binds entirely with 'disallow bind_anon'. Additionally, consider replacing crypt-format hashes stored in LDAP with a scheme that is not portable to offline cracking (e.g. store only a salted PBKDF2 verifier and enforce LDAPS on port 636). Verify the fix with: ldapsearch -x -H ldap://127.0.0.1 -b 'ou=People,dc=lightweight,dc=htb' — it should return an 'Insufficient access' or 'Anonymous bind disallowed' error.
3Initial AccessUnauthenticated self-service account provisioning with predictable credentials
Obtained a shell via the IP-address self-registration web page
The page at /user.php stated that visiting it automatically creates an LDAP-backed SSH account named after the visitor's source IP address, using that same IP as the password. One HTTP GET request from my VPN tunnel address ($ATTACKER_IP), a 65-second wait for the provisioning job to complete, and then SSHing in with the IP as both username and password produced a valid interactive shell — uid=1004($ATTACKER_IP). No credentials, wordlists, or exploitation of any software vulnerability were required.
Curl http://$TARGET/user.php triggered provisioning; sshpass login succeeded producing uid=1004($ATTACKER_IP) gid=1004($ATTACKER_IP)
Exact commands 3
Trigger LDAP account creation for source IP $ATTACKER_IP.
curl -sS -i http://$TARGET/user.php
Wait for the provisioning job to write the LDAP entry and SSH to pick it up.
sleep 65
Log in; both username and password are my own tunnel IP.
sshpass -p "$ATTACKER_IP" ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no -o StrictHostKeyChecking=no $ATTACKER_IP@$TARGET
FixRemove the IP-address self-registration endpoint (user.php)Critical
WeaknessThe /user.php web page automatically created a fully functional LDAP-backed SSH account for any visitor, using their source IP address as both username and password — a completely predictable credential derivable by any observer — with no authentication, approval, rate-limiting, or account lifetime enforcement.
FixRemove or disable user.php entirely. If self-service account provisioning is genuinely required, replace it with an authenticated flow: require the requestor to prove identity (e.g. corporate SSO), generate a cryptographically random temporary password, deliver it over a separate authenticated channel (email, SMS), enforce a mandatory password change on first login, and automatically expire the account after a defined window. Never derive credentials from observable network metadata such as a source IP address.
4Credential AccessOffline password hash cracking of LDAP userPassword attributes (T1110.002)
Cracked ldapuser2's password hash offline and pivoted to that account
The SHA-512-crypt hashes retrieved in step 2 were saved to a file and fed to John the Ripper. Ldapuser2's hash cracked quickly to [REDACTED: recovered credential] The credential was confirmed valid with ldapwhoami, then used from the foothold shell via su to switch to ldapuser2. In ldapuser2's home directory an encrypted archive backup.7z was found; the password '[REDACTED: recovered credential]' opened it immediately, though its contents were only a copy of user.php — confirming the auto-provisioning behaviour but revealing no additional credentials. The user flag was recovered from ldapuser2's home directory.
Ldapwhoami confirmed dn:uid=ldapuser2,ou=People,dc=lightweight,dc=htb; john cracked hash to [REDACTED: recovered credential] user.txt recovered from /home/ldapuser2/
Exact commands 6
Extract raw hash strings into a file John can read.
ldapsearch -LLL -x -H ldap://$TARGET -b "ou=People,dc=lightweight,dc=htb" "(objectClass=posixAccount)" userPassword | grep userPassword | awk '{print $2}' > hashes.txt
Crack sha512crypt hashes; ldapuser2 resolves to [REDACTED: recovered credential]
john --format=crypt hashes.txt
Validate the cracked credential against the live LDAP server.
ldapwhoami -x -H ldap://$TARGET -D "uid=ldapuser2,ou=People,dc=lightweight,dc=htb" -w "$PASSWORD"
From the foothold SSH session; enter [REDACTED: recovered credential] when prompted.
su - ldapuser2
Recover the user flag — value: <user.txt>
cat /home/ldapuser2/user.txt
Decrypt the archive with password '[REDACTED: recovered credential]'; examine contents for additional secrets.
7z x -p$PASSWORD2 /home/ldapuser2/backup.7z -o/tmp/backup/
5DiscoveryLinux file capability enumeration (T1057)
Found tcpdump carrying raw-socket file capabilities available to all users
Running a system-wide capability audit with getcap revealed that /usr/sbin/tcpdump held cap_net_admin and cap_net_raw+ep. These capabilities let any user who can execute the binary open raw sockets and capture traffic on any interface — including the loopback — with no root or sudo required. On a shared multi-user host this is a serious misconfiguration: it means every low-privilege account is effectively a potential network sniffer.
Exact commands 2
List all file capabilities on the system; look for cap_net_raw, cap_dac_read_search, cap_setuid.
getcap -r / 2>/dev/null
Confirm the binary is executable by the current low-privilege user.
ls -la /usr/sbin/tcpdump && id
FixRemove cap_net_raw and cap_net_admin from the tcpdump binary and enforce LDAPSHigh
WeaknessThe /usr/sbin/tcpdump binary held cap_net_raw and cap_net_admin file capabilities, allowing any user able to execute it — including the auto-provisioned foothold account — to capture all traffic on any interface including the loopback, where unencrypted LDAP simple-bind requests carried other users' passwords in cleartext.
FixRemove the file capabilities immediately: 'setcap -r /usr/sbin/tcpdump'. Restrict execution to root or a dedicated capture group: 'chmod 750 /usr/sbin/tcpdump && chown root:pcap /usr/sbin/tcpdump'. Eliminate the underlying exposure in parallel by migrating LDAP to use LDAPS (port 636) or mandatory STARTTLS, so that even if traffic is captured in the future no plaintext credentials are present. Run a recurring audit: 'getcap -r / 2>/dev/null' should appear in your change-management monitoring.
6Credential AccessNetwork traffic capture of cleartext LDAP simple-bind credentials (T1040)
Captured ldapuser1's cleartext password by sniffing loopback LDAP traffic
Port 389 LDAP uses simple-bind authentication, which transmits the Distinguished Name and password in plaintext on the wire. An automated process on the server periodically authenticates to LDAP as ldapuser1 over the loopback interface. Using the cap_net_raw-privileged tcpdump to record loopback packets for roughly 90 seconds captured that bind request. Passing the capture file through strings immediately revealed the DN and password — no cracking, no decryption.
Tcpdump loopback capture; LDAP bindRequest packet body contained uid=ldapuser1 DN and cleartext password visible via strings
Exact commands 2
Capture all loopback LDAP traffic; run for 90-120 seconds to catch the periodic bind, then Ctrl-C.
/usr/sbin/tcpdump -i lo -A -s 0 'port 389' -w /tmp/ldap_cap.pcap
Extract the plaintext DN and password from the captured bindRequest.
strings /tmp/ldap_cap.pcap | grep -A5 'lightweight\|ldapuser'
7Privilege EscalationLinux cap_dac_read_search arbitrary file read via GTFOBins (T1548)
Read root.txt using openssl's cap_dac_read_search file capability
SSH as ldapuser1 with the sniffed password succeeded. A second capability audit on that account revealed /usr/sbin/openssl held cap_dac_read_search+ep. This capability tells the Linux kernel to skip all discretionary access-control permission checks on file reads, so openssl can open any file — owned by any user, set to any permissions — and read it. Passing /root/root.txt directly to 'openssl enc -in' printed the flag. No shell was elevated, no SUID bit was involved, and no sudo was required.
Getcap output as ldapuser1: /usr/sbin/openssl = cap_dac_read_search+ep; root.txt content recovered via openssl enc -in /root/root.txt
Exact commands 3
Authenticate as ldapuser1 using the password captured from the loopback LDAP sniff.
ssh -o PreferredAuthentications=password -o StrictHostKeyChecking=no ldapuser1@$TARGET
Confirm /usr/sbin/openssl = cap_dac_read_search+ep.
getcap -r / 2>/dev/null
Cap_dac_read_search bypasses the root:root 0400 permissions; output is <root.txt>
openssl enc -in /root/root.txt
FixRemove cap_dac_read_search from the openssl binaryCritical
WeaknessThe /usr/sbin/openssl binary held the cap_dac_read_search file capability, which instructs the Linux kernel to skip all discretionary access-control permission checks on file reads. Any user who could run openssl could therefore read every file on the system — including /root/root.txt and /etc/shadow — regardless of ownership or mode bits.
FixRemove the capability immediately: 'setcap -r /usr/sbin/openssl'. Audit the entire host for unnecessary capabilities: 'getcap -r / 2>/dev/null' — the output on a correctly hardened system should be empty or contain only carefully justified entries. Apply the principle of least privilege: no user-executable binary should hold capabilities that allow reading files outside its normal operating scope. Deploy a file-integrity monitoring tool (e.g. AIDE or Wazuh) configured to alert on any capability change to system binaries.

Exposed services

22/tcp
80/tcp
389/tcp