Работает на OpenBSD
Как уже упоминалось в комментарии @eradman, это возможно в OpenBSD.
Как корень:
hzy# cat <<'EOT' >/tmp/foo; chmod 001 /tmp/foo
#! /bin/sh
: this is secret
echo done
EOT
Как обычный пользователь:
hzy$ cat /tmp/foo
cat: /tmp/foo: Permission denied
hzy$ /tmp/foo
done
Это работает путем передачи /dev/fd/3
(или чего бы то ни было открытого сценария для скрипта) интерпретатору. Этот трюк не сработает в Linux, где /dev/fd/N
используются не специальные символьные устройства, которые возвращают dup(2)
fd при открытии, а «магические» символические ссылки на оригинальный файл / dentry, которые открывают файл с нуля [1]. Это может быть реализовано в Free / NetBSD или Solaris ...
Но дело не в том, что
По сути, предоставление x
разрешения (execute) означает также предоставление разрешения r
(read) для любого файла, который имеет 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
не единственный путь; если интерпретатор является динамически связанным исполняемым файлом, например, perl
или python
, вместо этого можно использовать LD_PRELOAD
хак ed, который переопределяет read(2)
функцию.
И нет, установка setuid не помешает обычному пользователю увидеть его содержимое; она может просто запустить его ptrace(2)
, что приведет к игнорированию битов setuid:
Как корень:
hzyS# cat <<'EOT' >/tmp/bar; chmod 4001 /tmp/bar
#! /bin/sh
: this is secret
id
EOT
Как обычный пользователь:
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
"
(извините, если это не самый простой способ продемонстрировать это)
[1] это можно было бы эмулировать в Linux с помощью binfmt_misc
, но интерпретатор нужно будет изменить или использовать оболочку; см. последнюю часть этого ответа для примера, намеренно сделанного до смешного небезопасным.
[2] или вообще любой файл, который не вызовет execve()
возврата ENOEXEC
.