我正在 RedHat 7.5 上测试 PAM 场景
pam 模块 pam_succeed_if.so 看起来像是 PAM 必须提供的最基本级别的条件测试,但它不能满足我的需求。您只能在 user、uid、gid、shell、home、ruser、rhost、tty 和 service 字段上创建测试。
在我的情况下,我确实想基于“rhost”字段进行测试,但是将模块投入调试后,我发现 rhost 字段未设置。
我的目标是只运行 PAM 模块/etc/pam.d/sudo如果用户在本地登录到计算机。如果我们可以检测到用户是通过 SSH 登录的,那么我想跳过该模块。我实际上提出了 3 个我认为可行的不同想法,但最终都失败了。
我将分享一些最终失败的解决方案
使用 pam_exec.so 进行条件输入
我想在我想有条件跳过的 pam 模块顶部添加以下 pam 条目:
auth [success=ok default=1] pam_exec.so /etc/security/deny-ssh-user.sh
/etc/security/deny-ssh-user.sh 的内容
#!/bin/bash
# Returns 1 if the user is logged in through SSH
# Returns 0 if the user is not logged in through SSH
SSH_SESSION=false
if [ -n "${SSH_CLIENT}" ] || [ -n "${SSH_TTY}" ] || [ -n "${SSH_CONNECTION}" ]; then
SSH_SESSION=true
else
case $(ps -o comm= -p $PPID) in
sshd|*/sshd) SSH_SESSION=true;;
esac
fi
if "${SSH_SESSION}"; then
exit 1
else
exit 0
fi
我已经浏览了 pam_exec.so 的源代码https://github.com/linux-pam/linux-pam/blob/master/modules/pam_exec/pam_exec.c令人惊讶的是,无论脚本的退出代码如何,它总是返回 PAM_SUCCESS。我无法让脚本导致 pam_exec 模块返回 PAM_SERVICE_ERR、PAM_SYSTEM_ERR 或 PAM_IGNORE。
使用 pam_access.so 进行条件输入
再次,我在要有条件跳过的 pam 模块顶部添加以下 pam 条目
auth [success=ok perm_denied=1] pam_access.so accessfile=/etc/security/ssh-sudo-access.conf noaudit
/etc/security/ssh-sudo-access.conf 的内容
+:ALL:localhost 127.0.0.1
-:ALL:ALL
哇,超级干净吧?如果您在本地登录,它将返回成功,并拒绝其他一切。好吧,不。事实证明,当 pam_access.so 模块投入调试时,它不知道远程主机,只知道正在使用的终端。因此 pam_access 实际上无法阻止远程主机的访问。
这是令人气愤的一天,我从字面上阅读源代码,只是为了找出我必须施放什么黑魔法,这样我才能跳过 PAM 模块。
答案1
这是一个适合我的解决方案。我的/etc/pam.d/sudo
:
#%PAM-1.0
auth [success=1] pam_exec.so /tmp/test-pam
auth required pam_deny.so
auth include system-auth
account include system-auth
session include system-auth
和/tmp/test-pam
:
#! /bin/bash
/bin/last -i -p now ${PAM_TTY#/dev/} | \
/bin/awk 'NR==1 { if ($3 != "0.0.0.0") exit 9; exit 0; }'
我得到这种行为:
$ sudo date
[sudo] password for jdoe:
Thu Jun 28 23:51:58 MDT 2018
$ ssh localhost
Last login: Thu Jun 28 23:40:23 2018 from ::1
valli$ sudo date
/tmp/test-pam failed: exit code 9
[sudo] password for jdoe:
sudo: PAM authentication error: System error
valli$
第一行添加到默认pam.d/sudo
调用中pam_exec
,如果成功,则跳过下一个条目。第二行只是无条件拒绝访问。
在/tmp/test-pam
我调用中last
获取与调用 TTY pam 关联的 IP 地址。从值的前面${PAM_TTY#/dev/}
删除,因为无法识别完整的设备路径。该标志使显示 IP 地址或占位符(如果没有 IP 地址);默认情况下,它显示一个更难检查的信息字符串。这也是我使用or代替的原因;那些没有类似的选项。该选项并不是绝对必要的,因为我们将看到仅检查输出的第一行,但它限制为仅显示当前登录的用户。/dev/
last
-i
last
0.0.0.0
last
who
w
-p now
awk
last
该awk
命令仅检查第一行,如果第三个字段不是,0.0.0.0
则退出并出现错误。由于这是 中的最后一个命令/tmp/test-pam
,因此 awk 的退出代码将成为脚本的退出代码。
在我的系统上,您尝试的所有测试都不起作用deny-ssh-user.sh
。如果您将其放在env > /tmp/test-pam.log
脚本的顶部,您将看到环境已被删除,因此不会设置任何 SSH_FOO 变量。 $PPID 可以指向任意数量的进程。例如,运行perl -e 'system("sudo cat /etc/passwd")'
并查看 $PPID 引用该perl
进程。
这是 Arch Linux,内核4.16.11-1-ARCH
,以防万一。但我认为不应该。
答案2
事实证明我实际上是个白痴,该pam_exec.so
模块非常适合创建 PAM 条件。
蒂姆·史密斯(Tim Smith)的评估是正确的,我的/etc/security/deny-ssh-user.sh
脚本中的两个测试从未将变量设置SSH_SESSION
为 true。我没有考虑到这一点,因为该脚本在普通 shell 中工作,但在pam_exec.so
.
我最终重写了脚本以使用该last
实用程序,就像他的示例一样,但是我必须更改其中一些内容,因为last
从 Arch Linux 到 RedHat 的开关不同。
以下是 /etc/security/deny-ssh-user.sh 中修改后的脚本:
#!/bin/bash
# Returns 1 if the user is logged in through SSH
# Returns 0 if the user is not logged in through SSH
SSH_SESSION=false
function isSshSession {
local terminal="${1}"
if $(/usr/bin/last -i |
/usr/bin/grep "${terminal}" |
/usr/bin/grep 'still logged in' |
/usr/bin/awk '{print $3}' |
/usr/bin/grep -q --invert-match '0\.0\.0\.0'); then
echo true
else
echo false
fi
}
function stripTerminal {
local terminal="${1}"
# PAM_TTY is in the form /dev/pts/X
# Last utility displays TTY in the form pts/x
# Returns the first five characters stripped from TTY
echo "${terminal:5}"
}
lastTerminal=$( stripTerminal "${PAM_TTY}")
SSH_SESSION=$(isSshSession "${lastTerminal}")
if "${SSH_SESSION}"; then
exit 1
else
exit 0
fi
/etc/pam.d/sudo 的内容
....
auth [success=ok default=1] pam_exec.so /etc/security/deny-ssh-user.sh
auth sufficient pam_module_to_skip.so
....