About
RSS

Bit Focus


越俎代庖 - 文件系统权限设置与进程的用户身份

Posted at 2013-05-11 15:49:33 | Updated at 2018-10-17 02:08:55

    首先提一个问题, 为何用 sudo 执行需要 root 权限的程序时, 输入的是当前用户的口令而不是 root 的?

    看一下 sudo 的 manpage, 描述是 "execute a command as another user". 注意并不一定是以 root 身份跑程序, 而是可以用任何其他身份. 这做好事不留名干坏事不怕差评利器真是太方便了. 比如, 偷偷输出某个用户的 SSH RSA 私钥之类的. 在桌面机上很少有人会建多个用户, 那么现在创建一个 (建议接下来的试验都新建用户进行, 避免遗留安全隐患; 另外, 当前用户必须已经被添加到 sudo 白名单中)
sudo useradd -m furude
sudo passwd furude # 这一步最好设置一个与当前用户不同的口令
su - furude
furude $
    这样切换到试验用户身份, 然后创建 ssh key, 并确认生成的密钥
ssh-keygen
Generating public/private rsa key pair.

Enter file in which to save the key (/home/furude/.ssh/id_rsa): Created directory '/home/furude/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/furude/.ssh/id_rsa.
Your public key has been saved in /home/furude/.ssh/id_rsa.pub.
The key fingerprint is:
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
The keys randomart image is:
+--[ RSA 2048]----+
| ............... |
+-----------------+
ls .ssh
id_rsa id_rsa.pub
    现在另开一个 shell, 以非 furude 身份登入, 执行
ls /home/furude/.ssh
会看到错误, 不允许列举该目录. 这是当然了, 要是连密钥都能随便看, 就没什么安全感了.

    下面就轮到 sudo 登场了. 还是当前用户身份登入, 执行
sudo ls /home/furude/.ssh
id_rsa id_rsa.pub
sudo -u furude ls /home/furude/.ssh
id_rsa id_rsa.pub
sudo -u `whoami` ls /home/furude/.ssh
ls: cannot open directory /home/furude/.ssh/: Permission denied
    第一个是以 root 身份执行, root 最大嘛, 所以当然可以毫无压力偷看一切文件或目录了; 接下来 -u furude 参数则是指定以 furude 这个用户身份执行, 自己当然可以看自己的东西了; 最后一个则仍然是以当前用户身份执行, 所以被驳回了.
    在上面试验中, 即使以 furude 的身份使用 sudo, 要求输入的口令仍然是当前用户的口令而不是 furude 用户的 (最好新开一个终端试试, 连续使用 sudo 时口令会被缓存下来). 那么, 开头的问题应该是这样的
    这个问题的答案不在 sudo 的源代码中, 而在于文件系统对文件权限设置的一个不常见设置. 一般在谈到文件权限设置时, 都会说到如 751 或者 644 这样的选项, 对应的权限分别是 rwxr-x--xrw-r--r--, 详细的含义和构成这里就不讲了, 很多地方都有参考. 下面看看 sudo
ls -l `which sudo`
-rwsr-xr-x 2 root root /usr/bin/sudo*
    第一部分用户权限这里写的不是 `rwx` 而是 `rws`, 这个就略猎奇了. 在文件系统中, 用户权限中的可执行位 x 如果被标记为 s (含义是 "用户设置"), 则这个程序在启动时, 无论由谁启动, 将该进程对应的用户设置为该可执行文件的所有者.
    比如, 下面再来做个试验, 创建下面的 C 源代码文件 print_private_key.c
/* print_private_key.c */

#include <stdio.h>

int main(void)
{
    FILE* f = fopen("/home/furude/.ssh/id_rsa", "r");
    if (NULL == f) {
        fprintf(stderr, "Unable to open the file\n");
        return 1;
    }
    char buffer[81];
    while(fgets(buffer, 80, f) != NULL) {
        printf("%s", buffer);
    }
    return 0;
}
并以 furude 用户身份编译之
gcc print_private_key.c -o p.out
然后运行它.
    以 furude 的身份的话, 当然运行这个程序是没问题的. 但是, 如果以一般其它用户运行, 在打开文件那一步就会出错, 输出 "Unable to open the file" 然后灰溜溜地退出. 这一点当然毫不令人惊讶. 下面在编译生成的可执行程序上施加一点魔法 (以文件所有者 furude 的身份)
chmod 4751 p.out
    这个取值 4xxx 显得有点高调, 最前面那个 4 就表示对 "用户设置" 位置位. 现在看看 p.out, 它的权限是 -rwsr-x--x. 这时再随便找个用户运行 p.out (不会提示输入 furude 的口令) 都能看到 furude 用户的密钥.
    因此这是一件非常非常危险的事情!

    那么之前那个问题一般到此解答了, 为什么运行 sudo 不用输入假身份的口令, 但还没解决的是为何要输入真身的口令.
    实际上, 任何普通用户在都能执行 sudo, 启动 sudo 进程后, 其身份已然是 root 用户, root 想看谁想删谁想引爆电脑都可以. 只是在这之前, 要核实一下当前用户是否在白名单里, 并且要求用户输入一次口令. 这个检查的操作要借助一个系统调用 getuid 来完成; 而说到这个 API 又不得不说另一个与之类似的 geteuid (这俩好基友在 manpage 里面都是占一个坑的). 保存下面的源代码
#include <stdio.h>
#include <stdlib.h>
#include <pwd.h>

int main(void)
{
    uid_t uid = geteuid();
    struct passwd* pw = getpwuid(uid);
    printf("Efficient user: %s\n", pw->pw_name);

    uid = getuid();
    pw = getpwuid(uid);
    printf("Real user: %s\n", pw->pw_name);

    return 0;
}
    然后编译之, 并将生成的可执行文件按照之前一样的方式置上 "用户设置" 位. 以任何用户身份执行这个程序, 有效用户都是 furude, 而真实用户则显示了实际执行程序的用户. 这也就是为何 sudo 能判断出到底是谁在执行, 该输入谁的口令的方法.

Post tags:   C  文件系统  进程  权限

Leave a comment:




Creative Commons License Your comment will be licensed under
CC-NC-ND 3.0


. Back to Bit Focus
NijiPress - Copyright (C) Neuron Teckid @ Bit Focus
About this site