For the complete documentation index, see llms.txt. This page is also available as Markdown.
LDAPLINUXLINUX-PRIVESCNFS

Walnut

Challenge Lab (Easy) - by TheKeen

The following post by 0xb0b is licensed under CC BY 4.0


Scenario

Objective

You have been assigned a penetration test on a critical Linux server in the client's environment. The primary objective is to gain root-level access to this system to demonstrate maximum impact from the engagement.

Initial Access

The client has provided you with credentials for an "Assumed Breach" scenario.

Summary

Summary

On Walnut, we start with assumed-breach credentials for larryburns that fail against SSH and the readable SMB shares. Further NFS enumeration shows no shares configured at all, but LDAP enumeration succeeds once we bind with the OpenLDAP uid=larryburns,ou=people,dc=walnut,dc=local convention rather than the AD-style cn. Scrolling the directory dump, we recover an old password stored in the automation user's description, which re-grants us access to the previously locked print$ and automation SMB shares. Connecting with smbclient.py, we loot the automation home directory, retrieving user.txt and the account's SSH private RSA key, which we use to SSH in as automation. In the home directory we find a script called scripts/runScript.sh, which runs an arbitrary command as another user via su, feeding that user's password from a file in .hidden/ named after the MD5 hash of the username; since those files are readable, we compute each hash, read the corresponding password, and pivot through the localjob1localjob4 accounts. On localjob3 we find the account can restart the NFS service using sudo, and even though /etc/exports looks read-only, a + in its permission string reveals an ACL that getfacl confirms grants localjob3 write access. We append a no_root_squash export for /root, restart nfs-kernel-server, and mount the share from our host, where retaining root privileges lets us drop our public key into /root/.ssh/authorized_keys and finally SSH in as root.

Recon

We use rustscan -b 500 -a 10.1.167.132 --top -- -sC -sV -Pn to enumerate all TCP ports on the target machine, piping the discovered results into Nmap which runs default NSE scripts -sC, service and version detection -sV, and treats the host as online without ICMP echo -Pn.

A batch size of 500 trades speed for stability, the default 1500 balances both, while much larger sizes increase throughput but risk missed responses and instability.

The target 10.1.167.132 is a Linux host with the NetBIOS name WALNUT. Remote access is available via SSH on port 22. Directory services are exposed through OpenLDAP on port 389 (version 2.2.X–2.3.X). SMB is served by Samba 4.6.2on ports 139 and 445, with message signing enabled but not required. The host also runs an NFS stack: rpcbind/portmapper on port 111 and NFS itself on port 2049 (with nfs_acl), supported by the usual ancillary RPC services mountd on ports 41503, 53911, and 59667, nlockmgr on port 43311, and status/statd on port 48759.

SSH

We attempt to authenticate ourselves using the provided credentials on the exposed services, starting with SSH without success.

NFS

We try to identify shares on the NFS service, but it appears that nothing has been set up.

SMB

However, we can authenticate against the SMB service using the provided credentials and find two interesting shares: print$ and automation. However, these shares are neither writable nor readable with the credentials we have.

LDAP

As a last resort, we'll try to enumerate the LDAP service. Perhaps we'll find additional users there, and possibly sensitive information in their descriptions or other clues. But the attempt using NetExec fails and throws errors.

We're trying to use ldapsearch, but it fails again. We recall, we're working with OpenLDAP.

We try it uid based to return every entry in the dc=walnut,dc=local tree and are successful.

The uid=larryburns,ou=people,dc=walnut,dc=local bind worked because OpenLDAP stores users with uid as the RDN attribute (under ou=people), not cn. That's the standard POSIX/OpenLDAP convention, unlike Active Directory which uses cn.

Access as automation

We scroll through the output of our ldapsearch request and find an old password in the description of the automation user.

Shell as automation

We are not able to log in using SSH with the credentials, but we are able to enumerate the shares again and have now permissions on the shares we found earlier.

We connect to the shares using smbclient.py and take a closer look.

The automation share appears to be located in the automation users home directory. We download everything, including the first flag we find in the directory. We also find the private RSA key, which we can use to log in to the machine via SSH.

We adjust the permission of the private key and use it to log in as automation via SSH.

Shell as localjobX

In the home directory we inspect the scripts folder we already exifiltrated before. This contains a runScript.sh script that runs an arbitrary command ($2) as another user ($1) via su.

he standard input for that su session is redirected from a hidden file in /home/automation/.hidden/, and the command's output is written to a timestamped log in /home/automation/scripts/logs/.

The filename is the MD5 hash of the username. So each user has a matching file in .hidden/, named after the hash of their name, and that file holds their password piping it into su

In addition to the automation user, there are four other users: localjob1, localjob2, localjob3, and localjob4.

We look at the contents of the .hidden folder and find four files that could correspond to the users. One of them is empty, but there is a backup file for it.

We use the line from the script to identify the file associated with a user, which allows us to determine which password belongs to whom.

As an example, we retrieve the password for localjob1 and switch sessions using su with the password from the file and are successful. However, at first we don't notice anything out of the ordinary.

Shell as root

We're working our way through the users, and with the localjob3 user, whose original file was empty but whose backup doesn't reveal anything new. We find that this user can restart the NFS service using sudo.

We could take advantage of this if we could also edit the /etc/exports file. Then we could misconfigure NFS and restart it to apply the misconfiguration. So we try to disable no_root_squash this would allow us to access the NFS share as root from a remote client without our privileges being squashed to nobody, meaning we could read or write on the target system as root.

First we check the permission on /etc/exports. On the first glance it looks like we can only read that file. But the + at the end of the permission string (-rw-rw-r--+) means the file has an ACL - Access Control List set on it beyond the standard Unix owner/group/other permissions.

We can check them as follows and see that the localjob3 user has write access to it.

So we misconfigure the service to disable no_root_squash by adding the following line to /etc/exports mounting the root users home directory.

When root squashing is disabled (no_root_squash), the root user on the client maintains root privileges on the NFS share, allowing privilege escalation.

Next, we restart the service...

... and enumerate the nfs share again. We are able to enumerate the root folder now.

And we are also able to retrieve the root.txt using NetExec.

To get a remote session we could generate a new RSA key pair and replace the /root/.ssh/authorized_keys with the contents of the generated public key. This would allow us to gain a root shell through SSH using that generated private key. Alternatively we could also upload a SUID bash binary.

Unfortunately the upload fails using NetExec with the version used.

But the NFS share could be mounted manually and the file be replaced. This works.

With our public key now in place, we can SSH into the target as root using the generated private key.

Last updated