
Análise aprofundada da CVE-2021-3560, uma vulnerabilidade de escalonamento local de privilégios no Linux PolKit. Inclui análise da causa raiz, mecânica do exploit e uma demonstração da condição de corrida que leva ao acesso root.
[toc]
ID da vulnerabilidade: CVE-2021-3560
Pontuação da vulnerabilidade:
Produto vulnerável: linux PolKit (polkitd)
Escopo de impacto: introduzido no código-fonte 0.113; https://www.venustech.com.cn/new_type/aqtg/20210611/22788.html
Condições de exploração: linux local; dbus + polkitd presente
Efeito da exploração: elevação de privilégio local
Obtenção do código-fonte:
apt source accountsserviceapt source dbusA imagem padrão do Ubuntu ubuntu-20.04.2 já pode ser reproduzida: http://old-releases.ubuntu.com/releases/20.04.2/ubuntu-20.04.2-desktop-amd64.iso
Primeiro, veja o código do problema
/polkit-0.113/src/polkit/polkitsystembusname.c : 388 : polkit_system_bus_name_get_creds_sync
static void
on_retrieved_unix_uid_pid (GObject *src,
GAsyncResult *res,
gpointer user_data)
{
AsyncGetBusNameCredsData *data = user_data;
GVariant *v;
v = g_dbus_connection_call_finish ((GDBusConnection*)src, res,
data->caught_error ? NULL : data->error);
if (!v)
{
data->caught_error = TRUE; //某些原因失败之后设置error位
}
else
{
··· ···//执行成功的数据处理
}
··· ···
}
static gboolean
polkit_system_bus_name_get_creds_sync (PolkitSystemBusName *system_bus_name,
guint32 *out_uid,
guint32 *out_pid,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
AsyncGetBusNameCredsData data = { 0, }; //data 被初始化为0
··· ···
g_dbus_connection_call (connection,
"org.freedesktop.DBus", /* name */
"/org/freedesktop/DBus", /* object path */
"org.freedesktop.DBus", /* interface name */
"GetConnectionUnixUser", /* method */
g_variant_new ("(s)", system_bus_name->name),
G_VARIANT_TYPE ("(u)"),
G_DBUS_CALL_FLAGS_NONE,
-1,
cancellable,
on_retrieved_unix_uid_pid, //回调函数
&data);
g_dbus_connection_call (connection,
"org.freedesktop.DBus", /* name */
"/org/freedesktop/DBus", /* object path */
"org.freedesktop.DBus", /* interface name */
"GetConnectionUnixProcessID", /* method */
g_variant_new ("(s)", system_bus_name->name),
G_VARIANT_TYPE ("(u)"),
G_DBUS_CALL_FLAGS_NONE,
-1,
cancellable,
on_retrieved_unix_uid_pid, //回调函数
&data);
while (!((data.retrieved_uid && data.retrieved_pid) || data.caught_error))
g_main_context_iteration (tmp_context, TRUE); //等待总线那边执行完毕,uid、pid都处理完毕或异常
if (out_uid)
*out_uid = data.uid;
if (out_pid)
*out_pid = data.pid;
ret = TRUE; //无论如何都是 TRUE?
out:
if (tmp_context)
{
g_main_context_pop_thread_default (tmp_context);
g_main_context_unref (tmp_context);
}
if (connection != NULL)
g_object_unref (connection);
return ret;
}
Na função polkit_system_bus_name_get_creds_sync, que realiza a autenticação de identidade para certas requisições (a lógica do cenário específico será detalhada posteriormente). Ela chama os métodos GetConnectionUnixUser e GetConnectionUnixProcessID através do barramento org.freedesktop.DBus. Esses métodos, fornecidos pelo barramento org.freedesktop.DBus, obtêm o PID e o UID do processo solicitante, que são passados para a função de retorno on_retrieved_unix_uid_pid para processar os dados retornados. Em seguida, o código aguarda bloqueando até que o barramento termine o processamento.
Pode-se ver na função on_retrieved_unix_uid_pid que, com base no resultado retornado pelo barramento, define se houve uma exceção (error?) e então define o sinalizador de exceção. Se a execução for bem-sucedida, retorna normalmente o UID do usuário ou o PID do processo. Mas, na função polkit_system_bus_name_get_creds_sync, após aguardar bloqueando o retorno do barramento (julgando se os sinalizadores de processamento indicam que tanto UID quanto PID foram processados ou que ocorreu uma exceção), ou seja, logicamente, o barramento pode retornar três resultados: obter UID de usuário 0 (usuário privilegiado); obter UID de usuário não zero (usuário comum); ou exceção. No entanto, no processamento posterior, não há tratamento para exceções: ele define diretamente out_uid a ser retornado para a camada superior como data.uid retornado pelo barramento e define ret como TRUE:
while (!((data.retrieved_uid && data.retrieved_pid) || data.caught_error))
g_main_context_iteration (tmp_context, TRUE); //等待总线那边执行完毕,uid、pid都处理完毕或异常
if (out_uid)
*out_uid = data.uid;
if (out_pid)
*out_pid = data.pid;
ret = TRUE; //无论如何都是 TRUE?
Mas ignora uma situação: quando a execução no barramento DBUS resulta em exceção, a função de retorno define o sinalizador de exceção e retorna sem processar os dados data, que foi inicializado como 0. Isso faz com que posteriormente out_uid seja definido como 0, ou seja, usuário privilegiado.
Então, onde essa função problemática polkit_system_bus_name_get_creds_sync é usada?
O ponto vulnerável está no processo polkitd:
polkit é um conjunto de ferramentas em nível de aplicação que, através da definição e auditoria de regras de permissão, possibilita a comunicação entre processos de diferentes prioridades: as decisões de controle são centralizadas em um framework unificado, determinando se um processo de baixa prioridade tem acesso a um processo de alta prioridade.
Resumindo, é um processo root executado em segundo plano que, por meio de comunicação entre processos, realiza um julgamento de permissão quando outros processos de baixa prioridade tentam acessar funcionalidades fornecidas por processos de alta prioridade. Como anexar um depurador a esse processo o reinicia, só podemos analisar pelo código-fonte para encontrar o caminho de gatilho da função vulnerável:
O polkitd utiliza extensivamente o framework de comunicação entre processos gio DBUS. Pode-se consultar previamente o manual e prestar atenção a algumas funções importantes.
A seguir, analisa-se o caminho de gatilho da vulnerabilidade no polkitd:
Primeiro, o main:
polkit-0.113\src\polkitbackend\polkitd.c : 155 : main
int
main (int argc,
char **argv)
{
··· ···
··· ···
loop = g_main_loop_new (NULL, FALSE);
sigint_id = g_unix_signal_add (SIGINT,
on_sigint,
NULL);
name_owner_id = g_bus_own_name (G_BUS_TYPE_SYSTEM,
"org.freedesktop.PolicyKit1", //注册了一个总线名称
G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
(opt_replace ? G_BUS_NAME_OWNER_FLAGS_REPLACE : 0),
on_bus_acquired, //访问该总线时的回调函数
on_name_acquired,
on_name_lost,
NULL,
NULL);
g_print ("Entering main event loop\n");
g_main_loop_run (loop); //循环启动
··· ···
··· ···
}
Primeiro, registra um barramento chamado org.freedesktop.PolicyKit1, e há três funções de retorno; foca principalmente em on_bus_acquired, que é chamada quando o barramento é acessado.