Team82 and researchers from JFrog collaborated to research the security of BusyBox, a suite of Linux utilities used in IT use cases, as well as OT and IoT
Many popular PLCs, HMIs, and RTUs run on Linux and have BusyBox utilities running under the hood.
Our combined research uncovered 14 vulnerabilities affecting current versions of BusyBox, which was updated in version 1.34.0
Most of the vulnerabilities could allow an attacker to disrupt services, causing a denial of service condition.
In some cases, information leaks or remote code execution is possible.
As part of this research, Team82 is making freely available the custom AFL fuzzing harnesses used in this research.
Embedded devices with limited memory and storage resources are likely to leverage a tool such as BusyBox, which is marketed as the Swiss Army Knife of embedded Linux. BusyBox is a software suite of many useful Unix utilities, known as applets, that are packaged as a single executable file. Within BusyBox you can find a full fledged shell, a DHCP client/server, and small utilities such as cp, ls, grep, and others. You're also likely to find many OT and IoT devices running BusyBox, including popular programmable logic controllers (PLCs), human-machine interfaces (HMIs), and remote terminal units (RTUs)—many of which now run on Linux.
As part of our commitment to improving open-source software security, Claroty's Team82 and JFrog collaborated on a vulnerability research project examining BusyBox. Using static and dynamic techniques, Claroty's Team82 and JFrog discovered 14 vulnerabilities affecting the latest version of BusyBox. All vulnerabilities were privately disclosed and fixed by BusyBox in version 1.34.0, which was released Aug. 19.
In most cases, the expected impact of these issues is denial of service (DoS). However, in rarer cases, these issues can also lead to information leaks and possibly remote code execution.
In this report, we will provide details about the vulnerabilities, elaborate on who is affected, discuss our research methodology, explain one of the vulnerabilities in depth, and suggest fixes and workarounds for these issues.
In addition to disclosing the vulnerabilities, Team82 is also open-sourcing its custom AFL fuzzing harnesses, which were responsible for triggering many of the mentioned vulnerabilities. Hopefully this will help fellow researchers find and disclose even more issues.
Since the affected applets are not daemons, each vulnerability can only be exploited if the vulnerable applet is fed with untrusted data (usually through a command-line argument). Specifically, these are the conditions that must occur for each vulnerability to be triggered:
Applies if the attacker can control all parameters passed to man. man is built by the default BusyBox configuration, but not shipped with Ubuntu's default BusyBox binary.
Applies if the attacker can supply a crafted compressed file, that will be decompressed by using unlzma. Note that even if the unlzma applet is not available, but CONFIG_FEATURE_SEAMLESS_LZMA (enabled by default) is enabled, other applets such as tar, unzip, rpm, dpkg,lzma and man can also reach the vulnerable code when handling a file with the .lzma filename suffix. unlzma is built by the default BusyBox configuration and shipped with Ubuntu's default BusyBox binary.
Applies if the attacker can supply a command line to ash that contains the special characters $, {, }, or # . ash is built by the default BusyBox configuration and shipped with Ubuntu's default BusyBox binary.
Applies if the attacker can supply a command line to hush that contains the special character \x03 (delimiter). hush is built by the default BusyBox configuration, but not shipped with Ubuntu's default BusyBox binary.
Applies if the attacker can supply a command line to hush that contains the special character &.
Applies if the attacker can supply an arbitrary pattern to awk (the pattern is the first positional argument this applet takes). awk is built by the default BusyBox configuration and shipped with Ubuntu's default BusyBox binary.
To research BusyBox, we used static and dynamic analysis approaches.
First, a manual review of the BusyBox source code was conducted in a top-down approach (following user input up to specific applet handling). We also looked for obvious logical/memory corruption vulnerabilities.
The next approach was fuzzing. We compiled BusyBox with ASan and implemented an AFL harness for each BusyBox applet. Each harness was subsequently optimized by removing unnecessary parts of the code, running multiple fuzzing cycles on the same process (persistent mode), and running multiple fuzzed instances in parallel.
We started from fuzzing all the daemon applets, including HTTP, Telnet, DNS, DHCP, NTP etc. Many code changes were required in order to effectively fuzz network-based input. For example, the main modification we performed was to replace all recv functions with input from STDIN in order to support fuzzed inputs. Similar changes were done when we fuzzed non-server applets as well.
We prepared a couple of examples for each applet and ran hundreds of fuzzed BusyBox instances for a few days. This gave us tens of thousands of crashes to evaluate. We had to create classes of crashes with the same root cause to help reduce the volume of crashes we had in our sample set. Later, we minimized each group representative in order to work with a small subset of unique crash inputs.
To fulfill these tasks, we developed automatic tooling that digested all crash data and classified it based on the crash analysis report which mainly includes the crash stack trace, registers, and assembly code of the relevant code area. For example, we merged cases with similar crash stack traces because they usually had the same problematic root cause.
Finally, we researched each unique crash and minimized its input vector in order to understand the root cause, which allowed us to create a proof-of-concept (PoC) that exploits the vulnerability responsible for the crash. In addition, we tested our PoCs against several BusyBox versions to understand when the bugs were introduced to the source code.
To summarize, here are the steps we took in our research:
Code Review
Fuzzing
Reduction and Minimization
Triage
Proof of Concept
Testing Multiple Versions
Disclosure
As part of our commitment to the OSS and security communities, we created a simple on-boarding guide detailing how to fuzz BusyBox. The guide is published alongside all of the fuzzing harnesses we wrote as part of our fuzzing efforts. We hope these fuzzing harnesses can be further improved by the community, in order to find and fix even more bugs in BusyBox.
All materials are available on Claroty's GitHub page.
To assess the threat level posed by these vulnerabilities, we inspected JFrog's database of more than 10,000 embedded firmware images (comprised of only publicly available firmware, and not images uploaded to JFrog's Artifactory). We found that 40% of them contained a BusyBox executable file that is linked with one of the affected applets, making these issues extremely widespread among Linux-based embedded firmware.
However, we believe these issues do not currently pose a critical security threat because:
While the DoS vulnerabilities are trivial to exploit, the impact is usually mitigated by the fact that applets almost always run as a separate forked process.
The information leak vulnerability is nontrivial to exploit (see, next section).
The use-after-free vulnerabilities may be exploitable for remote code execution, but currently we did not attempt to create a weaponized exploit for them. In addition, it is quite rare (and inherently unsafe) to process an awk pattern from external input.
LZMA is a compression algorithm that uses dictionary compression, and encodes its output using a range encoder. The dictionary compressor finds matches using sophisticated dictionary data structures, and produces a stream of literal symbols and phrase references, which are encoded one bit at a time by the range encoder, using a complex model to make a probability prediction of each bit.
The compression algorithm encodes the compressed stream as a stream of bits using an adaptive binary range coder. Data is broken into packets, where each packet describes either a single byte, or an LZ77 sequence, with its length and distance implicitly or explicitly encoded.
The .lzma format, below, consists of a 13-byte header followed by the LZMA compressed data. Here is a small example of compressing the string "abc" using LZMA:
In order to output the decompressed stream, LZMA implementations use a memory buffer that is initialized in the size of the user-provided dictionary size (part of the LZMA header). Once that buffer is filled, it automatically outputs the data thus far, flushes the buffer and starts filling it up again.
The vulnerability is caused by an insufficient size check, below, in the function decompress_unlzma.c (at line 295) when the state >= LZMA_NUM_LIT_STATES:
To trigger the vulnerability and to control the starting offset where we will leak data from, we need to make sure that the following conditions are satisfied:
buffer_pos = 0 and rep0 = offset + dict_size
This way, pos will be equal to -(offset + dict_size). After adding dict_size, pos will be -offset and so we could leak memory from our desired offset through match_byte. The leaked memory will most likely contain pointers which could further assist attackers in their exploitation campaign (ex. by facilitating ASLR bypass).
The general idea to exploit this vulnerability is to prepare a specifically crafted LZMA encoded stream, so that when it is decoded, both conditions will be filled and pos will be equal to a negative number -offset. Eventually, the decompressed stream will contain the leaked memory, which will be written to the output steam.
To satisfy the first condition buffer_pos = 0, we need to make sure our code flow (state >= LZMA_NUM_LIT_STATES) is reached right after the current decompressed buffer stream is flushed and so the buffer pointer position will be 0. We can achieve this by reaching the last iteration, below, of a current match:
The second condition is more difficult to satisfy and requires intimate knowledge of how the LZMA algorithm works. The general idea is to encode a special length in the LZMA bit stream so that when decoded it will be used by the rep0 variable.
To conclude, in order to reach an OOB condition, we need to write some bytes, then use a match to fill the buffer to header.dict_size and change rep0 to our desired value. Therefore, pos will be equal -offset and we could leak bytes from offset as a reference to the buffer pointer.
After reading the match_byte, we will get to this flow, below:
As long as the bits match our match_byte (the leaked byte), it will be in the loop that reads the probability from prob + 0x100 + bit + mi, but once one bit is not matched, it reads it from prob + mi. We can detect what was the first unmatched bit, by checking if prob + mi was changed by writing more literal bytes, or if the probability was changed and we got a different output. Finally, the leaked bits will get flushed to the decompressed buffer.
Although the vulnerability was found in the LZMA decompression algorithm, we found that many applets support an LZMA compression and will try to decompress encoded LZMA streams by default (see section, "Fixes and Workarounds" for the configuration flags governing this behavior). For example, the ubiquitous ZIP format supports LZMA compression as a "type 14" compression.
From an attacker's perspective, ZIP is a much better attack vector because:
unzip invocations are much more common than direct invocations of unlzma.
With this attack vector, there are no constraints on the filename that's going to be unzipped (unlike in the tar attack vector, which requires a .lzma suffix).
The leaked data can be extracted and saved into files that can be later read remotely. For example, this can happen in an embedded web service that permits uploading zip files with media resources, which will get extracted to an accessible location. From there, the attacker could read the leaked memory data.
To test this, we built a small PoC script that generates a weaponized ZIP where one of the files is compressed using LZMA, below:
All 14 vulnerabilities have been fixed in BusyBox 1.34.0 (direct download link) and users are urged to upgrade.
If upgrading BusyBox is not possible (due to specific version compatibility needs), BusyBox 1.33.1 and earlier versions can be compiled without the vulnerable functionality (applets) as a workaround.
After running make deconfig in BusyBox's source directory (or if reusing a previous configuration), edit the .config file such as:
man - Comment out CONFIG_MAN=y
lzma - Comment out CONFIG_UNLZMA=y , CONFIG_FEATURE_SEAMLESS_LZMA=y and CONFIG_FEATURE_UNZIP_LZMA=y
ash - Comment out CONFIG_ASH=y
hush - Comment out CONFIG_HUSH=y
awk - Comment out CONFIG_AWK=y
We would like to thank Denys Vlasenko from BusyBox's development team for validating and fixing all of the above issues in a swift manner for version 1.34.0.
A NULL pointer dereference in man leads to denial of service when a section name is supplied but no page argument is given
Read more: "Unboxing Busybox: 14 Vulnerabilities Uncovered by Claroty, JFrog"
CVSS v3: 5.1
An out-of-bounds heap read in unlzma leads to information leak and denial of service when crafted LZMA-compressed input is decompressed. This can be triggered by any applet/format that internally supports LZMA compression.
Read more: "Unboxing Busybox: 14 Vulnerabilities Uncovered by Claroty, JFrog"
CVSS v3: 6.5
An incorrect handling of a special element in ash leads to denial of service when processing a crafted shell command, due to the shell mistaking specific characters for reserved characters. This may be used for DoS under rare conditions of filtered command input.
Read more: "Unboxing Busybox: 14 Vulnerabilities Uncovered by Claroty, JFrog"
CVSS v3: 4.1
A NULL pointer dereference in hush leads to denial of service when processing a crafted shell command, due to missing validation after a \x03 delimiter character. This may be used for DoS under very rare conditions of filtered command input.Read more: "Unboxing Busybox: 14 Vulnerabilities Uncovered by Claroty, JFrog"
CVSS v3: 4.1
An attacker-controlled pointer free in hush leads to denial of service and possible code execution when processing a crafted shell command, due to the shell mishandling the &&& string. This may be used for remote code execution under rare conditions of filtered command input.
Read more: "Unboxing Busybox: 14 Vulnerabilities Uncovered by Claroty, JFrog"
CVSS v3: 6.4
A use-after-free in awk leads to denial of service and possibly code execution when processing a crafted awk pattern in the getvar_i function
Read more: "Unboxing Busybox: 14 Vulnerabilities Uncovered by Claroty, JFrog"
CVSS v3: 6.6
A use-after-free in awk leads to denial of service and possibly code execution when processing a crafted awk pattern in the nvalloc function
Read more: "Unboxing Busybox: 14 Vulnerabilities Uncovered by Claroty, JFrog"
CVSS v3: 6.6