
Exploit de raiz para CVE-2021-4034 (PwnKit) que abusa da escrita fora dos limites do pkexec para escalar privilégios para root em sistemas Linux.
Exploit de raiz para a vulnerabilidade PwnKit. Consulte o relatório original aqui.
Use este exploit com permissão expressa dos proprietários do sistema alvo.
Nenhuma dependência necessária além da libc. Basta executar make.
Executar sem opções executará o exploit:
[linux@linux ~]$ ./exploit
-----------------------------------------------------------------------------
__\ / __ __ _ __ _ __ | \ / _ ___
/ V |_ --- _)/ \ _)/| ---|_|/ \__)|_| | V |_) _/|_|
\__ |__ /__\_//__ | |\_/__) | | | \/__| |
-----------------------------------------------------------------------------
sh-5.1# whoami
root
sh-5.1#
Você pode personalizar o caminho para pkexec bem como o conjunto de caracteres "from":
[linux@linux ~]$ ./exploit -h
...
./exploit [-c] [-h] [-f from_charset] [-p /path/to/pkexec]
-----------------------------------------------------------------------------
-c Apenas teardown - não explorar
-p <path> Caminho para pkexec (padrão: "/usr/bin/pkexec")
-f <from_charset> Conjunto de caracteres "from" personalizado (padrão: "UTF-8")
-h Exibir esta mensagem
GIO_USE_VFS?!Vi algumas pessoas nas redes sociais perguntarem por que alguns exploits falham se
GIO_USE_VFS= não está definido? Por que eles funcionam com as versões mais antigas?
O commit daf3d5c2d15466a267221fcb099c59c870098e03 no polkit é o culpado.
Aqui está a parte relevante do 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)
{
Versões anteriores a este commit são exploráveis sem a necessidade de definir a
variável GIO_USE_VFS. As versões posteriores - não são exploráveis a menos que esta variável seja definida.
O propósito do commit é na verdade uma pista falsa. Não é o que a variável
significa, é como sua presença afeta o ambiente do programa. Para a verdade, devemos olhar para a libc.
O ambiente de um processo na libc é representado por um array de char *s,
apontado por esta variável global:
char **environ;
environ vive no heap e é ocasionalmente realocado. Você pode
já saber aonde isso vai dar. Confira este trecho 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() é chamado tanto por setenv(3) quanto por putenv(3) para realizar
o que eles prometem - definir uma variável de ambiente. Se a variável de ambiente em
questão não está definida, environ precisa ser realocado para acomodar
uma nova entrada (um ponteiro para o novo par key=value do ambiente). Se ela está
definida, o tamanho do array environ não mudou e, portanto, não há
razão para realocação. Por brevidade, omiti essa parte do código - eu
encorajo você a conferi-la.
Agora vamos voltar ao exploit. Se você chegou até aqui, provavelmente
já conhece a metodologia por trás deste exploit (se não, por favor consulte o
relatório original).
Estamos tentando introduzir uma variável de ambiente passando argumentos de programa
vazios (argv) para pkexec. Quando argc está verdadeiramente vazio (nem mesmo um nome de
programa), as variáveis de ambiente, que são adjacentes, colidem com os argumentos.
Abusamos desse comportamento para forçar pkexec a escrever um caminho canônico de um
executável alvo no ambiente. No entanto, antes de chegarmos a esta parte do
código, isto acontece:
setenv ("GIO_USE_VFS", "local", 1);
Se esta variável não estiver presente no ambiente, environ será
realocado, portanto nunca colidindo com argv. Como resultado, a escrita fora dos limites
não afetará o ambiente do programa, fazendo com que o
exploit falhe.