如何检查输入的密码是该用户的有效密码?
场景:
在bash脚本中,我必须检查用户提供的密码是否是有效的用户密码。
也就是说我有一个用户A,密码为PA ..在脚本中我要求用户A输入他的密码,那么如何检查输入的字符串是否真的是他的密码?…
既然您想在shell脚本中执行此操作,那么在如何使用Linux检查密码方面有一些贡献? (在Unix.SE上 , 由AB建议 )特别相关:
- rozcietrzewiacz关于生成与
/etc/shadow
中的条目匹配的密码哈希的答案给出了解决方案的一部分。 - Daniel Alder的评论解释了Debian(和Ubuntu)中存在的
mkpasswd
命令的不同语法。
要手动检查字符串是否确实是某个用户的密码,必须使用与用户阴影条目中相同的散列算法对其进行散列,并使用与用户阴影条目中相同的salt 。 然后可以将其与存储在那里的密码哈希进行比较。
我写了一个完整的,有效的脚本来演示如何做到这一点。
- 如果你将它命名为
chkpass
,你可以运行chkpass user
,它将从标准输入读取一行并检查它是否是user
的密码。 - 安装whois package以获取此脚本所依赖的
mkpasswd
实用程序。 - 必须以root身份运行此脚本才能成功。
- 在使用此脚本或其任何部分进行实际工作之前,请参阅下面的安全说明 。
#!/usr/bin/env bash xcorrect=0 xwrong=1 enouser=2 enodata=3 esyntax=4 ehash=5 IFS=$ die() { printf '%s: %s\n' "$0" "$2" >&2 exit $1 } report() { if (($1 == xcorrect)) then echo 'Correct password.' else echo 'Wrong password.' fi exit $1 } (($# == 1)) || die $esyntax "Usage: $(basename "$0") " case "$(getent passwd "$1" | awk -F: '{print $2}')" in x) ;; '') die $enouser "error: user '$1' not found";; *) die $enodata "error: $1's password appears unshadowed!";; esac if [ -t 0 ]; then IFS= read -rsp "[$(basename "$0")] password for $1: " pass printf '\n' else IFS= read -r pass fi set -f; ent=($(getent shadow "$1" | awk -F: '{print $2}')); set +f case "${ent[1]}" in 1) hashtype=md5;; 5) hashtype=sha-256;; 6) hashtype=sha-512;; '') case "${ent[0]}" in \*|!) report $xwrong;; '') die $enodata "error: no shadow entry (are you root?)";; *) die $enodata 'error: failure parsing shadow entry';; esac;; *) die $ehash "error: password hash type is unsupported";; esac if [[ "${ent[*]}" = "$(mkpasswd -sm $hashtype -S "${ent[2]}" <<<"$pass")" ]] then report $xcorrect else report $xwrong fi
安全说明
这可能不是正确的方法。
这样的方法是否应该被认为是安全的和其他适当的取决于您未提供的有关您的用例的详细信息(截至撰写本文时)。
它尚未经过审计。
虽然我在编写此脚本时尝试过小心, 但尚未对安全漏洞进行适当的审核 。 它旨在作为演示,如果作为项目的一部分发布,它将是“alpha”软件。 此外...
另一个“观看”的用户可能能够发现用户的盐 。
由于mkpasswd
如何接受salt数据的限制,此脚本包含已知的安全漏洞 ,根据用例,您可能认为或不认为是可接受的。 默认情况下,Ubuntu和大多数其他GNU / Linux系统上的用户可以查看有关其他用户(包括root用户)运行的进程的信息,包括其命令行参数。 用户的输入和存储的密码哈希都不会作为命令行参数传递给任何外部实用程序。 但是从shadow
数据库中提取的salt 是作为mkpasswd
的命令行参数mkpasswd
,因为这是实用程序接受salt作为输入的唯一方法。
如果
- 系统上的另一个用户, 或
- 任何能够创建任何用户帐户(例如,
www-data
)的人都可以运行他们的代码, 或者 - 任何人都可以查看有关正在运行的进程的信息(包括通过手动检查
/proc
条目)
能够检查mkpasswd
的命令行参数,因为它由此脚本运行,然后他们可以从shadow
数据库获取用户盐的副本。 他们可能必须能够猜测该命令何时运行,但这有时是可以实现的。
你的盐的攻击者没有你的盐和哈希攻击者那么糟糕,但它并不理想。 盐没有提供足够的信息让别人发现您的密码。 但它确实允许某人在该系统上生成特定于该用户的彩虹表或预先计算的字典哈希 。 这最初是没有价值的,但是如果您的安全性在以后被破坏并且获得了完整的哈希,那么可以更快地破解它以获得用户的密码,然后才有机会更改它。
因此,这种安全漏洞是更复杂的攻击场景中的恶化因素,而不是完全可利用的漏洞。 你可能会认为上述情况牵强附会。 但是我不愿意推荐任何方法用于将/etc/shadow
中的任何非公共数据泄漏给非root用户的一般实际使用。
您可以完全避免此问题:
- 用Perl编写部分脚本或其他一些允许你调用C函数的语言,如Gilles 对相关Unix.SE问题 的回答所示 , 或者
- 用这种语言编写整个脚本/程序,而不是使用bash。 (根据您标记问题的方式,您似乎更喜欢使用bash。)
如何调用此脚本时要小心。
如果允许不受信任的用户以root身份运行此脚本或以root身份运行调用此脚本的任何进程,请小心 。 通过更改环境,他们可以使此脚本 - 或任何以root身份运行的脚本 - 执行任何操作 。 除非您可以防止这种情况发生,否则您不得允许用户提升运行shell脚本的权限。
见10.4。 Shell脚本语言(sh和csh Derivatives)在David A. Wheeler的Linux和Unix HOWTO安全编程中有关于此的更多信息。 虽然他的演讲主要关注setuid脚本,但如果他们没有正确消毒环境,其他机制可能成为一些相同问题的牺牲品。
其他说明
它仅支持从shadow
数据库中读取哈希值。
必须隐藏密码才能使此脚本正常工作(即,它们的哈希应位于单独的/etc/shadow
文件中,只有root才能读取,而不是在/etc/passwd
)。
在Ubuntu中应该始终如此。 在任何情况下,如果需要,脚本可以简单地扩展为从passwd
和shadow
读取密码哈希值。
修改此脚本时请记住IFS
。
我在开头设置了IFS=$
,因为影子条目的哈希字段中的三个数据用$
分隔。
- 它们也有一个领先的
$
,这就是为什么哈希类型和盐是"${ent[1]}"
和"${ent[2]}"
而不是"${ent[0]}"
和"${ent[1]}"
,分别。
这个脚本中唯一的地方是$IFS
确定shell如何拆分或组合单词
-
当这些数据被拆分成一个数组时,通过从未加引号的
$(
)
命令替换来初始化它:set -f; ent=($(getent shadow "$1" | awk -F: '{print $2}')); set +f
-
当数组重构成一个字符串以与
shadow
的完整字段进行比较时,"${ent[*]}"
表达式在:if [[ "${ent[*]}" = "$(mkpasswd -sm $hashtype -S "${ent[2]}" <<<"$pass")" ]]
如果修改脚本并让其在其他情况下执行分词(或单词连接),则需要将IFS
设置为不同命令或脚本不同部分的不同值。
如果你没有记住这一点,并假设$IFS
设置为通常的空格( $' \t\n'
),你最终可能会以一些非常奇怪的方式表现你的脚本。
你可以为此滥用sudo
。 sudo
有-l
选项,用于测试用户拥有的sudo权限, -S
用于从stdin读取密码。 但是,无论用户具有什么权限级别,如果成功通过身份validation, sudo
将返回退出状态0.因此,您可以将任何其他退出状态作为身份validation不起作用的指示(假设sudo
本身没有任何问题) ,如权限错误或无效的sudoers
配置)。
就像是:
#! /bin/bash IFS= read -rs PASSWD sudo -k if sudo -lS &> /dev/null << EOF $PASSWD EOF then echo 'Correct password.' else echo 'Wrong password.' fi
这个脚本在很大程度上依赖于sudoers
配置。 我假设了默认设置。 可能导致失败的事情:
-
targetpw
或runaspw
已设置 -
listpw
never
- 等等
其他问题包括(感谢Eliah):
- 不正确的尝试将记录在
/var/log/auth.log
-
sudo
必须以您要进行身份validation的用户身份运行。 除非你有sudo
权限,以便你可以运行sudo -u foo sudo -lS
,这意味着你必须以目标用户身份运行脚本。
现在,我在这里使用文档的原因是阻碍窃听。 通过使用top
或ps
或其他工具检查进程,可以更轻松地显示用作命令行一部分的变量。
另一种方法(其理论内容可能比其实际应用更有趣)。
用户密码存储在/etc/shadow
。
这里存储的密码是使用SHA-512
在最新的Ubuntu版本中加密的。
具体来说,在创建密码时,明文密码将通过SHA-512
加密并加密。
然后,一种解决方案是对给定密码进行加密/加密,并将其与存储在给定用户的/etc/shadow
条目中的加密用户密码进行匹配。
为了快速细分密码在每个用户/etc/shadow
条目中的存储方式,这里是一个带密码bar
的用户foo
的示例/etc/shadow
条目:
foo:$6$lWS1oJnmDlaXrx1F$h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/:16566:0:99999:7:::
-
foo
:用户名 -
6
:密码的加密类型 -
lWS1oJnmDlaXrx1F
:密码的加密盐 -
h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/
:SHA-512
加密/加密密码
为了匹配给定用户foo
的给定密码bar
,首先要做的是获取salt:
$ sudo getent shadow foo | cut -d$ -f3 lWS1oJnmDlaXrx1F
然后应该获得完整的盐渍/加密密码:
$ sudo getent shadow foo | cut -d: -f2 $6$lWS1oJnmDlaXrx1F$h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/
然后,可以对给定的密码进行加盐/加密,并与存储在/etc/shadow
中的加盐/加密用户密码进行匹配:
$ python -c 'import crypt; print crypt.crypt("bar", "$6$lWS1oJnmDlaXrx1F")' $6$lWS1oJnmDlaXrx1F$h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/
他们匹配! 所有内容都放入了一个bash
脚本:
#!/bin/bash read -p "Username >" username IFS= read -p "Password >" password salt=$(sudo getent shadow $username | cut -d$ -f3) epassword=$(sudo getent shadow $username | cut -d: -f2) match=$(python -c 'import crypt; print crypt.crypt("'"${password}"'", "$6$'${salt}'")') [ ${match} == ${epassword} ] && echo "Password matches" || echo "Password doesn't match"
输出:
$ ./script.sh Username >foo Password >bar Password matches $ ./script.sh Username >foo Password >bar1 Password doesn't match