# Analysis of Aruba CVE-2024-31472 ## I. Overview This vulnerability was discovered by Erik De Jong ([bugcrowd.com/erikdejong](http://bugcrowd.com/erikdejong)) and published at [Aruba Vulnerability Summary](https://csaf.arubanetworks.com/2024/hpe_aruba_networking_-_2024-006.txt) ### 1\. Vuln details ```bash Unauthenticated Command Injection Vulnerabilities in the Soft AP Daemon Service Accessed by the PAPI Protocol (CVE-2024-31472) -------------------------------------------------------------- There are command injection vulnerabilities in the underlying Soft AP Daemon service that could lead to unauthenticated remote code execution by sending specially crafted packets destined to the PAPI (Aruba's Access Point management protocol) UDP port (8211). Successful exploitation of these vulnerabilities result in the ability to execute arbitrary code as a privileged user on the underlying operating system. Internal References: ATLWL-448, ATLWL-449 Severity: Critical CVSSv3 Overall Score: 9.8 CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H Discovery: These vulnerabilities were discovered and reported by Erik De Jong (bugcrowd.com/erikdejong) via HPE Aruba Networking's bug bounty program. Workaround: Enabling cluster-security via the cluster-security command will prevent these vulnerabilities from being exploited in InstantOS devices running 8.x or 6.x code. For ArubaOS 10 devices this is not an option and instead access to port UDP/8211 must be blocked from all untrusted networks. Please contact HPE Services - Aruba Networking TAC for configuration assistance. Link: https://www.arubanetworks.com/assets/alert/ARUBA-PSA-2024-006.txt ``` ### 2\. Affected version ```bash - ArubaOS 10.6.x.x: 10.6.0.0 and above - ArubaOS 10.5.x.x: 10.5.1.1 and above - ArubaOS 10.4.x.x: 10.4.1.1 and above - InstantOS 8.12.x.x: 8.12.0.0 and above - InstantOS 8.11.x.x: 8.11.2.2 and above - InstantOS 8.10.x.x: 8.10.0.11 and above - InstantOS 8.6.x: 8.6.0.24 and above ``` ### 3\. Firmware Extract Download 2 version of firmware: ```bash ArubaInstant_Ursa_8.11.2.1_88699 ArubaInstant_Ursa_8.11.2.2_89329 ``` The vulnerable version is `8.11.2.1` Extract command: ```bash binwalk -e -M <firmware> ``` The vulnerable file: ```bash cpio-root/aruba/bin/sapd ``` ## II. Bindiff Sort by similarity, because the vuln we are looking for is a command injection, we should check each entry for patches which contains functions like `system`, `execute_xxxx`,… ![1](https://hackmd.io/_uploads/SyblRXg9Je.png) Also, the parameter passed to those functions must be able to be controlled. The problematic call appear at address `ED8A0` in `sub_000ED61C`, in the patched version, the whole function is removed from the binary. ![2](https://hackmd.io/_uploads/B1DxRQxqkl.png) ## III. Analysis This is what that call looks like in IDA ![3](https://hackmd.io/_uploads/Bk5xR7lqJx.png) The `command` variable can be controlled by attacker, go back to the function that call `sub_ED61C` to understand how to control `command` value. Function `sub_F6428` has a big switch-case block which process message sent to `sapd` service base on msg opcode. For each opcode, it call the appropriate function and pass a structure as parameter. This structure can be controlled. `sub_ED61C` can be accessed by sending opcode value `0xAC`. ![4](https://hackmd.io/_uploads/r1Re07ec1e.png) Note that the same opcode value is not available in the patched firmware version. Maybe HPE decided to remove the problem instead of fixing it. Enter `sub_ED61C`, it extract some option value from struct `a1` ![5](https://hackmd.io/_uploads/rJbbRXgckx.png) With option `v4` = `filename`, `v5` is copied to `s`. `v5` value is supposed to be a filename string, or a path to file. Its value is then go through a loop which insert `\` prior to some characters. ![6](https://hackmd.io/_uploads/ByEZRmlqyg.png) Even though most of the dangerous command injection character are filtered out, "\`" remain available. Attacker can craft a payload like this to create file `/tmp/haha.txt` ```bash `touch${IFS}/tmp/haha.txt` ``` ![image](https://hackmd.io/_uploads/SkVOcYD6Je.png) The command that executed in `system` should be like this ```bash dumpregs > /tmp/`touch${IFS}/tmp/haha.txt` ``` ## IV. Emulation using qiling This blog post won't demonstrate the way to reach the targeted function but instead we will focus on the emulation of the function itself. We will using qiling to run the service and at the start of `main` function, we set up parameters and change the `pc` register to our targeted function. Then (hopefully) the program will reach that problematic `system` call. Even though qiling is not good at handling `system`, we just have to verify the parameter that passed to it to declare victory! First we must identify the start and end of emulation, to make it simple, i choose the function call instruction for start address and the next instruction for end address. ![image](https://hackmd.io/_uploads/HJsLScPTJx.png) Also, it is important to specify the binary and directory that we want to emulate ```bash from qiling import * from qiling.const import * import struct BEGIN_ADDRESS = 0xF706C END_ADDRESS = 0xF7070 argv = "./cpio-root-2.1/aruba/bin/sapd".split() rootfs = "./cpio-root-2.1" ``` We want to execute the function immediately by setting hook at the start of the program. ```bash def startHook(ql): ql.arch.regs.write("r0", BEGIN_ADDRESS) if __name__ == "__main__": ql = Qiling(argv, rootfs, multithread=True, verbose=QL_VERBOSE.DEBUG) ql.hook_address(startHook, 0x10A38) ``` `sub_ED61C` requires 1 parameter, it is actually a struct that contains many informations. However, we only focus on data at offset 396 ![image](https://hackmd.io/_uploads/SyOVs5DaJx.png) This function will craft a valid object that makes `v4` hold `filename` and `v5` become our command payload ```bash def setUpReg(ql): buf_a1 = ql.mem.map_anywhere(0x1000) addr = ql.mem.map_anywhere(0x1000) struct_a1 = b"\x00" * 128 + b"\xac" struct_a1 += b"\x00" * (396 - len(struct_a1)) struct_a1 += struct.pack('<I', addr + 0x200) struct_a1 += b"\x00" * (0x200 - len(struct_a1)) struct_a1 += struct.pack('<I', addr + 0x300) struct_a1 += b"\x00" * (0x300 - len(struct_a1)) struct_a1 += struct.pack('<I', addr + 0x400) struct_a1 += b"\x00" * (0x400 - len(struct_a1)) struct_a1 += struct.pack('<I', addr + 0x500) struct_a1 += struct.pack('<I', addr + 0x600) struct_a1 += b"\x00" * (0x500 - len(struct_a1)) struct_a1 += b"filename" + b"\x00" struct_a1 += b"\x00" * (0x600 - len(struct_a1)) struct_a1 += b"`touch${IFS}/tmp/haha.txt`" ql.mem.write(addr, struct_a1) ql.arch.regs.write("r0", addr) ``` During the emulation progress, there will be some function that the emulate environment not satisfy then crash the program. When we encounter these problem, it is better to hook or patch the process than actually fix it - which is time consuming. However, keep in mind that the hook should not interfere with the logic too much that will change the code flow directly. For example, in `sub_ED61C` there is a call to check if a specific binary is available for use ![image](https://hackmd.io/_uploads/HyQ5a9vaye.png) Checks like this can be hooked to make it always return our desired value. Here is the full script: ```python from qiling import * from qiling.const import * import struct BEGIN_ADDRESS = 0xF706C END_ADDRESS = 0xF7070 argv = "./cpio-root-2.1/aruba/bin/sapd".split() rootfs = "./cpio-root-2.1" def setUpReg(ql): buf_a1 = ql.mem.map_anywhere(0x1000) addr = ql.mem.map_anywhere(0x1000) struct_a1 = b"\x00" * 128 + b"\xac" struct_a1 += b"\x00" * (396 - len(struct_a1)) struct_a1 += struct.pack('<I', addr + 0x200) struct_a1 += b"\x00" * (0x200 - len(struct_a1)) struct_a1 += struct.pack('<I', addr + 0x300) struct_a1 += b"\x00" * (0x300 - len(struct_a1)) struct_a1 += struct.pack('<I', addr + 0x400) struct_a1 += b"\x00" * (0x400 - len(struct_a1)) struct_a1 += struct.pack('<I', addr + 0x500) struct_a1 += struct.pack('<I', addr + 0x600) struct_a1 += b"\x00" * (0x500 - len(struct_a1)) struct_a1 += b"filename" + b"\x00" struct_a1 += b"\x00" * (0x600 - len(struct_a1)) struct_a1 += b"`touch${IFS}/tmp/haha.txt`" ql.mem.write(addr, struct_a1) ql.arch.regs.write("r0", addr) def hook1(ql): ql.arch.regs.write("r0", 0) def startHook(ql): ql.arch.regs.write("r0", BEGIN_ADDRESS) if __name__ == "__main__": ql = Qiling(argv, rootfs, multithread=True, verbose=QL_VERBOSE.DEBUG) ql.hook_address(startHook, 0x10A38) ql.hook_address(setUpReg, BEGIN_ADDRESS) ql.hook_address(hook1, 0xED824) ql.debugger = True ql.run(end=END_ADDRESS) ``` Run that script, open up `gdb-multiarch`, set breakpoint at `0xED8A0` and then `target remote :9999`. After continuing the debugger, we reach the `system` call ![image](https://hackmd.io/_uploads/SJcNkjDpyx.png) Success! We can later verify this by creating a simple C program that call system with the exact parameter. It should works. The script above serve only as the demonstration of the vulnerability. The full POC to actually attack real device is not included in this blog post and left as an exercise for readers. It requires a deeper analysis of how services communicate with each other using `Amapi` packet.