Skip to content

Commit fe6725d

Browse files
committed
Fix CVE-2026-62661 JFSA rendering: restore Description and PoC
1 parent b38fdc7 commit fe6725d

1 file changed

Lines changed: 28 additions & 7 deletions

File tree

vulnerabilities/the-reachy-mini-bluetooth-command-handler-is-vulnerable-to-arbitrary-root-script-execution-via-path-traversal-cve-2026-62661.md

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,21 @@ reachy-mini (Bluetooth command service)
2929

3030
## Description
3131

32-
The Reachy Mini Bluetooth command handler (BluetoothCommandService._handle_command in src/reachy_mini/daemon/app/services/bluetooth/bluetooth_service.py) accepts a CMD_<script> payload after a successful PIN authentication and runs the matching file from the commands/ directory with sudo. The script name is taken from the BLE payload with no sanitization and combined using os.path.join("commands", script_name). On POSIX, if the second argument is an absolute path, os.path.join discards the commands/ prefix entirely, so a payload such as CMD_/tmp/reachy_pwn causes the handler to execute sudo /tmp/reachy_pwn.sh. Relative payloads such as CMD_../attacker/pwn likewise escape the intended directory. An attacker who can place a .sh file on the filesystemfor example via the unrestricted media-sounds upload (CVE-2026-55419) can therefore run that script as root.
32+
The Reachy Mini Bluetooth command handler (`BluetoothCommandService._handle_command` in `src/reachy_mini/daemon/app/services/bluetooth/bluetooth_service.py`) accepts a `CMD_` payload after a successful PIN authentication and runs the matching `.sh` file from the `commands/` directory with `sudo`. The script name is taken from the BLE payload with no sanitization and combined using `os.path.join("commands", script_name)`. On POSIX, if the second argument is an absolute path, `os.path.join` discards the `commands/` prefix entirely, so a payload such as `CMD_/tmp/reachy_pwn` causes the handler to execute `sudo /tmp/reachy_pwn.sh`. Relative payloads such as `CMD_../attacker/pwn` likewise escape the intended directory. An attacker who can place a `.sh` file on the filesystem, for example via the unrestricted media-sounds upload (CVE-2026-55419), can therefore run that script as root.
3333

3434
This issue is the third step in a documented compromise chain: unrestricted file upload, Bluetooth authentication bypass, then this directory traversal. The published CVSS score (7.2, AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H) reflects authenticated Bluetooth access.
3535

36+
The vulnerable join looks like this:
37+
38+
```
39+
script_name = command_str[4:].strip() + ".sh"
40+
script_path = os.path.join("commands", script_name)
41+
if os.path.isfile(script_path):
42+
subprocess.run(["sudo", script_path], capture_output=True, text=True)
43+
```
44+
45+
Python documents that an absolute second argument replaces the earlier join pieces, so `os.path.join("commands", "/tmp/reachy_pwn.sh")` evaluates to `/tmp/reachy_pwn.sh`.
46+
3647
## PoC
3748

3849
<br>
@@ -42,7 +53,7 @@ This issue is the third step in a documented compromise chain: unrestricted file
4253
<br>
4354

4455
```
45-
python3 -c "import os; print(os.path.join('commands', '/tmp/reachy_pwn.sh'))"
56+
python3 -c "import os; print(os.path.join('commands', '/tmp/reachy_pwn.sh')); print(os.path.join('commands', '../attacker/pwn.sh'))"
4657
```
4758

4859
<br>
@@ -51,22 +62,23 @@ Expected output:
5162

5263
```
5364
/tmp/reachy_pwn.sh
65+
../attacker/pwn.sh
5466
```
5567

5668
<br>
5769

58-
The first argument is discarded because the second argument is an absolute path.
70+
The first argument is discarded when the second argument is an absolute path. A `../` segment also leaves `commands/`.
5971

6072
<br>
6173

6274
**Step 2 - Authenticate on the Bluetooth command characteristic**
6375

6476
<br>
6577

66-
From a BLE client (nRF Connect, Web Bluetooth, or the Reachy Mini control app), write the robot PIN as:
78+
From a BLE client (nRF Connect, Web Bluetooth, or the Reachy Mini control app), write the robot PIN. The handler expects the `PIN_` prefix followed by the last five digits of the serial, for example:
6779

6880
```
69-
PIN_<last_5_digits_of_serial>
81+
PIN_12345
7082
```
7183

7284
<br>
@@ -99,15 +111,24 @@ On a vulnerable build this resolves to `sudo /tmp/reachy_pwn.sh` instead of a fi
99111

100112
<br>
101113

102-
On a Linux checkout of reachy_mini 1.10.0 or later:
114+
The vendor shipped `tests/unit_tests/test_ble_path_traversal.py` with the fix. On a Linux checkout of an unpatched tree, copy that test in and run:
103115

104116
```
105117
pytest tests/unit_tests/test_ble_path_traversal.py -v
106118
```
107119

108120
<br>
109121

110-
Those tests assert that the handler never invokes sudo on a path outside `commands/`. They fail against unpatched trees (PATH TRAVERSAL: handler invoked sudo ...) and pass once the command name is constrained to a bare `[A-Za-z0-9_-]+` filename.
122+
Expected output on a vulnerable build:
123+
124+
```
125+
PATH TRAVERSAL: handler invoked `sudo ...` for payload ...
126+
FAILED tests/unit_tests/test_ble_path_traversal.py
127+
```
128+
129+
<br>
130+
131+
Those tests assert that the handler never invokes sudo on a path outside `commands/`. They fail while the vulnerability is present and pass once the command name is constrained to a bare `[A-Za-z0-9_-]+` filename.
111132

112133
## Vulnerability Mitigations
113134

0 commit comments

Comments
 (0)