I’ve been wanting to blog about this vulnerability for some time, but needed to give vendors, ICS-CERT, and the DNP Tech committee sufficient time to address the issue. The tech committee recently published Technical Bulletin TB2014-006, Clarification of the Use of Variation 0 with Object Groups 110-113 which can be downloaded from the DNP.org member portal. Since the portal has a pay-wall, I’ve decided to provide some information publicly. It’s also important to emphasize that this technical bulletin has security implications, primarily for master stations.
What is an octet string?
DNP3 has a measurement type called an Octet String (Groups 110 & 111) that is designed to be a “catch all” type if the thing you want to report from an outstation to a master doesn’t quite fit into one of the predefined boxes provided by the specification. For example, say you want to transmit a 64-bit unsigned integer measurement. DNP3 only has 32-bit integer types, so you could encode your 64-bit type in an Octet string using 8-bytes. The length of the octet string is defined by the variation used to report it. Therefore Group 111 Variation 8 would be an octet string event that’s 8 bytes long. The interesting thing about this method of encoding is that it allows for the possibility of encoding a zero-length string.
What’s wrong with a zero-length octet string?
The problem with this is that a very large number of such objects can be encoded in a comparatively tiny packet. In fact, all it takes is the right header to specify the objects since they are zero-length. If your implementation supports 16-bit addressing (which it must to be conformant) you can cram ~26 Million empty strings into a single 2KB message. If your implementation supports 32-bit addressing closer to 1 trillion empty strings can be encoded. This is analogous to the infamous “zip bomb” attack where a malicious zip file is crafted that decodes to fill your entire hard drive.
Many have claimed that these vulnerabilities are “not a big deal because you can reboot a DNP3 device through the protocol itself”. This is another illustration of why this is an apologist attitude. In theory, the worst you can do to a perfectly implemented DNP3 master is spoof data. There are no controls to execute and no function codes to make it reboot. Spoofing is bad enough, but can you do it for every endpoint if the network is segmented properly? This is not a trivial task.
With a vulnerability like this, however, you can take down the entire master and all the remote sessions with a single packet. At best, this causes a self-resolving CPU-bound denial-of-service as observed in a popular source code library. At worst, it causes gigabytes of logging that runs out the hard disk of the target or it expands into memory and exhausts the programs resources.
NOT Malformed and NOT identified via Fuzzing
This packet is not malformed. While it can be argued that the DNP3 standard says “you can’t use variation zero in-conjunction with responses” this is a subtlety that is easy to overlook and no deep packet inspection (DPI) firewall currently deployed is going to catch this. All of the specified data is actually there.
Unlike the litany of other issues we’ve found via fuzzing, this vulnerability was instead found via protocol analysis and subsequent conjecture. A single such “empty string” header only causes a hiccup that may not be noticed unless you’re doing sophisticated latency monitoring (something being added to Aegis now). Only when you craft a packet that repeats many such headers is the vulnerability easily noticeable.