如何通过多个sudo和su命令找到原始用户?

104

在通过 sudo 或 su 运行脚本时,我想要获取原始用户。这应该发生在每个其他 sudosu 的运行内部,特别是在 sudo su - 中。

10个回答

148

结果:

使用who am i | awk '{print $1}'logname,因为其他方法不能保证。

已登录为自己:

evan> echo $USER
evan
evan> echo $SUDO_USER

evan> echo $LOGNAME
evan
evan> whoami
evan
evan> who am i | awk '{print $1}'
evan
evan> logname
evan
evan>

普通的sudo:

evan> sudo -s
root> echo $USER
root
root> echo $SUDO_USER
evan
root> echo $LOGNAME
root
root> whoami
root
root> who am i | awk '{print $1}'
evan
root> logname
evan
root>

sudo su - :

evan> sudo su -
[root ]# echo $USER
root
[root ]# echo $SUDO_USER

[root ]# echo $LOGNAME
root
[root ]# whoami
root
[root ]# who am i | awk '{print $1}'
evan
[root ]# logname
evan
[root ]#

sudo su -; su tom :

evan> sudo su -
[root ]# su tom
tom$ echo $USER
tom
tom$ echo $SUDO_USER

tom$ echo $LOGNAME
tom
tom$ whoami
tom
tom$ who am i | awk '{print $1}'
evan
tom$ logname
evan
tom$

1
在这种情况下,您可以只使用 who | awk '{print $1}' - SiegeX
3
如果您是唯一登录的人(且这只发生一次)。 - Dennis Williamson
9
你只需要2个参数:who am iwho smells bad是相同的。但是,仅当STDIN与TTY相关联时它才能起作用。因此,如果你运行echo "hello" | who am i,它就不会起作用。 - tylerl
1
通常情况下,您不会运行echo "hello" | who am i,除非您的脚本在没有终端的环境中运行。然后,您可能会看到错误,即who am i无法工作,因为非可读stdin存在某种问题,在这种情况下,您可能会尝试通过将数据管道传输到who am i来满足其stdin要求。tylerl只是指出他已经走过了那条路,而管道不起作用,因为stdin必须既可读又与TTY相关联。 - Edwin Buck
4
即使如此,我希望它需要尽可能少的配置,因此现在我正在使用 logname,结果证明它确实有效,而 who am i 则不行。 - Bart van Heukelom
显示剩余4条评论

21

没有一个完美的答案。当您更改用户ID时,原始用户ID通常不会保留,因此信息会丢失。一些程序,例如lognamewho -m实现了一个hack,它们检查连接到stdin的终端,然后检查在该终端上登录的用户。

这个解决方案经常有效,但并非绝对可靠,肯定不应被视为安全。例如,想象一下如果who输出以下内容:

tom     pts/0        2011-07-03 19:18 (1.2.3.4)
joe     pts/1        2011-07-03 19:10 (5.6.7.8)

tom使用su来获取root权限,并运行您的程序。如果STDIN未重定向,则像logname这样的程序将输出tom。如果它已被重定向(例如从文件中),则会发生以下情况:

logname < /some/file

那么结果就是“没有登录名”,因为输入不是终端。更有趣的是,用户可以冒充另一个已登录的用户。由于Joe在pts/1上登录,Tom可以通过运行来冒充他。

logname < /dev/pts1

现在,它显示joe,尽管是tom运行了命令。换句话说,如果你在任何安全角色中使用这种机制,那就疯了。


2
如果你自己运行脚本(如所使用的命令所示),那么安全性就不是问题。 如果是这样,你会有更多的问题,因为他们也具有sudo访问权限。 这个人可以随便复制脚本并修改它。 这只是一种获取用于脚本中的登录名的方法。或者我对你说的话有什么误解吗? - evan
1
@evan:拥有sudo访问权限并不意味着可以覆盖文件。 - Jonathan Hall
@Flimzy 在什么情况下,根用户没有覆盖文件的能力? - evan
1
@evan:当您的sudo访问权限无法访问shell或任何其他可以覆盖文件的命令时,显然是不行的。 - Jonathan Hall
@evan,sudo访问权限并不总是(在大多数管理员情况下也不应该是)完全的root访问权限。它是一组可配置的受限执行上下文。 - DylanYoung
@tylerl 尝试通过将其tty重定向到logname来模拟另一个用户,在我的CentOS 6.7上失败,显示“bash:/dev/pts/0:权限被拒绝”,这意味着从安全角度来看这是足够好的。而且CentOS 6.7已经过时了... 我有什么遗漏吗? - Irfy

8
如果我们能够将进程生成层次结构排列成一棵树,那么我们就可以在该树的根部查找生成该进程的用户。幸运的是,pstree 命令为我们完成了这种排列。
pstree -lu -s $$ | grep --max-count=1 -o '([^)]*)' | head -n 1 | sed 's/[()]//g'

pstree 展示了运行中的进程树。该树以一个pid为根,这里给出的是$$,在bash中扩展为当前shell的进程id。因此,命令的第一部分列出了当前shell的所有祖先进程,并带有一些有趣的格式。命令的其余部分丢弃了有趣的格式,以挑选出拥有最老的祖先进程的用户名称。

与其他基于pstree的答案相比,主要改进之处在于输出中不包含多余的括号。


2
不解释,并且只是最小限度地改进自现有答案 - sondra.kinsey

8
这是我在HP-UX上编写的ksh函数。我不知道它在Linux上的Bash中是否可用。这个想法是sudo进程以原始用户身份运行,子进程则是目标用户。通过循环回到父进程,我们可以找到原始进程的用户。
#
# The options of ps require UNIX_STD=2003.  I am setting it
# in a subshell to avoid having it pollute the parent's namespace.
#
function findUser
{
    thisPID=$$
    origUser=$(whoami)
    thisUser=$origUser
    while [ "$thisUser" = "$origUser" ]
    do
        ( export UNIX_STD=2003; ps -p$thisPID -ouser,ppid,pid,comm ) | grep $thisPID | read thisUser myPPid myPid myComm
        thisPID=$myPPid
    done
    if [ "$thisUser" = "root" ]
    then
        thisUser=$origUser
    fi
    if [ "$#" -gt "0" ]
    then
        echo $origUser--$thisUser--$myComm
    else
        echo $thisUser
    fi
    return 0
}

我知道原问题早已过去,但像我这样的人仍在提问,而这似乎是一个很好的解决方案所在之处。


6

可以使用 logname(1) 命令获取用户的登录名,你觉得怎么样?


logname(1)无效,但logname有效 - 将结果添加在上方 - evan
最初我尝试了$LOGNAME,但没有起作用。同时将其添加到上面的结果中。 - evan
logname 命令是否仍需要 tty?根据我的测试,它总是通过的。(也许我做错了什么。)我正在运行带有 coreutils 8.26 的 Linux。 - simohe
我的logname(GNU coreutils)8.28在always上返回“logname:no login name”(Ubuntu 18.04.2)。 - sondra.kinsey

6
在运行systemd-logind的系统上,Systemd API提供了这些信息。如果您想从shell脚本访问此信息,需要使用类似以下内容:
$ loginctl session-status \
  | (read session_id ignored; loginctl show-session -p User $session_id)
User=1000
< p > loginctlsession-statusshow-session 系统命令在没有参数的情况下具有不同的行为: session-status 使用当前会话,而 show-session 使用管理器。然而,由于其机器可读输出,对于脚本使用来说,使用 show-session 更可取。这就是为什么需要两次调用 loginctl 的原因。


3

将user1683793的findUser()函数移植到中,并扩展其功能,使其可以返回存储在NSS库中的用户名。

#!/bin/bash

function findUser() {
    thisPID=$$
    origUser=$(whoami)
    thisUser=$origUser

    while [ "$thisUser" = "$origUser" ]
    do
        ARR=($(ps h -p$thisPID -ouser,ppid;))
        thisUser="${ARR[0]}"
        myPPid="${ARR[1]}"
        thisPID=$myPPid
    done

    getent passwd "$thisUser" | cut -d: -f1
}

user=$(findUser)
echo "logged in: $user"

请注意:此功能(以及其基础功能)不会在sudo嵌套的多个外壳之间进行循环。 - asdfghjkl

2

多次调用ps的替代方案:只需进行一次pstree调用

pstree -lu -s $$ | grep --max-count=1 -o '([^)]*)' | head -n 1

输出(以even身份登录时):(evan)

pstree参数:

  • -l:长行(不缩短)
  • -u:显示用户名更改为(userName)
  • -s $$:显示此过程的父进程

使用grep -ohead获取第一个用户更改(即登录)。

限制:命令中不得包含任何大括号()(通常不会)


pstree -lu -s $$ | head -n1 | sed -e 's/[^(]*(([^)]))./\1/' - Alexx Roche

2

根据user1683793的回答,回溯并列出用户列表。

通过排除非TTY进程,我跳过了以root作为登录发起者的情况。但我不确定在某些情况下是否排除得太多。

#!/bin/ksh
function findUserList
{
    typeset userList prevUser thisPID thisUser myPPid myPid myTTY myComm
    thisPID=$$                 # starting with this process-ID
    while [ "$thisPID" != 1 ]  # and cycling back to the origin
    do
        (  ps -p$thisPID -ouser,ppid,pid,tty,comm ) | grep $thisPID | read thisUser myPPid myPid myTTY myComm
        thisPID=$myPPid
        [[ $myComm =~ ^su ]] && continue        # su is always run by root -> skip it
        [[ $myTTY == '?' ]] && continue         # skip what is running somewhere in the background (without a terminal)
        if [[ $prevUser != $thisUser ]]; then   # we only want the change of user
                prevUser="$thisUser"            # keep the user for comparing
                userList="${userList:+$userList }$thisUser"  # and add the new user to the list
        fi
        #print "$thisPID=$thisUser: $userList -> $thisUser -> $myComm " >&2
    done
    print "$userList"
    return 0
}

lognamewho am i都不能给我想要的答案,特别是在更长的su user1su user2su user3等列表中。

我知道这个问题很久以前就有了,但像我这样的人仍在询问,而这似乎是一个好地方来提供解决方案。


1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接