Today | News | Books | Recipes Data-Only Attacks Are Easier than You Think | USENIX Data-Only Attacks Are Easier than You Think | USENIX Our Board of Directors Board Meeting Minutes Updates & Announcements Governance & Financials Lifetime Achievement Award EventsEventsUpcoming Join & SupportJoin & SupportBecome a Member Student Opportunities Sponsorship Opportunities ArchiveArchiveProceedings Short Topics in System Administration Series Journal of Education in System Administration (JESA) Journal of Election Technology and Systems (JETS) Computing Systems Journal Back to ;login: Online Brian Johannesmeyer, Herbert Bos, Cristiano Giuffrida, Asia Slowinska Article shepherded by: Suppose you are a hacker and you just found a bug that allows you to overwrite data in a victim program. Such a scenario is not uncommon: Microsoft, Google, and Mozilla report that about 70% of their security bugs are indeed such memory safety bugs [1, 2, 3]. The question then becomes, as a hacker, how do you weaponize this bug into a real exploit? In the past, it would have been relatively straightforward: you could, for example, use the bug to conduct a control-flow hijacking attack, overwriting code pointers in the program [4], forcing it to execute your own malicious code. However, due to decades of research (resulting in defenses such as DEP, CFI, CPI, etc.), it is now very difficult to divert a program's control flow away from the code that it intends to execute. Hence, weaponizing the bug in such a way is now often infeasible in practice. In our recently published paper at USENIX Security 2024 [5], we present a practical approach to an entirely different method of exploitation: letting the program execute all of its intended code (e.g., any benign functions, system calls, etc.), but with malicious data. These so-called data-only attacks have been known for quite some time [6], but were assumed to be too application-specific or complex to pose any practical threat [7]. In our work, we show that such assumptions are not justified. In particular, we implemented a scalable and automated solution, Einstein, that demonstrates that building data-only attacks is easy - well within reach of low-effort attackers. In this article, we will discuss the insights that allow Einstein to automatically generate such exploits with surprising ease, and the implications of our findings on software vendors. Let us first walk through one of the classic data-only attacks described in the literature, which exploits a victim web server [6] (simplified for clarity). At start up, the server reads its configuration file to initialize its data. One such configuration option is the CGI-BIN path, which is the directory it uses to execute external programs. In our example, the server sets its cgi_bin_path variable to "/usr/local/server/cgi-bin". We assume that the server has a program in its CGI-BIN directory, sort_script, that a client can use to sort numbers. Moreover, the server has a memory safety bug that allows a malicious client to overflow some buffer and overwrite, for instance, the contents of the cgi_bin_path variable to "/bin": Figure 1: An example memory safety bug that allows the attacker to modify the CGI-BIN path. Figure 2: An example data-only attack that corrupts a server's CGI-BIN path to execute arbitrary code. ➊ The client sends a "POST sort-script" request with the unsorted numbers "2 1 3" in the request body. ➋ The server concatenates the CGI-BIN path and the request's path to determine the program to be executed, "/usr/local/server/cgi-bin/sort-script". ➌ The server executes the program, i.e., the sort script, and passes in "2 1 3" as its input. It does so by invoking the execve system call, which instructs the operating system to run the script on behalf of the server. ➍ The script sorts the numbers and outputs "1 2 3", which the server forwards to the client in its HTTP response. Let us now sketch how a malicious client could exploit this (Fig. 2c): ➎ The client exploits the bug to set the cgi_bin_path to the string "/bin" (Fig. 1). ➏ The client sends a "POST /sh" request with "touch /tmp/attacker-was-here" in the request body. ➐ The server concatenates the CGI-BIN path and the request's path to determine the program to be executed, "/bin/sh". ➑ The server executes the program, i.e., the system shell, and passes in "touch /tmp/attacker-was-here" as its input. It does so by invoking the execve system call, which instructs the operating system to run the shell on behalf of the server. ➒ The shell creates the file /tmp/attacker-was-here. First, the victim server does not execute any malicious code provided by the client; all harmful actions are triggered by malicious data. The attack effectively modifies only the arguments of the execve syscall. Other than that, the benign and malicious executions are equivalent - when handling a request, the victim server performs the same steps, and executes the same functions, albeit with different arguments. Second, this attack is very powerful, as it allows the attacker to execute arbitrary programs on the victim machine. In our example, the client only creates the file /tmp/attacker-was-here, but any shell command is possible, e.g., to install a malicious program or to exfiltrate data. Despite the discovery of data-only attacks almost two decades ago, conventional wisdom says they rarely pose a practical threat, either because they are too application-specific or too complex. Application-specific. As pointed out by the authors of the example attack, building such an attack "require[s] sophisticated knowledge about program semantics". In other words, an attacker has to become so familiar with the server's inner-workings - either through reverse-engineering its code, or studying its protocols, etc. - that they know that out of all the program's data, the cgi_bin_path variable specifically is security-critical, and that a POST request that is malformed in a very specific way can exploit it. In all likelihood, this kind of labor-intensive, application-specific analysis is prohibitively expensive, and hence, according to conventional wisdom, such data-only attacks are too niche to pose a practical threat. Complex. Recent approaches to building data-only attacks foray into complex territory, under the assumption that simpler attacks - such as the example attack - are not generally at reach. In particular, they assume the need either to solve complex data-flow constraints using heavyweight analyses, or to deviate the victim program away from the code it intends to execute to circumvent a variety of defenses. Several approaches even go so far as to construct highly complicated, Turing-complete machines - something real-world attackers rarely need. Exploitation requires neither extensive knowledge of the program semantics, nor the solving of complex data-flow constraints, nor the diversion of the control flow in a complicated (or even any) way. Inspired by the quote attributed to Albert Einstein, we present a simple (but not too simple) data-only attack exploitation pipeline, named Einstein, that builds attacks with surprising ease. It generates data-only attacks using an application-agnostic technique, proving that such attacks are well within reach of low-effort attackers. Application-agnostic. Rather than attempting to understand application-specific semantics (e.g., the corner cases of the HTTP protocol), Einstein targets a universal interface used by any program to communicate with the operating system kernel: its syscalls. In particular, we track the data that ends up in syscall arguments, determining whether an attacker can corrupt them to e.g., execute arbitrary code via execve or modify files in the filesystem via write. Simple. Moreover, Einstein abstracts away unnecessary complexities and, instead, targets the exploits that are not only the simplest to identify, but also the most promising for an attacker. In particular, Einstein automatically generates exploits for the security-sensitive syscalls along a program's (already valid) runtime path, and whose arguments are (simply) copied verbatim from attacker-controllable data. As detailed later, this simple approach can automatically generate a surprisingly large number of practical data-only exploits in popular real-world programs. How Einstein builds the example attack To explain how Einstein works, we walk through each step of how it builds the example attack and how it crafts the arguments of a security sensitive system call. We assume that the attacker has access to a program that is equivalent to the one deployed by their prospective victim, so they can run the server locally for analysis. Einstein takes the victim program as input, and operates in two stages: first, it generates candidate exploits; and second, it confirms whether each candidate exploit is indeed a working exploit. For an explanation of the finer points of the design beyond the scope of this example - e.g., how Einstein tracks unbounded data, chains together multiple syscalls, etc. - please refer to our paper [5]. Candidate exploit generation. To generate candidate exploits, Einstein tracks all attacker-corruptible data at runtime, determining which can influence the arguments of security-sensitive syscalls. To facilitate this, we first start the server with Einstein's binary-level instrumentation (Fig. 3a, ➊). The instrumentation adds support for dynamic taint analysis, which allows us to track any "tainted" program data at runtime [8]. The server starts up, initializes its cgi_bin_path, and starts waiting for requests. Einstein models an attacker exploiting the memory safety bug by uniquely tainting any data that it could potentially corrupt, e.g., the string "/usr/local/server/cgi-bin", but also all other data within reach of it. Additionally, we record the tainted data in a memory snapshot (➋). Next, Einstein continues executing the program and tracks how the tainted data propagates throughout the program's execution as the server handles a workload consisting of benign requests (➌). For instance, it sends the "POST /sort-script" request from Fig. 2b. Then, while handling the request, the server passes the tainted string as an argument to the execve syscall. Einstein identifies this flow of attacker-controllable data into a security-sensitive syscall, and records information about it, such as the arguments and their taintedness (➍). Then, Einstein determines that execve's pathname and argv parameters are not only tainted with an identifier that corresponds to cgi_bin_path, but they are in fact identical to cgi_bin_path. We refer to this kind of (very) straightforward data flow as an identity data flow. Einstein builds a candidate exploit for the identity data flow by generating (address, value) pairs that specify that the memory write bug could exploit the execve by overwriting the cgi_bin_path from "/usr/local/server/cgi-bin" to "/bin" (➎). Figure 3a: First, Einstein generates a candidate exploit. Links
Browse another page: |