首页
社区
课程
招聘
苹果*OS认证系统简介(1/3)
发表于: 2018-1-29 10:53 5790

苹果*OS认证系统简介(1/3)

2018-1-29 10:53
5790

苹果*OS认证系统简介(一/三)

提出疑问

某日做可信认证登录功能的时候,遇到一个问题,怎样把自己制作的PAM模块加入到MacOS的登录流程中去,可以使用自己的PAM模块登录到MacOS,相信很多2B的企业会用到这个功能。

 

本质上微软的Active Directory域控制也是同样的功能,由于域控制太过于有名,所以苹果已经在用户与群组→网络账户服务器→加入界面集成了Active Deirectory,并且集成到了苹果自己的open directory服务中去了。

 

我们想要实现的这个功能,是要在苹果本身的PAM验证机制上,再加一层我们自己的PAM,我们的PAM模块也要验证通过,才可以进入MacOS系统。

 

这就涉及到了搞清楚苹果PAM模块认证的流程和机制等问题。

查找资料

Pluggable Authentication Module ,字面意思就是“插入式认证模块”,那么可拔插就是其基本特征,还有一个特征就是模块化,他是UN*X系统认证API的抽象化和模块化。有了PAM机制之后,我们不再受限于古老的/etc/passwd/etc/group,而是可以采用“可拔插”的第三方认证模块、甚至是外部模块,来执行和返回认证结果,并可以通过对模块的hook操作,进行日志记录、审计或者强制策略执行等任务。PAM机制最大的意义在于,通过配置认证文件的方式,引入单独的认证模块,解除了认证过程和认证逻辑的耦合,提高可用性、复用性和安全性。

 

虽然*OS系统中没有采用PAM机制,但是MacOS中大量应用了PAM机制。当然,不仅仅是MacOCLinuxDebianSolars等流行系统中都大量应用了PAM机制,PAM机制在这些系统中都是通用的,而且PAM的文档做的也特别好,大家可以去看它的man的page。

 

PAM模块非常简洁易用,从开发者的角度来说,只要在代码里调用PAMpam API就好了。开发者加入libpam.dylib的依赖之后,调用pam API,PAM的库文件就会找到其相应的PAM模块配置文件,然后加载配置文件里相应的认证库,认证库里就有回调函数,用来返回结果给PAM库。这样就实现了认证过程与认证逻辑的分离,认证逻辑是看不到认证过程的,我们可以用下图来描述一下这个过程。

 

 

/etc/pam.d/service是PAM的配置文件,配置文件里包含了认证规则,也就是authacctsessionpasswd等关键字。pam_xxx.so为认证库,这里才是真正的认证过程的实现。用户程序的认证过程通过PAM配置文件来查找认证过程,认证过程将结果返回给libpam.dylib,最终回到用户程序。

函数类

PAM的模块库会导出四种类型的API,也就是四种类型的函数类。

类型 NAME 类别 功能
类型一 auth 认证类API 提供认证函数,来对调用者的凭证进行认证,根据认证结果返回可供内核识别的UID值;
类型二 account 账户策略管理类API 提供用户策略管理和执行的相关函数;
类型三 session 会话控制类API 为认证通过的用户创建会话。在会话进行时,模块会提供可供PAM调用的回调函数,用来加载用户默认配置等功能;
类型四 password 凭证类API 提供凭证管理功能,用户可以创建、删除、修改凭证。凭证的范围不仅仅是指文字密码,还有证书等等。

流程控制语句

虽然对于PAM来说,验证模块内部的执行过程是不可见的,但是验证模块终归会有返回值。如果验证通过,返回值为0,如果不通过,则会有一个错误代码(error code)。有了错误代码,我们就可以与位运算(与、或、取反、异或,等等)相结合进行按位叠加,进行位运算的规则就由流程控制语句来决定。

语句 含义 功能
requisite 一票否决 如果该语句指定的模块返回错误码,则让PAM立刻停止运行,返回认证失败;
required 部分否决 如果该语句指定的模块返回错误码,虽然PAM还会继续运行剩余语句,但是仍会返回认证失败;
sufficient 立即通过 如果该语句指定的模块返回0(验证通过),则让PAM立刻停止运行,返回认证通过;
binding 部分通过 如果该语句指定的模块返回0(验证通过),虽然PAM还会继续运行剩余语句,但是仍会返回认证成功;
optional 无关紧要 该语句指定的模块不会对接下来的语句结果产生任何影响;
 

总结一下就是:

语句 requisited required optional binding sufficient
模块返回 失败 失败 任意结果 成功 成功
PAM运行状态 立即停止 不会停止 不会停止 不会停止 立即停止
PAM返回结果 失败 失败 取决剩余语句结果 成功 成功
 

将函数类和流程控制语句结合在一起,就是PAM的配置文件了。由于配置文件就是最普通的文本文件,(而不是花哨的XML或者json之流,PS:那个年代貌似也没有json等风骚的格式)因此非常易于阅读,笔者MacOS 10.12的配置文件是这样的:

$ cd /etc/pam.d

$ ls
authorization        authorization_la    cups            login.term        screensaver        screensaver_la        su
authorization_aks    checkpw            ftpd            other            screensaver_aks        smbd            sudo
authorization_ctk    chkpasswd        login            passwd            screensaver_ctk        sshd

$ cat login
# login: auth account password session
auth       optional       pam_krb5.so use_kcminit
auth       optional       pam_ntlm.so try_first_pass
auth       optional       pam_mount.so try_first_pass
auth       required       pam_opendirectory.so try_first_pass
account    required       pam_nologin.so
account    required       pam_opendirectory.so
password   required       pam_opendirectory.so
session    required       pam_launchd.so
session    required       pam_uwtmp.so
session    optional       pam_mount.so

/etc/pam.d文件夹里存在着颇多的配置文件,我们查看login这个配置文件,可见其每一行由函数类、流程控制语句和.so认证模块所组成。对的,你没看错,即使是在MacOSDarwin系统上,也是.so的格式,也就是说这些模块在UN*X系统上是同样可以运行的。虽然.so文件需要提供全路径,如果只提供文件名,则MacOS会默认去/usr/lib/pam文件夹去取.so文件。

总结

稍微总结一下MacOS中默认提供的那些.so模块文件,这些模块也都是开源的。

模块 提供的函数类 /etc/pam.d用户 功能
pam_aks.so.2 auth authorization_aks<br>screensaver_aks AppleKeyStore接口,目前暂未使用
pam.deny.so.2 all other 直接返回失败
pam_env.so.2 auth<br>session 设置(取消)环境变量
pam.group.so.2 account screensaver<br>su 组设定
pam_krb5.so.2 all authorization<br>login<br>sshd<br>screensaver Kerberos 5 接口<br>Active Directory接口
pam_launchd.so.2 session login<br>rshd<br>sshd<br>su launchd接口
pam_localauthorization.so.2 auth authorization_la<br>screensaver_la 本地认证
pam_mount.so.2 auth<br>session login<br>sshd 自动挂载磁盘、用户分区等
pam_nologin.so.2 account login<br>rshd<br>sshd 如果存在/etc/nologin文件,则直接返回失败
pam_ntlm.so.2 auth authorization<br>login<br>sshd Windows NTLM 接口
pam_opendirectory.so.2 auth<br>account<br>passwd authorization<br>checkpw,chkpasswd,cups<br>ftpd,login,passwd<br>rshd,screensaver<br>sshd,su,sudo opendirectory数据库接口
pam_permit.so.2 account<br>auth<br>session<br> cups,ftpd,su<br>passwd,rshd<br>chkpasswd,cups,ftpd<br>passwd,smbd,sudo 直接返回成功
pam_rootok.so.2 auth su 如果getuid==0则返回通过
pam_sacl.so.2 account smbd,sshd 服务访问控制列表
pam_self.so.2 account screensaver 验证目标账户名与程序用户名是否相同
pam_smartcard.so.2 auth authorization_ctk<br>screensaver_ctk 智能卡支持接口
pam_uwtmp.so.2 session login 将登陆记录写进utmpx数据库里
 

当然,我们还可以给验证模块来传递参数,参数写在.so文件的后面就好,当然这些参数都是随.so验证模块指定的,具体可以看man命令,那里有详细的文档。参数可以是uidgid、文件路径等等,我们用su命令的PAM模块来举个例子:

$ cat /etc/pam.d/su
# su: auth account session
auth       sufficient     pam_rootok.so
auth       required       pam_opendirectory.so
account    required       pam_group.so no_warn group=admin,wheel ruser root_only fail_safe
account    required       pam_opendirectory.so no_check_shell
password   required       pam_opendirectory.so
session    required       pam_launchd.so

我们从上往下看:

  • pam_rootok.soauthsufficient,按照字面意思就是认证立即通过的意思,也就是如果以root用户身份登陆,那么立即通过,没有运行后续语句的必要;
  • pam_opendirectory.sorequired是充分非必要条件,如果失败则整个结果都是失败。su命令会把密码传输给pam_opendirectory模块,如果失败,则su命令失败;
  • pam_group.so:所有带required字段都是指充分非必要条件,no_warngroup=admin,wheel等参数,则表示如果su命令的调用者想要切换到root,则自身必须是admin组或者wheel组里面的;
  • pam_opendirectory.so:这个模块第二次被调用,用来进行account账户验证,no_check_shell的意思是无需验证是否存在一个shell,这就是为什么bin或者daemon这样的用户也可以运行su命令;
  • pam_opendirectory.so:这个模块第三次被调用,用来验证password密码,将确认密码的任务交给opendirectoryd,如果没有这句话,验证密码则又要去检查/etc/master.passwd文件了;
  • pam_launchd.so:上面的验证都通过了之后,即将开启会话session时,调用pam_launchd.so将会话移动到用户自己的launchd命名空间内。

我们再举个例子,比如说我们要让任何用户都可以肆意使用su命令,也不需要输入密码,可以把这句话:auth sufficient pam_permit.so加到/etc/pam.d/su文件的开头,这样任何情况下使用su命令都会成功,哪怕是suroot也可以,也不需要密码,哪怕root用户没有启用也没关系,详见HT204012。同样,如果将auth required pam_deny.so作为文件第一行的话,那么任何情况下调用su都会失败,哪怕是root用户来调用也不行,因为pam_deny.so会拒绝所有请求。

拓展

认证是系统安全保障的基础,认证的关键是搞清楚执行某项操作的用户具体是谁。用户使用凭证(账号加密码)登录到系统,然后开始进行会话。该会话的中的所有操作,都属于该用户,都在该用户的权限和策略下执行。

 

在这里需要强调一下,在内核的最底层,UNX系统只看得见用户ID和组ID,是看不见用户名的。"root"这个用户名是没有任何意义的,其UID0才是真正起到作用的。内核也不明白什么叫做凭证,以及这些凭证是否有效,比如说loginpassword这些操作的含义内核是不知道的,内核只会按照用户模式的代码来返回结果。也就是说,由UID(UNX内核空间对象)到用户名(用户所熟知的账户名)的映射是至关重要的,以及其凭证是否有效,这些就构成了系统的认证子系统。下面来看看MacOS也好,这些类UNIX系统从UNIX系统继承下来的古老的认证机制。

password files (* OS)

UN*X的“远古时期”,UN*X使用一个密码表文件——/etc/passwd来存储用户信息和密码,参数之间使用冒号隔开。BSD 4.3MacOS也采用了这种方法,该文件被命名为/etc/master.passwd,格式也稍作调整:

name:passwd:uid:gid:class:change:expire:gecos:/path/to/home/dir:shell

使用单文件的认证系统无疑是非常危险的,黑客入侵系统后的第一件事情就是dump这个文件,暴力破解passwd的域,然后重建缓存,尝试登陆。而且这个格式也存在很多缺陷,所以即使是MacOS也只是仅仅把这种单文件认证方式用在单用户登陆的模式上。并且保持着对master.passwd文件的更新,在MacOS 10.12系统上还给他增加了很多守护进程的用户定义,比如_ctkd还有_applepay等等。

 

因为在OS系统上还残留着该认证方式的应用,所以我们这里还是需要再多提一下。/etc/master.passwd文件是`OS`系统根文件系统(rootfilesystem)的一部分,定义了如下图所示的用户:

 

 

虽然该文件最初的用途是提供UID到用户名的对应关系,我们可以看到大多数用户并不需要一个密码(* ),而且也不需要一个shell/usr/bin/false)。当然,最重要的rootmobile用户并不是这样,他们的shell被设置为/bin/sh,可能是因为苹果觉得即使这样设置也没关系吧,因为发行版的iOS系统上也不会有/bin/sh的二进制文件存在,更加不会有login(1)或者sshd(8)这样的二进制工具存在了,没有这些工具,会话(session)更是无从谈起。

 

在越狱机器上,我们拥有了/bin/shsshd之后,getpwent(3)就会去查/etc/master.passwd文件,rootmobile的初始密码就是上图中的/smx7MYTQIi2M,其明文就是alpine(这是初代的iPhoneOS的编译代号)。

 

PS:

 

曾经出现过针对iPhone越狱设备的SSH蠕虫,原理就是尝试连接越狱过的iPhone22端口,并以默认的rootalpine作为用户名和密码进行登录,然后感染该设备。因此我们越狱之后要做的第一件事情,就是将默认密码改掉,换成一个强口令。当然最好还是生成一个密码对,每次使用秘钥文件登录,这样我们每次登录时都无需再输入密码,也只有拥有该秘钥的设备,才能够登录iPhone设备,这样是最安全的。

SetUID and GetGID(MacOS)

setuidsetgid也是历史遗留的概念,可执行文件的上这两个权限位可以让执行用户迅速地了解到该二进制文件的所有人和所在组。这听上去好像有点烦,我们来具体解释一下。

 

但我们要更换自己地身份时,我们可以调用标准UN*Xsu(1)命令(然后su会检查PAM规则,这个后面再说),su命令会在内部调用setuid这个系统调用。所以切换用户这个操作,一个特权操作,不是每一个普通用户,都可以操作成功的,得是root用户才能成功。

 

这跟现状肯定是不符合的,因为我们一直在普通用户的身份下使用su命令,这里面UN*X系统做了什么事情呢?为了在普通用户的身份下setuid也可以成功,UN*X系统会让su命令在被执行时,假定已经拥有了root的权限。实现的方式是通过chown将后续二进制文件的拥有者改为root,再通过chmod u+s这个命令,来执行该二进制文件。

 

这种机制其实问题比较大,但是初期的UN*X系统却大量使用了这种机制,比如passwd,因为这个命令必须要编辑/etc/passwdshadow文件,但是又只能是root才能编辑这两个文件,所以也使用了跟su一样的机制。这种机制的核心问题,就是对su命令也好、passwd命令也好,对这些命令的绝对信任;但是,我们也清楚,绝对的权利,就会导致绝对的浪费(低效甚至无效)和绝对的腐败(内部溃烂,户枢不蠹)。

 

对于这种非常“不优雅”的机制,发生问题也是迟早的事情,有些命令可以轻易地被符号链接或者条件竞争所绕过,执行任意文件修改;有些则存在字符串溢出漏洞,轻易达成任意代码执行。像setuidsetgid这种机制其本身就不应该存在,但是这些却是UN*X构建之初就存在的组建,想要拿掉无异于重写系统,我们还是得与之共存。

 

Darwin一直在减少setuidgetuid机制的使用。在引进opendirectory之后,passwd命令终于不再遭受setuidgetuid的荼毒了。其他命令也或多或少在引入XPCentitlement(沙盒)之后,减少了对setuidgetuid的依赖。就拿最近发生的事情来看,Install.framework's runnerSystemAdministration.framework's readconfigsetdui标志位已经在10.1110.12中被移除了,只剩下下图中的这几个命令还需要依赖setuidgetuid了(原因被标注在右侧#号内。)

JialindeMacBook-Air:~ jialinchen$  find / -user root -perm -4000 2> /dev/null
/bin/ps                                                #系统进程统计
/System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/MacOS/ARDAgent
/usr/bin/at                                         #连接到atd守护进程
/usr/bin/atq                                      #计划作业
/usr/bin/atrm                                   #移除计划作业
/usr/bin/batch                                 #功能性设置
/usr/bin/crontab                            #历史遗留问题(必须编辑/usr/lib/cron/tabs)
/usr/bin/login                                  #必须依赖setuid
/usr/bin/newgrp                            #历史遗留问题(必须编辑/etc/group)
/usr/bin/quota                                #历史遗留问题(编辑配额文件)
/usr/bin/su                                        #历史遗留问题,必须依赖setuid
/usr/bin/sudo                                  #历史遗留问题,必须依赖setuid
/usr/bin/top                                     #提权进行系统进程统计
/usr/libexec/authopen                #打开系统内的任意文件
/usr/libexec/security_authtrampoline      #执行特权命令
/usr/sbin/traceroute                    #历史遗留问题,直接操作接口
/usr/sbin/traceroute6                 #历史遗留问题,直接操作接口

虽然“榜单”已经很小了,但问题其实还是很大,举个例子来看,一直到10.10.5版本的动态链接器/usr/lib/dyld,当跟有setuid的二进制配合使用时,在特定情况下可以触发提权漏洞,导致任意代码执行(后面有时间可以细聊下)。总之,苹果是时候将历史的裹脚布脱下来,换上特步或者阿迪王,继续“不走寻常路”!


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 0
支持
分享
最新回复 (1)
雪    币: 893
活跃值: (382)
能力值: ( LV13,RANK:600 )
在线值:
发帖
回帖
粉丝
2
支持一下版主~
2018-2-5 16:08
0
游客
登录 | 注册 方可回帖
返回
//