Cela fonctionne sur OpenBSD
Comme déjà mentionné dans un commentaire de @eradman, cela est possible sur OpenBSD.
En tant que root:
hzy# cat <<'EOT' >/tmp/foo; chmod 001 /tmp/foo
#! /bin/sh
: this is secret
echo done
EOT
En tant qu'utilisateur régulier:
hzy$ cat /tmp/foo
cat: /tmp/foo: Permission denied
hzy$ /tmp/foo
done
Cela fonctionne en passant /dev/fd/3
(ou quel que soit le fd ouvert au script) à l’interprète. Cette astuce ne fonctionnerait pas sous Linux, où il /dev/fd/N
n’ya pas de périphériques à caractères spéciaux qui renvoient un dup(2)
fd lorsqu’ils sont ouverts, mais des liens symboliques "magiques" vers le fichier / dentry original, qui ouvre le fichier à partir de zéro [1]. Cela pourrait être implémenté dans Free / NetBSD ou Solaris ...
Mais ce n'est pas ce que ça craint d'être
Donner l’ x
autorisation (exécuter), c’est aussi donner l’ r
autorisation (lire) sur tout fichier qui a un shebang [2]:
hzy$ cat /tmp/foo
cat: /tmp/foo: Permission denied
hzy$ ktrace -ti /tmp/foo
done
hzy$ kdump | tail -n8
70154 sh GIO fd 10 read 38 bytes
"#! /bin/sh
: this is secret
echo done
"
70154 sh GIO fd 1 wrote 5 bytes
"done
ktrace
n'est pas le seul moyen; si l'interpréteur est un exécutable lié dynamiquement comme perl
ou python
, un LD_PRELOAD
hack ed qui remplace la read(2)
fonction pourrait être utilisé à la place.
Et non, le rendre setuid n'empêchera pas un utilisateur régulier de voir son contenu; elle pourrait simplement l'exécuter sous ptrace(2)
, ce qui ferait ignorer les bits setuid:
En tant que root:
hzyS# cat <<'EOT' >/tmp/bar; chmod 4001 /tmp/bar
#! /bin/sh
: this is secret
id
EOT
En tant qu'utilisateur régulier:
hzyS$ ktrace -ti /tmp/bar
uid=1001(duns) euid=0(root) gid=1001(duns) groups=1001(duns)
hzyS$ kdump
... nothing, the kernel disabled the ktrace ...
hzyS$ cc -Wall -xc - -o pt <<'EOT'
#include <unistd.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <signal.h>
int main(int ac, char **av){
int s; pid_t pid;
if((pid = fork()) == 0){
ptrace(PT_TRACE_ME, 0, 0, 0);
execvp(av[1], av + 1);
}
while(wait(&s) > 0 && WIFSTOPPED(s)){
s = WSTOPSIG(s);
ptrace(PT_CONTINUE, pid, (caddr_t)1, s == SIGTRAP ? 0 : s);
}
}
EOT
hzyS$ ./pt ktrace -ti /tmp/bar
uid=1001(duns) gid=1001(duns) groups=1001(duns)
hzyS$ kdump | tail -5
29543 sh GIO fd 10 read 31 bytes
"#! /bin/sh
: this is secret
id
"
(désolé si ce n'est pas la façon la plus directe de le démontrer)
[1] ceci pourrait être émulé sur Linux en utilisant binfmt_misc
, mais l'interpréteur devra être modifié ou un wrapper devra être utilisé; Voir la dernière partie de cette réponse pour un exemple délibérément ridiculement incertain.
[2] ou en général, tout fichier qui ne fera pas execve()
revenir ENOEXEC
.