场景:
在 bash 脚本中,我必须检查用户提供的密码是否是有效的用户密码。
即假设我有一个密码为 PA 的用户 A .. 在脚本中我要求用户 A 输入他的密码,那么如何检查输入的字符串是否真的是他的密码?...
Eli*_*gan 17
由于您想在 shell 脚本中执行此操作,因此在How to check password with Linux 中有一些贡献?(在Unix.SE 上,由 AB 建议)特别相关:
/etc/shadow提供了部分解决方案。mkpasswdDebian(和 Ubuntu)中存在的命令的不同语法。要手动检查字符串是否真的是某个用户的密码,您必须使用与用户影子条目中相同的哈希算法对其进行哈希处理,并使用与用户影子条目中相同的盐。然后它可以与存储在那里的密码哈希进行比较。
我已经编写了一个完整的工作脚本来演示如何执行此操作。
chkpass,你可以运行它,它会从标准输入中读取一行并检查它是否是密码。chkpass useruser
包以获取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
Run Code Online (Sandbox Code Playgroud)
这样的方法是否应该被认为是安全的和适当的取决于您尚未提供的用例的详细信息(在撰写本文时)。
尽管我在编写此脚本时已尽量小心谨慎,但尚未针对安全漏洞对其进行适当审核。它旨在作为演示,如果作为项目的一部分发布,它将是“alpha”软件。此外...
由于如何mkpasswd接受盐数据的限制,此脚本包含一个已知的安全漏洞,您可能会或可能不会根据用例认为可接受。默认情况下,Ubuntu 和大多数其他 GNU/Linux 系统上的用户可以查看其他用户(包括 root)运行的进程的信息,包括他们的命令行参数。用户的输入和存储的密码哈希都不会作为命令行参数传递给任何外部实用程序。但盐,从所提取的shadow数据库,被给定为一个命令行参数mkpasswd,因为这是实用程序接受盐作为输入的唯一途径。
如果
www-data)运行其代码的人,或/proc)能够检查mkpasswd该脚本运行时的命令行参数,然后他们可以从shadow数据库中获取用户盐的副本。他们可能必须能够猜测该命令何时运行,但这有时是可以实现的。
使用您的 salt 的攻击者并不像使用您的 salt和 hash的攻击者那么糟糕,但这并不理想。salt 没有提供足够的信息让别人发现你的密码。但它确实允许某人在该系统上生成特定于该用户的彩虹表或预先计算的字典哈希。这最初是没有价值的,但是如果您的安全性在以后受到损害并且获得了完整的哈希值,那么它可以被更快地破解以在用户有机会更改密码之前获得用户的密码。
因此,在更复杂的攻击场景中,这个安全漏洞是一个加剧因素,而不是一个完全可利用的漏洞。您可能认为上述情况有些牵强。但是我不愿意推荐任何将任何非公开数据泄露/etc/shadow给非 root 用户的方法用于一般的实际使用。
您可以通过以下方式完全避免此问题:
如果您允许不受信任的用户以 root 身份运行此脚本或以 root 身份运行任何调用此脚本的进程,请小心。通过改变环境,他们可以让这个脚本——或任何以 root 身份运行的脚本——做任何事情。除非您可以防止这种情况发生,否则您不得允许用户提升权限来运行 shell 脚本。
见10.4。有关更多信息,请参阅David A. Wheeler 的Secure Programming for Linux and Unix HOWTO 中的Shell Scripting Languages(sh 和 csh Derivatives)。虽然他的演讲侧重于 setuid 脚本,但如果其他机制不能正确清理环境,则它们可能会成为一些相同问题的牺牲品。
shadow数据库读取哈希值。必须隐藏密码才能使该脚本工作(即,它们的散列值应该在一个单独的/etc/shadow文件中,只有 root 可以读取,而不是在 中/etc/passwd)。
在 Ubuntu 中应该始终如此。在任何情况下,如果需要的脚本可以平凡扩展到从读取密码的哈希值passwd,以及shadow。
IFS修改此脚本时请记住。我一IFS=$开始就设置了,因为一个shadow entry的hash字段中的三个数据是用$.分隔的。
$,这就是为什么散列类型和盐分别是"${ent[1]}"and"${ent[2]}"而不是"${ent[0]}"and 的原因"${ent[1]}"。此脚本中唯一$IFS确定 shell 如何拆分或组合单词的地方是
当这些数据被拆分成一个数组时,通过从不带引号的$( )命令替换中初始化它:
set -f; ent=($(getent shadow "$1" | awk -F: '{print $2}')); set +f
Run Code Online (Sandbox Code Playgroud)当数组重组为字符串以与 from 的完整字段进行比较时shadow,"${ent[*]}"表达式为:
if [[ "${ent[*]}" = "$(mkpasswd -sm $hashtype -S "${ent[2]}" <<<"$pass")" ]]
Run Code Online (Sandbox Code Playgroud)如果您修改脚本并让它在其他情况下执行分词(或词连接),则需要IFS为不同的命令或脚本的不同部分设置不同的值。
如果您不牢记这一点并假设$IFS设置为通常的空格 ( $' \t\n'),您最终可能会使您的脚本以一些看起来非常奇怪的方式运行。
mur*_*uru 10
您可以sudo为此滥用。sudo有-l选项,用于测试用户拥有的 sudo 权限,以及-S从标准输入读取密码。但是,无论用户具有什么权限级别,如果成功通过身份验证,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
Run Code Online (Sandbox Code Playgroud)
此脚本在很大程度上取决于sudoers配置。我已经假设了默认设置。可能导致它失败的事情:
targetpw或runaspw已设置listpw 是 never其他问题包括(感谢 Eliah):
/var/log/auth.logsudo必须以您要为其进行身份验证的用户身份运行。除非您有sudo权限运行sudo -u foo sudo -lS,否则您必须以目标用户身份运行脚本。现在,我使用 here-docs 的原因是为了防止窃听。通过使用top或ps或其他工具来检查进程,可以更容易地揭示用作命令行一部分的变量。
另一种方法(其理论内容可能比实际应用更有趣)。
用户的密码存储在/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:::
Run Code Online (Sandbox Code Playgroud)
foo: 用户名6: 密码的加密类型lWS1oJnmDlaXrx1F: 密码的加密盐h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/:SHA-512加盐/加密密码为了匹配给bar定用户的给定密码foo,首先要做的是获取盐:
$ sudo getent shadow foo | cut -d$ -f3
lWS1oJnmDlaXrx1F
Run Code Online (Sandbox Code Playgroud)
然后应该得到完整的加盐/加密密码:
$ sudo getent shadow foo | cut -d: -f2
$6$lWS1oJnmDlaXrx1F$h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/
Run Code Online (Sandbox Code Playgroud)
然后可以对给定的密码进行加盐/加密,并与存储在/etc/shadow以下位置的加盐/加密的用户密码进行匹配:
$ python -c 'import crypt; print crypt.crypt("bar", "$6$lWS1oJnmDlaXrx1F")'
$6$lWS1oJnmDlaXrx1F$h4vuzZVBwIE1Z6vT7N.spwbxYig9e/OHOIH.VDv9JPaC3.OtTusPFzma7g.R/oSZFW5QOI7IDdDY01G0zTGQE/
Run Code Online (Sandbox Code Playgroud)
他们匹配!一切都放在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"
Run Code Online (Sandbox Code Playgroud)
输出:
$ ./script.sh
Username >foo
Password >bar
Password matches
$ ./script.sh
Username >foo
Password >bar1
Password doesn't match
Run Code Online (Sandbox Code Playgroud)