Notes for PAM

网上大部分的教程都是介绍完 auth/account/password/session 就没了,参考文末列出的参考资料,总结出如下内容,其中前面的基础带过,重点说后面。

基础

PAM 模块存放位置 /lib64/security/,32 位的是 /lib/security/

很早之前的一个文件包揽全部的 /etc/pam.conf 没有在 5.5 下看到,取而代之的是以目录为结构的配置方式,这个跟 apache/nginx 等类似,为 /etc/pam.d/ 下。在该目录下的配置文件会一行一行的列出需要实现的策略,称之为 stack。

/etc/security/ 目录是某些对应模块的配置文件,比如 pam_time.so 模块对应的就是 time.conf;pam_env.so 的则是 pam_env.conf 文件。

查看程序是否支持 PAM:

# ldd /fullpath/cmd | grep libpam.so

注意:ldd 后面接绝对路径,现在常见的 ssh,vsftp 等支持。

在 /etc/pam.d/ 目录下的文件,一条完整的语句结构如下:

TYPE    CONTROL    MODULE_PATH    MODULE_ARGS

重点说说前两栏。

TYPE

auth
检查用户的真实性,一般通过查询其密码。高级的可以是智能卡,指纹等等。

account
检查用户是否有权限使用请求的服务。比如,确保没有用户能够使用一个过期的帐号进行登陆。

password
这类模块负责认证 token 的修改,通常指的是修改密码。

session
负责用户的会话,通常在用户登陆之前记录用户的登陆日志,登陆之后负责配置用户的环境变量等工作。

CONTROL

required
模块必须成功;如果 required 模块返回的值为失败,那么整个 stack 最终会失败,但是要等待 required 下的模块全部执行完之后。即使在 stack 里的其余的所有模块都执行成功,一个 required 模块的失败意味着整个操作的失败。

requisite
模块必须成功;如果 requisite 模块执行失败,整个 stack 过程不但失败,而且是立即失败,即不会再往下执行其他的模块。这个是跟 required 的区别所在。

sufficient
如果一个 sufficient 模块成功,该接下来的被列为 sufficient 模块就不会执行了;如果失败,如果接下执行的模块有成功整个 stack 仍然会成功。注意如果一个 required 的模块在 sufficient 前面,并且前者失败,后者成功,则 stack 还失败。

optional
模块可以成功,也可以失败,PAM 根据模块是否最终成功返回 success 或 failure。

MODULE_PATH   

模块的位置,默认是 /lib64/security/,在该目录下的不需要写绝对路径,如果在其他路径,需注明绝对路径。

MODULE_ARGS

附加的参数

进阶

顺序问题

在 PAM 中顺序问题十分重要,结合上面,我们来看看下面的这段:

auth     required     pam_moduleA
auth     sufficient        pam_moduleB
auth     required     pam_moduleC

这段 stack 的如果要执行通过的话,有两种情况:1) moduleA 和 moduleB 都通过;2)moduleA 和 moduleC 都通过,此时也就是在执行到 moduleB 时失败了。

换一下顺序

auth     sufficient        pam_moduleB
auth     required     pam_moduleA
auth     required     pam_moduleC

此 stack 要通过的话:1) moduleB 通过;2) moduleA 和 moduleC 都通过。

system-auth

不知道大家有没有看下 pam.d/ 下的文件,在该目录下几乎每一个文件都有一行

auth            include         system-auth

在 CONTROL 下,除了上述的 required,sufficient,requisite 外,还有一个就是 include 了,顾名思义,就是要将后面的 system-auth 包含进来了,我们在同样的目录下再看看这个 system-auth:

auth        required      pam_env.so
auth        sufficient    pam_unix.so nullok try_first_pass
auth        requisite     pam_succeed_if.so uid >= 500 quiet
auth        required      pam_deny.so

account     required      pam_unix.so
account     sufficient    pam_localuser.so
account     sufficient    pam_succeed_if.so uid < 500 quiet
account     required      pam_permit.so

有点像上面这样(版本不同,最后结果不一定完全一样)。

这里面每一个模块的含义请查阅 kernel.org 的文档,点击这里获取每一个模块表示的含义。

该 system-auth 策略通常是用来验证用户运行命令时输入的有效密码的(比如你运行 nmap 这个工具,如果是 root 的话可以直接运行而不必输入密码;但是如果是一个普通用户,你必须要提供密码才能运行)。

先来看看第一个四行提供的模块的含义:
pam_env.so:设置/取消环境变量。通常是用来设置 DISPLAY 变量的。/etc/security/pam_env.conf 文件指定了需要设置的环境变量,用的不是很多。

pam_unix.so: 这个是传统的密码验证模块,也就是 /etc/passwd,/etc/shadow 文件。如果出现在 account 栏,该模块会根据某些规则来确认用户的账户是否过期等问题;如果是在 auth 栏,它负责验证用户的用户名和密码;如果出现在 password 栏,它会负责更新用户的密码;跟 session 搭配则会记录用户登陆/注销日志。该模块的选项很多,具体的请参见官方。

pam_succeed_if.so:测试用户的特征。根据用户所属的账户的特征来决定认证的成功与否。

pam_deny.so:拒绝认证,pam.d/ 下的 other 就是用的这个模块,大家意会下一般 iptables 默认全部 DROP 的规则。

requisite 这个 CONTROL 表示,如果失败就会停止下面的认证,所以是 required 的“失败快速版本”。

注意:pam_unix.so 模块使用标准的系统调用来验证用户和密码。在现代的系统上,系统调用可以根据 /etc/nsswitch.conf 文件使用不同的用户数据库。所以如果你要使用 NIS,你可以使用 pam_nis.so 模块或者使用 pam_unix.so 模块,然后更改 nsswitch.conf 为 NIS。殊途同归!

auth 段表达的意思如下:如果用户输入有效的密码或者用户不是系统用户(UID <= 500 的为系统用户),可以继续接下来的 account。

模块在不同的 TYPE 下有不同的含义,以 pam_unix.so 为例,在 auth 下负责检查用户及密码,而到了 account 则是检查帐号是否过期等问题。

对于 account 段来说,第一行表示必须为一个有效的用户(没有过期)的用户,第二句表示如果用户在本地的 passwd 和 shadow 下是 OK 的;第三行表示如果是系统用户也是 OK 的。最后一行便是永远允许,基本上没什么用。

hwbrowser

下面我们看一下 hwbrowser 这个工具的 PAM 配置,cat 发现 auth,account,session 全部 include 到了 config-util 这个文件了,再看看 pam.d/ 下的 config-util:

#%PAM-1.0
auth            sufficient      pam_rootok.so
auth            sufficient      pam_timestamp.so
auth            include         system-auth
account         required        pam_permit.so
session         required        pam_permit.so
session         optional        pam_xauth.so
session         optional        pam_timestamp.so

前三行应该很好理解了。
第一行表示如果模块通过了,也就是用户是 root,那么接下来的模块就不会再检验了;如果不是 root,PAM 会继续检验接下来的模块来决定是否通过。
第 二行,在 kernel.org 上没有查阅到,那就 man timestamp 看看。一般你输入密码运行某个命令后,系统会帮助你保存一段时间(默认是 5 分钟),在这 5 分钟之内如果还要运行该命令的话就不需要你输入密码了,最典型的就是 sudo 了。而这个模块就是负责时间戳问题的。
第三行,如果通过了 pam.d/ 下 system-auth 中的所有模块则予以通过。

注意:每次运行 authconfig 命令都会覆盖该文件。

“# This file is auto-generated.
# User changes will be destroyed the next time authconfig is run.”

所以每次运行该命令前请先备份。

account 段中的 pam_permit.so 模块永远返回成功也就是允许通过,没有实际意义。
session 段一般用作记录日志,没有他对程序的影响不会有太大的影响。

合起来讲就是用户可以使用 hwbrowser 命令,在什么情况下了?UID 为 0 也就是 root,或者该用户在默认的时间戳内验证过,或者用户通过了 system-auth 里面的所有规则认证。如果上面一个都不符合,禁止使用。

继续看前三行,如果将 sufficient 改成 required 会怎么样了?现在就只有 root 有权限执行了。如果第一行失败,PAM 会继续 pam_timestamp 以及 system-auth,这个也是 required 跟 requisite 不同。

注释掉二,三两行是可以之让 root 使用的,不过最好的方式是将 sufficient 改成 required 并且注释掉上面的两行。

other

有人错误的认为可以在诸如 /etc/default/login,/etc/login.defs 这样的文件中设置密码的长度,但是修改修改这些文件大部分情况下并没有效果。这取决于你使用的 *nix 的版本, passwd 命令的版本以及你使用的数据库类型(shadow,NIS 等)。最有效的控制密码策略的方法就是使用 PAM。如果你在系统的某个文件中设置了密码长度的策略并且也有效了,那么这时候该文件和 PAM 可能会发生冲突,或许设置的密码要同时满足二者才可以继续下去。所以最好的办法就是只修改 PAM。

当修改密码时,password 模块决定了密码的策略,比如使用的数据库(/etc/shadow,RADIUS,LDAP 等),一些约束密码的条件(最短长度,密码中字符串的要求等)。auth 仍然会使用因为要确认当前用户有权限修改密码,真正起作用的是 password 模块。

# cat passwd 发现还是 system-auth,回到了上面讲的。而整个 system-auth 中起核心作用的就是 cracklib 模块了,他控制着密码的诸如最短长度等特性。接下来就是 RTFM 了。

在不同的系统上,即使模块的名字相同,他们的工实际表达内容也可能不同,所以修改 PAM 前有必要检查文档确认 PAM 文件。

在基础模块 pam_unix 中,accout 这种类型检查帐号的存在以及是否过期等问题,但是不会检查该账户是否被锁定或者该 shell 是否有效。这也就意味着,只要用户跟服务器是以公钥来登陆的,即使使用 passwd -l 来锁定用户,用户依然可以登陆!所以你如果是想通过这种方法来锁定用户是不可行的。可以通过修改 login 和 sshd 的 PAM 文件来达到目的。

实战

验证上述 ssh

添加实验用户 test

# useradd test
# passwd test

输入密码,登陆 OK

# ssh test@localhost

锁定帐号,再次输入密码,一直 deny

# passwd -l test

解锁帐号

# passwd -u test

使用公钥方式登陆,OK

# ssh-copy-id -i .ssh/id_rsa.pub test@localhost
# ssh test@localhost

再次锁定账户,公钥登陆,依然可登陆

# passwd -l test
# ssh test@localhost

限制 ssh 登陆

注意:修改 pam.d/ 下的文件前注意备份。

比较简单的方式是使用 pam_access.so 模块

# cat /etc/pam.d/sshd
#%PAM-1.0
auth       include      system-auth
account    required     pam_nologin.so
account    include      system-auth
password   include      system-auth
session    optional     pam_keyinit.so force revoke
session    include      system-auth
session    required     pam_loginuid.so
account    required     pam_access.so

在 /etc/security/access.conf 下加入:

-:test:ALL

# ssh test@localhost
test@localhost's password:
Connection closed by 127.0.0.1

上面就禁止 test 用户从任何 ip 访问主机,如果想让用户在本地使用 ssh:

-:test:ALL EXECPT 127.0.0.1

还可以使用到的是 /lib64/security/ 下的 pam_listfile.so 模块。如果用户名出现在 /etc/deny 里面,sshd 就禁止该用户的访问。

# cat /etc/pam.d/sshd
#%PAM-1.0
auth       required     pam_listfile.so item=user sense=deny    file=/etc/deny onerr=succeed
auth       include      system-auth
account    required     pam_nologin.so
account    include      system-auth
password   include      system-auth
session    optional     pam_keyinit.so force revoke
session    include      system-auth
session    required     pam_loginuid.so

# ssh test@localhost
test@localhost's password:
Permission denied, please try again.

如果只允许特定的用户登陆,将第一句改成:

auth       required     pam_listfile.so item=user sense=allow    file=/etc/allow onerr=fail

将允许登陆的用户加入 /etc/allow 中

资源分配

pam_limits.so 控制资源的分配,还是以 ssh 为例,通过添加 pam_limits.so 模块来控制用户最大登陆数。

# cat /etc/pam.d/sshd
#%PAM-1.0
#auth      required     pam_listfile.so item=user sense=deny    file=/etc/deny onerr=succeed
auth       include      system-auth
account    required     pam_nologin.so
account    include      system-auth
password   include      system-auth
session    optional     pam_keyinit.so force revoke
session    include      system-auth
session    required     pam_loginuid.so
session    required     pam_limits.so

在 /etc/security/limits/conf 下添加

test    hard    maxlogins    2

在一个,第二个终端上 ssh 没问题,但是到了第三终端:

# ssh test@202.*.*.*
test@202.*.*.*'s password:
Too many logins for 'test'.
Too many logins for 'test'.
Last login: Sat Apr 23 20:12:53 2011 from 10.20.133.203
Connection to 202.*.*.* closed.

除了可以限制登陆,还可以防止恶意程序,比如这个:

: () { : | : & } ; :

默认情况下 pam.d/login 下应该有这行:

session    required     pam_loginuid.so

需要对普通用户(root 无效)的使用进程数进行控制,还是使用 pam_limits.so 模块,修改 limits.conf 文件,添加:

test    hard    nproc    32

32 的意思就是 test 用户最多能 fork 32 个进程

ps axu | grep script | grep -v grep | wc -l <= 32

上面列举的只是一些常用的手法,更多的需要在实际应用的等待发掘。

参考:
http://www.kernel.org/pub/linux/libs/pam/Linux-PAM-html/Linux-PAM_SAG.html
http://content.hccfl.edu/pollock/AUnix2/PAM-Help.htm
http://www.vpsee.com/2010/09/limit-linux-user-process/