背景
用户反馈使用密码无法正常登录,提示“Permission Denied”,需要重置密码后才能正常,而相同的密码在一个月前能正常登录,一共有两台实例有这样的现象。
分析
一般来说,类似问题可以从几个方面来考虑可能性:
- SSH server配置问题
- 服务器密码被改动过或者密码相关配置文件被改动过
- 通过远程或者VNC的方式键入密码传递到系统内时候发生了变动
- 记错密码
以上几点我们一一进行排查,由于要保留现场,无法重置密码后再登录查看,我们可以通过将系统盘快照做成云盘来绕过登录的环节,从而进入系统。
挂载快照盘,我们通过chroot进入系统,要定位是否密码有改动或者配置文件有变动,我们首先通过stat查看/etc/ssh/目录下各文件,从mtime/ctime时间戳来看,最近的改动在一年多前,说明sshd如果是读取的正常路径配置文件,那么用户名和密码以及sshd相关配置都没有被改动过。
单方面看MAC时间可能说服力不够,我们通过查看配置文件和对比测试进一步说明sshd正常工作且服务器端没有改动:
1. 实际查看/etc/ssh/sshd_conf配置文件,发现是保持的默认配置,没有异常改动。
2. 有快照的前提下,尝试修改密码/重置密码,之后再登录发现可以正常登录。
以上排查验证了服务器端没有改动且sshd工作正常,为了验证是否输入密码到sshd读取过程是否存在异常,我们挂载快照盘并chroot进入系统,通过strace来跟踪sshd进程以及其fork出的进程,之后通过ssh自己系统来规避网络上的影响。我们可以看到输入的密码在被读取时候并没有发生改变。也能从strace输出中了解到去读取的配置文件是正常路径的配置文件。说明输入的密码在验证之前也没有被修改过。
基于以上信息,我们基本可以肯定是用户密码记录存在问题。但用户也表示从数据库读取的,期间没动过数据库,应该不会出现问题,那么为了更进一步说服用户,我们可以考虑实际验证一下用一样的办法加密密码明文后获取的信息是否和/etc/shadow里记录的一致。
我们知道密码加密后的口令存储在/etc/shadow文件里,那么我们来看一下/etc/shadow文件里的口令存储形式,以CentOS 7.2为例:
root:$6$KqCVFlfd$vAARGaFcI5mNWGoOSJeE87ZrI5anStEWZYRz6EkrvXRSuD7X1teJwrWvC2a3YQRSeKuDaqmuN5ScinV/fvOyy0:17497:0:99999:7:::
这里引用wiki的注解:
-
User login name
-
salt and hashed password OR a status exception value e.g.:
-
"$id$salt$hashed", the printable form of a password hash as produced by crypt (C), where "$id" is the algorithm used. (On GNU/Linux, "$1$" stands for MD5, "$2a$" is Blowfish, "$2y$" is Blowfish (correct handling of 8-bit chars), "$5$" is SHA-256 and "$6$" is SHA-512, other Unix may have different values, like NetBSD. Key stretching is used to increase password cracking difficulty, using by default 1000 rounds of modified MD5, 64 rounds of Blowfish, 5000 rounds of SHA-256 or SHA-512. The number of rounds may be varied for Blowfish, or for SHA-256 and SHA-512 by using e.g. "$6$rounds=50000$".
-
Empty string – No password, the account has no password (reported by passwd on Solaris with "NP").
-
"!" – the account is password locked, user will be unable to log in via password authentication but other methods (e.g. ssh key) may be still allowed.
-
"*LK*" or "*" – the account is locked, user will be unable to log in via password authentication but other methods (e.g. ssh key) may be still allowed.
-
"!!" – the password has never been set (RedHat)
-
Days since epoch of last password change
-
Days until change allowed
-
Days before change required
-
Days warning for expiration
-
Days before account inactive
-
Days since epoch when account expires
这里我们主要需要关注的部分是$id$salt$hashed,其中$id是加密算法,$salt是混入加密算法的盐,$hashed是经过加密算法hash对应密码和盐的结果。
ID | Method
───────────────────────────────────────────────────────── 1 | MD5 2a | Blowfish (not in mainline glibc; added in some Linux distributions) 5 | SHA-256 (since glibc 2.7) 6 | SHA-512 (since glibc 2.7)
这种场景加密过程就是将密码通过加密算法计算得到一个hash串,从hash串是无法反推出密码的。而加入盐的目的是增强加密复杂性,规避一些强行破解的办法。
具体加密算法介绍可以参考: https://en.wikipedia.org/wiki/SHA-2
在实际设置密码过程中,salt是随机生成的,shadow文件记录加密算法、salt以及加密后hash串,而当有用户登录时候,会将输入的密码与shadow文件中salt基于加密算法重新计算一次然后跟hash串比对,如果相同,则说明密码正确。
我们有了算法和salt后,那么就可以实际验证用户提供的密码加密后的hash串是否跟shadow文件中对应用户的hash串相符。
RHEL 6 and newer - python • Execute the following one-liner: $ python -c 'import crypt,getpass; print crypt.crypt(getpass.getpass())' Password: $6$Q3dbIWgPMCVBpRZK$QSw1cG41FImM0E8B.Hpx1G8eZGqHALzGg75LLAt.MkFZtVma3MHRGBpFSrXEEdVHwySr8B0JfXAgLHgmpSViI0
In short, the above will prompt for input and then interface with python's implementation of the standard crypt() function, generating a shadow-compatible hash by choosing a random salt and the strongest hash method available (SHA-512) • For documentation, do as the following demonstrates $ python Python 2.6.6 (r266:84292, May 1 2012, 13:52:17) [GCC 4.4.6 20110731 (Red Hat 4.4.6-3)] on linux2 Type "help", "copyright", "credits" or "license" for more information.
import crypt help(crypt)
RHEL 5 and newer - perl • Execute the following one-liner: $ perl -le 'print "Password:"; stty -echo
; chomp($passphrase=); stty echo
; @chars = ("a".."z", "A".."Z", 0..9, ".", "/"); $salt .= $chars[rand @chars] for 1..16; print crypt($passphrase, "$6$$salt");' Password: $6$RDGTu1VHQPH2NLpw$no4LMowmxaJogzYoIUdsfi7pfTz2EtzN//oRmEe12AEWc2h6NPoQwrWA4KYe4W0OSAUaBLOG8K59kzENV2bvY0 • Notes on the above perl code: ○ This code was tested by the author on RHEL 5 - RHEL 7; however, it is offered without any guarantees or warranty (to use it, ensure the quotation marks and backslashes are kept intact) ○ This code generates a SHA512-hashed password string; to use SHA256 instead, change the maroon-colored $6 to a $5
*以上为RedHat给出的模拟方式
为了让数据更有说服力,我们先要验证加密办法是对的,通过类似方法加密的信息能正确跟shadow文件里存储的信息对的上。
验证符合后,我们进一步验证用户提供的密码信息。发现从用户提供的密码来正向获取hash串后跟shadow里root的不匹配,由此进一步说明了是用户提供的密码存在问题。
# cat /mnt/etc/shadow root:$6$D9.mVsWm$W9XlxzRHWU6B6KFNaPZQPCkXF0GrL.Cq.zzx2H24qaDC4bphDFZqZtD5G.t1Rz1cegl2tyQAdx9W6iBoJTANc.:17013:0:99999:7::: # python -c 'import crypt; printcrypt.crypt("Spidermanwsx123","$6$D9.mVsWm")' $6$D9.mVsWm$Y8rVe1915SlIkdwOloHhFedpW2MZ7xYOYHy69jXOoZqyodDm5WJOBP4P/EeLP1BjJlibHwovaHqawTWD05c6o1