Inter-Process Communication (IPC) is a critical aspect of modern computing, enabling different processes within an operating system to exchange data and coordinate actions. Through various methods such as message passing, shared memory, and sockets, IPC facilitates seamless interaction between distinct applications, enhancing system efficiency and functionality. However, ensuring the security of these communications is paramount, as vulnerabilities in authenticated IPC clients can lead to severe breaches and exploitation.
The story of how we ended up on this blog
So, our team was assessing an application that consisted of two binaries: one with normal user privileges and the other with root privileges.
The application has a set of credentials that it verifies on the server side. It then sends an IPC message from the binary with normal user privileges to the binary with root privileges to execute some privileged actions.
The ultimate objective was to perform these privileged actions without providing any credentials to the application.
Our initial plan was to attack the IPC component itself. So, we began the reconnaissance process to understand the IPC mechanism and how the low-privilege application sends messages to the root-privilege application.
Before reversing the application, we speculated that the IPC component might involve Shared Memory, Message Queues, Sockets, Pipes, or Signals. If the implementation used any of these methods, we already had an idea of how to attack it.
After a brief period of reversing and examining the decompiled code, we discovered that the application was using D-Bus for sending and receiving IPC messages.
The application utilizes GLib APIs to interact with D-Bus for IPC communication.
D-Bus (Desktop Bus) is an inter-process communication (IPC) system widely utilized in Unix-like operating systems. It enables multiple software applications to communicate with each other in a standardized manner, facilitating coordination and data sharing. D-Bus supports both system-wide and user-session communication, making it integral to modern desktop environments and system services.
So, we spent some time learning about D-Bus by visiting its documentation page and came across this cool blog on hacktricks on how to enumerate and perform privilege escalation on D-Bus.
After spending a considerable amount of time learning about D-Bus and performing enumeration as described in the blog, we finally concluded that attacking D-Bus is extremely complicated and not as straightforward as attacking a custom implementation, as I initially thought it would be.
We concluded that D-Bus is implemented well, and finding vulnerabilities in D-Bus itself is challenging.
After a brainstorming session lasting several hours, we devised a plan to create our application that sends IPC messages to the root binary to execute privileged actions.
So, we spent some hours reading the Glib Documentation and wrote a C code with multiple errors, and finally, after numerous attempts, the code was working.
#include <stdio.h>
#include <gio/gio.h>
int main() {
GError *error = NULL;
GDBusConnection *connection;
GDBusMessage *message, *reply;
GVariant *variant;
GDBusProxy *proxy;
int ret;
// Synchronously get the session bus
connection = g_bus_get_sync(1, NULL, &error);
if (error != NULL) {
g_printerr("Error connecting to bus: %s\n", error->message);
g_error_free(error);
return 1;
}
proxy = g_dbus_proxy_new_sync(connection,0,0,"dbus_service_name","/object_path","interface_name",0,&error);
variant = g_variant_new("data_type","data");
variant = g_dbus_proxy_call_sync(proxy,"method_name",variant,0,60000,0,&error);
g_variant_get(variant,"return_value_data_type",&ret);
printf("The server answered with %d\n",ret);
}
The code was functioning correctly, but the root application was not performing the required action; it was rejecting the IPC message we sent.
After all the reading I have done from the D-Bus Documentation, I am pretty sure this behaviour wasn’t typical of D-Bus.
Our attention turned towards the application binary once again, and we scrolled through some decompiled code. Then, there was this function call with the name.
pcVar16 = (char *)dbus_message_get_sender(param_2);
cVar8 = secret_object_name::verifyRPCClient(this,pcVar16);
This verifyRPCClient function, in turn, calls verifySignature, which calls the signedelf function with an RSAPublicKey to verify the binary that has been sending the IPC messages.
We were monitoring the bus using `busctl`, and whenever I attempted to send a message, D-Bus displayed an error indicating that authentication was being confirmed.
In my mind, I was still pondering a way to perform unauthenticated IPC calls. Finally, reality struck me that making unauthenticated IPC calls to this application was not possible.
Plan B also failed miserably.
Both the plans have failed miserably because of the secure implementation of the application as well as the D-bus.
The only way is to send authenticated IPC messages without requiring credentials. However, the problem is that the messages are only sent when credentials are provided.
We explored all our previous options, searching for any issues in the application that could potentially assist us in achieving this goal.
We have already identified a shared object injection using the LD_Preload method in the application with lower privileges. However, initially, the issue seemed to have no impact since the application runs with normal user privileges, and our objective was to perform privileged actions.
Since the IPC component is present in the application, it can make calls to the application with root privileges. The impact doesn’t seem to be less than we thought it would be, and it helps us achieve our primary objective as well. This issue can also bypass the IPC signature checks.
Shared object injection on Linux is a technique used to load a shared library (shared object, .so file) into the address space of a running process. This allows the injected code to execute within the context of the target process, granting access to the process’s memory, resources, and execution flow.
The shared object injection vulnerability came into play again, allowing us to intercept the library calls we make by passing them through our shared library and injecting our code into the application.
We searched for the attack surface and found multiple function points that could alter the code flow to achieve our goal.
After spending hours finding the attack surface for reliably performing the attack, our team discovered something very serious: the application with user privileges was sending critical sensitive data to the root privileges application via IPC, which could be exploited using our injection bug and could have a greater impact than our initial goal.
This single bug can now exploit two critical vulnerabilities in the application, each with maximum impact:
1) Without providing credentials, we can bypass the application’s logic flow and perform actions it wasn’t supposed to allow.
2) The application with normal user privileges sent sensitive critical data to the application with root privileges through D-Bus.
By intercepting the library calls to the application and injecting our code, we could perform privileged actions without authentication and exploit a sensitive data leak.
In conclusion, after a thorough investigation and leveraging a Shared Object Injection vulnerability, our team uncovered significant weaknesses in the application’s security. This allowed us to bypass authentication, manipulate how the application operates to perform unauthorized actions, and intercept sensitive data transmitted between different privilege levels. These discoveries emphasize the need for careful security assessments and shed light on the vulnerabilities that can arise from loading shared libraries without integrity checks in software applications.