Funciona en OpenBSD.
Como ya se mencionó en un comentario de @eradman, esto es posible en OpenBSD.
Como root:
hzy# cat <<'EOT' >/tmp/foo; chmod 001 /tmp/foo
#! /bin/sh
: this is secret
echo done
EOT
Como usuario habitual:
hzy$ cat /tmp/foo
cat: /tmp/foo: Permission denied
hzy$ /tmp/foo
done
Eso funciona al pasar /dev/fd/3
(o lo que sea el fd abierto al script) al intérprete. Ese truco no funcionaría en Linux, donde /dev/fd/N
no hay dispositivos de caracteres especiales que devuelven una dup(2)
de las fd cuando se abren, sino enlaces simbólicos "mágicos" al archivo / dentry original, que abren el archivo desde cero [1]. Se podría implementar en Free / NetBSD o Solaris ...
Pero no es lo que parece ser
Básicamente, otorgar el x
permiso (ejecutar) significa también otorgar el r
permiso (lectura) en cualquier archivo que tenga 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
no es el único camino; si el intérprete es ejecutable vinculado dinámicamente como perl
o python
, en su lugar, se podría usar un LD_PRELOAD
hack ed que anula la read(2)
función.
Y no, hacer que sea setuid no impedirá que un usuario normal vea su contenido; ella simplemente podría ejecutarlo ptrace(2)
, lo que hará que los bits setuid sean ignorados:
Como root:
hzyS# cat <<'EOT' >/tmp/bar; chmod 4001 /tmp/bar
#! /bin/sh
: this is secret
id
EOT
Como usuario habitual:
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
"
(perdón si esta no es la forma más directa de demostrarlo)
[1] esto podría emularse en Linux mediante el uso binfmt_misc
, pero el intérprete tendrá que modificarse o se deberá usar un contenedor; vea la última parte de esta respuesta para ver un ejemplo deliberadamente hecho ridículamente inseguro.
[2] o en general, cualquier archivo que no hará que se execve()
devuelva ENOEXEC
.