Partie 26 - Seccomp - fonctionnement (1/2)
Seccomp : fonctionnement (1/2)
Avoir la possibilitĂ© dâexĂ©cuter un shellcode dans un programme exploitĂ© permet dâavoir accĂšs Ă un trĂšs grand nombre dâactions possibles :
- ouvrir un terminal ;
- gérer des connexions réseau ;
- ouvrir, lire et écrire des fichiers ;
- et bien plus encore.
Une idĂ©e qui peut rapidement venir Ă lâesprit est la suivante : mon programme âHello worldâ ne fait quâafficher une chaĂźne de caractĂšres. Pourquoi ne pas restreindre les appels systĂšme autorisĂ©s Ă read et write uniquement ?
Eh bien câest justement ce que permet de faire seccomp !
Quâest-ce que seccomp ?
seccomp signifie SECCure COMPuting (informatique sĂ©curisĂ©e đ„). Il sâagit dâun mĂ©canisme disponible sous Linux qui permet de filtrer les appels systĂšme accessibles depuis une application.
Il y a principalement trois maniĂšres de le mettre en place :
- bloquer certains appels systĂšme (liste noire) ;
- autoriser certains appels systĂšme (liste blanche) ;
- filtrer certains appels systĂšme en fonction de leurs arguments.
seccomp est une fonctionnalité assez ancienne mais qui reste utilisée dans des applications qui nécessitent une sécurité accrue :
Contrairement Ă ce que lâon pourrait penser, seccomp nâest pas un mĂ©canisme de cloisonnement (sandboxing) Ă part entiĂšre.
Il se limite Ă restreindre les appels systĂšme quâun programme est autorisĂ© Ă invoquer, en appliquant un filtrage au niveau du noyau, sans fournir Ă lui seul dâisolation complĂšte de lâenvironnement dâexĂ©cution.
Comment cela fonctionne ?
đ Le mode strict (SECCOMP_SET_MODE_STRICT)
Initialement seccomp limitait les appels systÚme que peut exécuter un programme aux quatre suivants :
read;write;exit;sigreturn.
Si le programme exécute un autre appel systÚme que ceux-là , le signal SIGKILL sera envoyé au programme.
Ah ouais câest la hess đ !
Eh oui ⊠Le programme ouvre un fichier ? â. Le programme utilise des sockets rĂ©seau ? â. Ainsi, Ă part un programme bateau type âHello Worldâ, on ne peut pas faire grand chose đ. SeccompliquĂ© cette histoire âŠ
đ Le mode filtrage (SECCOMP_SET_MODE_FILTER)
Bon je vous rassure, le prĂ©cĂ©dent mode nâest (quasiment ?) plus utilisĂ©. A la place on utilise plutĂŽt le mode filtrage qui permet bien plus de flexibilitĂ©.
Ce mode permet de dĂ©finir des rĂšgles prĂ©cises indiquant quels appels systĂšme sont autorisĂ©s, refusĂ©s ou bloquĂ©s, ce qui rend seccomp plus adaptĂ© aux besoins dâune application.
Pour écrire une liste de rÚgles nous utilisons des BPF ou Filtre de Paquets de Berkeley.
Et ⊠le rapport avec Berkeley ?
Le nom Berkeley Packet Filter (BPF) tire son nom de lâUniversitĂ© de Californie Ă Berkeley, oĂč il a Ă©tĂ© dĂ©veloppĂ© Ă la fin des annĂ©es 1980. Ă lâorigine, il a Ă©tĂ© conçu par des chercheurs de Berkeley pour filtrer efficacement les paquets rĂ©seau sans avoir Ă les copier inutilement entre le noyau et lâespace utilisateur. Le terme Berkeley fait donc rĂ©fĂ©rence au lieu de conception du projet, et non Ă une technologie rĂ©seau en particulier đ€.
De toute façon, la maniĂšre dont ces filtres sont mis en place ne nous intĂ©resse pas particuliĂšrement, puisque nous ne sommes pas dans un cours de dĂ©veloppement sĂ©curisĂ©. En revanche, ce qui nous importe rĂ©ellement est de savoir comment dĂ©terminer, lors de lâanalyse dâun programme, si lâexĂ©cution de tel ou tel appel systĂšme est autorisĂ©e ou non.
đ Les rĂšgles de filtrage
Pour ce faire, nous allons utiliser lâoutil seccomp-tools qui permet notamment dâextraire les rĂšgles de filtrage BPF mises en place dans un programme en lâexĂ©cutant via lâoption dump.
Si vous manipulez les programmes de ce chapitre via le conteneur Docker, lâoutil seccomp-tools y est dĂ©jĂ installĂ©.
Si vous ne souhaitez pas exĂ©cuter le programme dont les rĂšgles de filtrage sont Ă extraire, il est possible dâutiliser lâoption
disasm. Néanmoins, il faudra extraire en amont le filtre BPF brut du programme.
Exemple de test
Prenons un exemple concret pour comprendre comment lire ces filtres. Voici le programme que nous utiliserons dans cet exemple :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <linux/audit.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <stddef.h>
/* Macros utilitaires simples */
#define SC_ALLOW(syscall) \
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, syscall, 0, 1), \
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW)
#define SC_KILL \
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS)
int install_seccomp(void)
{
struct sock_filter filter[] = {
/* Chargement du numero de lâappel systeme */
BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
offsetof(struct seccomp_data, nr)),
/* Appels systeme autorises */
SC_ALLOW(__NR_read),
SC_ALLOW(__NR_write),
SC_ALLOW(__NR_open),
/* Gestion de la memoire (maximum 3) */
SC_ALLOW(__NR_brk),
SC_ALLOW(__NR_mmap),
SC_ALLOW(__NR_munmap),
/* Tout le reste est interdit */
SC_KILL,
};
struct sock_fprog prog = {
.len = sizeof(filter) / sizeof(filter[0]),
.filter = filter,
};
/* Obligatoire pour installer un filtre seccomp */
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
perror("prctl(NO_NEW_PRIVS)");
return -1;
}
if (syscall(SYS_seccomp, SECCOMP_SET_MODE_FILTER, 0, &prog)) {
perror("seccomp");
return -1;
}
return 0;
}
int main(void) {
printf("[+] Installation du filtre seccomp\n");
if (install_seccomp() < 0) {
fprintf(stderr, "[-] Echec de lâinstallation de seccomp\n");
exit(1);
}
printf("[+] Seccomp installe\n");
printf("[+] Tentative dâexecution de execve(\"/bin/sh\")...\n");
char *argv[] = {"/bin/sh", NULL};
char *envp[] = {NULL};
execve("/bin/sh", argv, envp);
/* Cette ligne ne sera JAMAIS atteinte */
perror("execve");
return 0;
}
Il nâest pas nĂ©cessaire de sâintĂ©resser Ă la fonction install_seccomp. Ce programme implĂ©mente un filtre BPF grĂące Ă seccomp via syscall(SYS_seccomp, SECCOMP_SET_MODE_FILTER, 0, &prog).
Normalement les commentaires ont dĂ» vous aider Ă comprendre ce que fait le programme :
- autoriser seulement 6 appels systĂšme ;
- tous les autres appels systĂšme sont interdits.
AccĂšs au conteneur Docker :
- âŹïž TĂ©lĂ©chargement : pwn-seccomp-exemple-1.zip
- đ SHA256 & Analyse Virus Total : 915fbaa0269f75f63d62428e3329f36a1c150d334bf16aaaca4de1e292ae20a7
- âïž Construction et lancement du conteneur :
1
2
3
docker build -t pwn-seccomp-exemple-1 .
docker run -it --rm -p 1234:1234 --cap-add=SYS_PTRACE --security-opt seccomp=unconfined pwn-seccomp-exemple-1
Compilons le programme avec gcc main.c -o exe et exécutons-le :
1
2
3
4
5
6
$ ./exe
[+] Installation du filtre seccomp
[+] Seccomp installe
[+] Tentative dâexecution de execve("/bin/sh")...
[1] 52597 invalid system call (core dumped) ./exe
On pouvait sây attendre : execve nâest pas exĂ©cutĂ©. Le code de retour est le suivant :
1
2
echo $?
159
Or 159 == 128 + 31 oĂč 31 est le signum de SIGSYS: Bad system call.
Bah lĂ câest trivial de comprendre le filtre pourquoi a-t-on besoin dâutiliser un outil tel que
seccomp-toolsalors ?
Ici nous avons accĂšs au code source. Avez-vous jetĂ© un Ćil Ă ce que donne la dĂ©compilation de install_seccomp đ€Ż ?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
__int64 __fastcall install_seccomp()
{
__int64 v7; // [rsp+0h] [rbp-90h] BYREF
// (...)
unsigned __int64 v64; // [rsp+88h] [rbp-8h]
v64 = __readfsqword(0x28u);
v8 = 32;
v9 = 0;
v10 = 0;
v11 = 0;
v12 = 21;
v13 = 0;
v14 = 1;
v15 = 0;
v16 = 6;
v17 = 0;
v18 = 0;
v19 = 0x7FFF0000;
v20 = 21;
v21 = 0;
v22 = 1;
v23 = 1;
v24 = 6;
v25 = 0;
v26 = 0;
v27 = 0x7FFF0000;
v28 = 21;
v29 = 0;
v30 = 1;
v31 = 2;
v32 = 6;
v33 = 0;
v34 = 0;
v35 = 0x7FFF0000;
v36 = 21;
v37 = 0;
v38 = 1;
v39 = 12;
v40 = 6;
v41 = 0;
v42 = 0;
v43 = 0x7FFF0000;
v44 = 21;
v45 = 0;
v46 = 1;
v47 = 9;
v48 = 6;
v49 = 0;
v50 = 0;
v51 = 0x7FFF0000;
v52 = 21;
v53 = 0;
v54 = 1;
v55 = 11;
v56 = 6;
v57 = 0;
v58 = 0;
v59 = 0x7FFF0000;
v60 = 6;
v61 = 0;
v62 = 0;
v63 = 0x80000000;
LOWORD(v7) = 14;
if ( prctl(38, 1, 0, 0, 0, a6, v7, &v8) )
{
perror("prctl(NO_NEW_PRIVS)");
return 0xFFFFFFFFLL;
}
else if ( syscall(317, 1, 0, &v7) )
{
perror("seccomp");
return 0xFFFFFFFFLL;
}
else
{
return 0;
}
}
Je pense que lâon sera dâaccord sur le fait quâun outil permettant de les extraire automatiquement ne serait pas de refus đ.
prctl(PR_SET_NO_NEW_PRIVS, (...))doit toujours ĂȘtre appelĂ© avant dâappliquer le filtre seccomp.Lâune des autres consĂ©quence importante de
PR_SET_NO_NEW_PRIVSest que le processus courant (et ses potentiels processus fils) ne pourront pas gagner plus de privilĂšges qui en avaient au moment de lâappel deprctl(PR_SET_NO_NEW_PRIVS, (...)).Cela signifie notamment que les bits SUID et SGID seront ignorĂ©s. RIP đ.
Utilisons la commande seccomp-tools dump ./exe pour extraire le filtrage mis en place :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ seccomp-tools dump ./exe
[+] Installation du filtre seccomp
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000000 A = sys_number
0001: 0x15 0x00 0x01 0x00000000 if (A != read) goto 0003
0002: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0003: 0x15 0x00 0x01 0x00000001 if (A != write) goto 0005
0004: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0005: 0x15 0x00 0x01 0x00000002 if (A != open) goto 0007
0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0007: 0x15 0x00 0x01 0x0000000c if (A != brk) goto 0009
0008: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0009: 0x15 0x00 0x01 0x00000009 if (A != mmap) goto 0011
0010: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0011: 0x15 0x00 0x01 0x0000000b if (A != munmap) goto 0013
0012: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0013: 0x06 0x00 0x00 0x80000000 return KILL_PROCESS
Nous remarquons tout dâabord la prĂ©sence de la chaĂźne de caractĂšres [+] Installation du filtre seccomp mais pas celle des autres. On en dĂ©duit que :
- le programme est réellement exécuté et les rÚgles de filtrage ne sont pas extraites statiquement ;
- le programme nâest pas exĂ©cutĂ© dans son entiĂšretĂ©. Son exĂ©cution est arrĂȘtĂ©e au moment du
syscallqui fait appel Ăseccomp.
Ensuite, pour comprendre le filtre mis en place il suffit de lire linĂ©airement la partie Ă droite de haut en bas oĂč A est le potentiel appel systĂšme Ă exĂ©cuter.
Lorsque lâappel systĂšme a le droit dâĂȘtre exĂ©cutĂ©, ALLOW est retournĂ©. Lorsquâil nâa pas le droit dâĂȘtre exĂ©cutĂ©, KILL_PROCESS est retournĂ©.
Oui mais lĂ câest facile, câest un exemple bateau đŽ âŠ
TrĂšs bien. Analysons donc le cas dâun programme un peu plus connu.
Exemple réel : chromium
Nous avons vu un peu plus haut que chromium utilise seccomp afin de renforcer sa sĂ©curitĂ©. Nous devrions donc pouvoir en extraire le filtre seccomp, nan ? Tout dâabord installons-le. Pour les distributions basĂ©es sur Ubuntu/Debian cela peut se faire avec sudo apt install chromium-browser.
Ensuite, lançons le en arriÚre-plan sans utiliser la partie graphique :
1
2
chromium --headless --disable-gpu &
[1] 52665
Utilisons seccomp-tools avec le PID de chromium :
1
seccomp-tools dump --pid 52665
Vous devriez normalement tomber sur une erreur de ce type :
1
2
3
4
[ERROR] Operation not permitted - ptrace attach failed
PTRACE_SECCOMP_GET_FILTER requires CAP_SYS_ADMIN
Try:
sudo env "PATH=$PATH" seccomp-tools dump --pid 52665
Bon. Essayons avec la commande via sudo :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
$ sudo env "PATH=$PATH" seccomp-tools dump --pid 52665
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x12 0xc000003e if (A != ARCH_X86_64) goto 0020
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x1d 0xffffffff if (A != 0xffffffff) goto 0034
0005: 0x15 0x00 0x03 0x00000125 if (A != pipe2) goto 0009
0006: 0x20 0x00 0x00 0x0000001c A = flags >> 32 # pipe2(fildes, flags)
0007: 0x54 0x00 0x00 0x00000000 A &= 0x0
0008: 0x15 0x0e 0x17 0x00000000 if (A == 0) goto 0023 else goto 0032
0009: 0x15 0x00 0x16 0x00000010 if (A != ioctl) goto 0032
0010: 0x20 0x00 0x00 0x0000001c A = cmd >> 32 # ioctl(fd, cmd, arg)
0011: 0x15 0x00 0x03 0x00000000 if (A != 0x0) goto 0015
0012: 0x20 0x00 0x00 0x00000018 A = cmd # ioctl(fd, cmd, arg)
0013: 0x15 0x13 0x00 0x0000541c if (A == 0x541c) goto 0033
0014: 0x15 0x12 0x00 0x00005412 if (A == 0x5412) goto 0033
0015: 0x20 0x00 0x00 0x0000001c A = cmd >> 32 # ioctl(fd, cmd, arg)
0016: 0x54 0x00 0x00 0x00000000 A &= 0x0
0017: 0x15 0x00 0x0e 0x00000000 if (A != 0) goto 0032
0018: 0x20 0x00 0x00 0x00000018 A = cmd # ioctl(fd, cmd, arg)
0019: 0x15 0x0d 0x0b 0x0000541c if (A == 0x541c) goto 0033 else goto 0031
0020: 0x15 0x00 0x0d 0x40000003 if (A != ARCH_I386) goto 0034
0021: 0x20 0x00 0x00 0x00000000 A = sys_number
0022: 0x15 0x00 0x03 0x0000014b if (A != i386.pipe2) goto 0026
0023: 0x20 0x00 0x00 0x00000018 A = args[1]
0024: 0x54 0x00 0x00 0x00000080 A &= 0x80
0025: 0x15 0x07 0x06 0x00000080 if (A == 128) goto 0033 else goto 0032
0026: 0x15 0x00 0x05 0x00000036 if (A != i386.ioctl) goto 0032
0027: 0x20 0x00 0x00 0x00000018 A = level # setsockopt(fd, level, optname, optval, optlen)
0028: 0x15 0x04 0x00 0x0000541c if (A == 0x541c) goto 0033
0029: 0x15 0x03 0x00 0x0000541c if (A == 0x541c) goto 0033
0030: 0x15 0x02 0x00 0x00005412 if (A == 0x5412) goto 0033
0031: 0x15 0x01 0x00 0x00005412 if (A == 0x5412) goto 0033
0032: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0033: 0x06 0x00 0x00 0x0005000d return ERRNO(13)
0034: 0x06 0x00 0x00 0x00000000 return KILL
Et voilĂ đ ! Nous nâallons pas dĂ©cortiquer le filtre utilisĂ© par chromium nĂ©anmoins nous y reviendrons dans le prochain chapitre afin dâanalyser les erreurs dâimplĂ©mentation qui peuvent ĂȘtre prĂ©sentes dans un filtre seccomp.
đ SynthĂšse
Ce qui est pas mal quand on sâintĂ©resse Ă seccomp du point de vue dâun attaquant, câest que lâon a pas nĂ©cessairement besoin de comprendre comment lâimplĂ©menter en dĂ©tails.
En revanche, ce qui nous intĂ©resse est de savoir comment comprendre et lire le filtre seccomp et ce que cela implique en termes de protection dans un programme. Ăa tombe bien ! Voyons dĂ©sormais comment tirer profit des vulnĂ©rabilitĂ©s prĂ©sentes dans un filtre seccomp.