场景:
在bash脚本中,我必须检查用户提供的密码是否为有效的用户密码。
即假设我有一个密码为PA的用户A。在脚本中,我要求用户A输入密码,那么如何检查输入的字符串是否真的是他的密码?
expect
的stackoverflow.com/a/1503831/320594。
场景:
在bash脚本中,我必须检查用户提供的密码是否为有效的用户密码。
即假设我有一个密码为PA的用户A。在脚本中,我要求用户A输入密码,那么如何检查输入的字符串是否真的是他的密码?
expect
的stackoverflow.com/a/1503831/320594。
Answers:
由于您要在shell脚本中执行此操作,因此如何使用Linux检查密码中有几处内容?(在AB。建议的Unix.SE上)特别相关:
/etc/shadow
给出了部分解决方案。mkpasswd
Debian(和Ubuntu)中存在的命令的不同语法。要手动检查字符串是否确实是某个用户的密码,必须使用与该用户的影子条目相同的哈希算法对它进行哈希处理,并使用相同的盐。然后可以将其与存储在其中的密码哈希进行比较。
我编写了一个完整的工作脚本,演示了如何执行此操作。
chkpass
,则可以运行,它将从标准输入中读取一行并检查是否chkpass user
user
密码。mkpasswd
此脚本依赖的实用程序。#!/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") <username>"
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
接受盐数据的方式受到限制,此脚本包含一个已知的安全漏洞,根据使用情况,您可能会或可能不认为它是可接受的。默认情况下,Ubuntu和大多数其他GNU / Linux系统上的用户可以查看有关其他用户(包括root)运行的进程的信息,包括其命令行参数。用户的输入和存储的密码哈希都不会作为命令行参数传递给任何外部实用程序。但是,从数据库中提取的盐是作为的命令行参数给出的,因为这是实用程序接受盐作为输入的唯一方法。shadow
mkpasswd
如果
www-data
)运行其代码的人,或/proc
)能够检查mkpasswd
由该脚本运行的命令行参数,然后他们可以从shadow
数据库中获取用户盐的副本。他们可能必须能够猜测该命令何时运行,但这有时是可以实现的。
拥有盐分的攻击者并没有拥有盐分和哈希的攻击者那么糟糕,但这并不理想。盐不能为某人提供足够的信息来发现您的密码。但是,它确实允许某人生成该系统上该用户特定的彩虹表或预先计算的字典哈希。最初这毫无价值,但是如果您的安全性在以后受到损害,并且获得了完整的哈希,则可能是在用户有机会对其进行更改之前更快地破解以获取用户的密码。
因此,此安全漏洞是更复杂的攻击情形中的加剧因素,而不是完全可利用的漏洞。您可能会认为上述情况牵强。但我不愿意推荐任何方法一般,实际使用该泄漏任何非公开数据/etc/shadow
给非root用户的。
您可以通过以下方式完全避免此问题:
如果您允许不受信任的用户以root用户身份运行此脚本或以root用户身份运行任何调用此脚本的进程,请当心。通过更改环境,他们可以使此脚本或任何以root用户身份运行的脚本执行任何操作。除非可以防止这种情况发生,否则必须不允许用户提升特权来运行Shell脚本。
参见10.4。有关更多信息,请参阅David A. Wheeler的Linux和Unix HOWTO安全编程中的Shell脚本语言(sh和csh衍生物)。尽管他的演讲着重于setuid脚本,但如果其他机制无法正确清理环境,则其他机制也可能成为某些相同问题的牺牲品。
shadow
数据库。为了使此脚本正常工作,必须对密码进行阴影处理(即,其哈希值应位于单独的/etc/shadow
文件中,只有root可以读取,而不是/etc/passwd
)。
在Ubuntu中应该总是这样。在任何情况下,如果需要,都可以对脚本进行简单扩展以从passwd
和读取密码哈希shadow
。
IFS
修改此脚本时请记住。我IFS=$
在开始时进行了设置,因为阴影条目的哈希字段中的三个数据用分隔$
。
$
,这就是为什么哈希类型和盐分别是"${ent[1]}"
和"${ent[2]}"
而不是"${ent[0]}"
和的原因"${ent[1]}"
。当这些数据拆分为一个数组时,可以通过以下$(
)
命令中未引用命令的替换将其初始化:
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读取密码的选项。但是,无论用户具有什么特权级别,如果成功通过身份验证,则sudo
返回的退出状态为0。因此,您可以将其他任何退出状态都视为身份验证无效(假设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
必须以您要验证的用户身份运行。除非您具有sudo
特权才能运行sudo -u foo sudo -lS
,否则这意味着您必须以目标用户身份运行脚本。现在,我在这里使用文档的原因是为了防止窃听。用作命令行的一部分的可变更容易通过使用显示top
或ps
或其他工具,以检查流程。
sudo
配置不常见(如您所说)且/var/log/auth.log
每次尝试记录混乱的系统上不起作用,但这是快速,简单的并且使用现有实用程序,而不是重新发明轮子(可以这么说)。我建议将设置IFS=
为read
,以防止添加空格的用户输入和未填充密码的错误成功,以及填充空格的用户输入和填充密码的错误的错误。(我的解决方案有一个类似的错误。)您可能还想解释一下如何以要检查其密码的用户身份运行它。
sudo -l
使条目写为“ COMMAND=list
”。顺便说一句(无关),虽然这样做并不是客观上更好,但是使用here字符串代替here文档可以达到相同的安全目标,并且更加紧凑。由于脚本已经使用了bashisms read -s
和&>
,因此using 的可移植性同样出色if sudo -lS &>/dev/null <<<"$PASSWD"; then
。我不建议您更改所拥有的(很好)。而是,我提到了它,因此读者知道他们也有此选项。
sudo -l
禁用了某些内容-也许是在用户尝试运行不允许其执行的命令时报告。
另一种方法(其理论内容可能比其实际应用更有趣)。
用户密码存储在 /etc/shadow
。
在最新的Ubuntu版本中,使用以下方式加密存储在此处的密码: SHA-512
。
具体来说,在创建密码后,将通过明文形式加密密码并加密SHA-512
。
然后一种解决方案是对给定密码加盐/加密,并将其与存储在给定用户/etc/shadow
条目中的加密用户密码进行匹配。
为了快速分解密码在每个用户/etc/shadow
条目中的存储方式,/etc/shadow
以下是foo
具有密码的用户的示例条目bar
:
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
加密/加密密码为了匹配给bar
定用户的给定密码foo
,首先要做的是获取盐:
$ 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
sudo getent shadow
>> sudo grep ...
。否则,很好。这是我最初想到的,然后变得太懒了。
getent
+ grep
+ cut
> grep
+cut
getent
可以为您搜索。我拥有编辑的自由。
mkpasswd
以前是根据用户输入和现有盐(包括附加功能和诊断程序)生成哈希的,但您却编写了一个简单的Python程序,实现了mkpasswd
最重要的功能,并使用了它。我建议您IFS=
在读取密码输入时进行设置,并用引起${password}
来"
,这样使用空格的密码尝试不会破坏脚本。