如何检查输入的密码是该用户的有效密码?


15

场景:

在bash脚本中,我必须检查用户提供的密码是否为有效的用户密码。

即假设我有一个密码为PA的用户A。在脚本中,我要求用户A输入密码,那么如何检查输入的字符串是否真的是他的密码?


您输入的有效密码是什么意思?是否要测试是否真的是用户密码?
AB

@AB有效的密码=登录密码无论您想致电...我的意思是该用户的密码
Maythux 2015年

@AB我知道这将是一个安全漏洞,但在这里,你已经知道的用户名也......换句话说它只是一个测试,如果密码是该用户..
Maythux

3
这里是你的答案:unix.stackexchange.com/a/21728/107084
AB

Answers:


14

由于您要在shell脚本中执行此操作,因此如何使用Linux检查密码中有几处内容?(在AB建议的Unix.SE上)特别相关:

要手动检查字符串是否确实是某个用户的密码,必须使用与该用户的影子条目相同的哈希算法对它进行哈希处理,并使用相同的。然后可以将其与存储在其中的密码哈希进行比较。

我编写了一个完整的工作脚本,演示了如何执行此操作。

  • 如果命名chkpass,则可以运行,它将从标准输入中读取一行并检查是否chkpass useruser密码。
  • 安装whois 安装Whois软件包以获得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") <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)运行的进程的信息,包括其命令行参数。用户的输入和存储的密码哈希都不会作为命令行参数传递给任何外部实用程序。但是,从数据库中提取的作为的命令行参数给出的,因为这是实用程序接受盐作为输入的唯一方法。shadowmkpasswd

如果

  • 系统上的另一个用户, 或者
  • 有能力使任何用户帐户(例如www-data)运行其代码的人,
  • 任何人都可以查看有关正在运行的进程的信息(包括通过手动检查中的条目/proc

能够检查mkpasswd由该脚本运行的命令行参数,然后他们可以从shadow数据库中获取用户盐的副本。他们可能必须能够猜测该命令何时运行,但这有时是可以实现的。

拥有盐分的攻击者并没有拥有盐分和哈希的攻击者那么糟糕,但这并不理想。盐不能为某人提供足够的信息来发现您的密码。但是,它确实允许某人生成该系统上该用户特定的彩虹表预先计算的字典哈希。最初这毫无价值,但是如果您的安全性在以后受到损害并且获得了完整的哈希,则可能是在用户有机会对其进行更改之前更快地破解以获取用户的密码。

因此,此安全漏洞是更复杂的攻击情形中的加剧因素,而不是完全可利用的漏洞。您可能会认为上述情况牵强。但我不愿意推荐任何方法一般,实际使用该泄漏任何非公开数据/etc/shadow给非root用户的。

您可以通过以下方式完全避免此问题:

  • Perl语言编写或其他语言,可以让你的脚本的一部分,你调用C函数,如显示在吉尔的回答相关Unix.SE问题
  • 用这种语言而不是bash编写整个脚本/程序。(根据您标记问题的方式,您似乎更喜欢使用bash。)

请小心如何调用此脚本。

如果您允许不受信任的用户以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]}"

该脚本中唯一可$IFS确定外壳如何拆分组合单词的位置是

  • 当这些数据拆分为一个数组时,可以通过以下$( )命令中未引用命令的替换将其初始化:

    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'),则可能最终使脚本以某种看起来很奇怪的方式运行。


8

您可以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配置。我假设默认设置。可能导致其失败的事情:

  • targetpwrunaspw已设定
  • listpwnever
  • 等等

其他问题包括(感谢Eliah):

  • 错误的尝试将被登录 /var/log/auth.log
  • sudo必须以您要验证的用户身份运行。除非您具有sudo特权才能运行sudo -u foo sudo -lS,否则这意味着您必须以目标用户身份运行脚本。

现在,我在这里使用文档的原因是为了防止窃听。用作命令行的一部分的可变更容易通过使用显示topps或其他工具,以检查流程。


2
这样看起来很棒。尽管它在sudo配置不常见(如您所说)且/var/log/auth.log每次尝试记录混乱的系统上不起作用,但这是快速,简单的并且使用现有实用程序,而不是重新发明轮子(可以这么说)。我建议将设置IFS=read,以防止添加空格的用户输入和未填充密码的错误成功,以及填充空格的用户输入和填充密码的错误的错误。(我的解决方案有一个类似的错误。)您可能还想解释一下如何以要检查其密码的用户身份运行它。
伊莱亚·卡根

@EliahKagan每次失败的尝试都会记录下来,但是是的,您对其他所有事情都是正确的。
muru

还将记录成功的身份验证。 sudo -l使条目写为“ COMMAND=list”。顺便说一句(无关),虽然这样做并不是客观上更好,但是使用here字符串代替here文档可以达到相同的安全目标,并且更加紧凑。由于脚本已经使用了bashisms read -s&>,因此using 的可移植性同样出色if sudo -lS &>/dev/null <<<"$PASSWD"; then。我不建议您更改所拥有的(很好)。而是,我提到了它,因此读者知道他们也有此选项。
伊利亚·卡根

@EliahKagan啊。我以为sudo -l禁用了某些内容-也许是在用户尝试运行不允许其执行的命令时报告。
muru

@EliahKagan确实。我以前曾经用过herestrings。我在这里的误解下工作,导致了heredocs,但是后来我决定还是保留它们。
muru

4

另一种方法(其理论内容可能比其实际应用更有趣)。

用户密码存储在 /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

1
sudo getent shadow>> sudo grep ...。否则,很好。这是我最初想到的,然后变得太懒了。
muru

@muru谢谢。Definetly感觉更好,所以更新,但我还是不敢肯定,如果在这种情况下getent+ grep+ cut> grep+cut
科斯

1
真是的 getent可以为您搜索。我拥有编辑的自由。
muru

@muru是的,您实际上应该拥有,现在我明白了!
kos 2015年

2
我认为这实际上很实用,尽管我可能会有所偏见:这与我使用的方法相同。但是,您的实现和演示方式却大不相同-这绝对是一个有价值的答案。尽管我mkpasswd以前是根据用户输入和现有盐(包括附加功能和诊断程序)生成哈希的,但您却编写了一个简单的Python程序,实现了mkpasswd最重要的功能,并使用了它。我建议您IFS=在读取密码输入时进行设置,并用引起${password}",这样使用空格的密码尝试不会破坏脚本。
伊莱亚·卡根
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.