Shell 脚本和安全

作者:Dave Taylor

您可以使用 shell 脚本来监控密码强度和秘密帐户的基本方法。

互联网已不再是过去的样子。我记得还在 ARPAnet 时代上网——那时只有大学和少数公司互联。坏人偷偷进入你的电脑?那时我们还生活在幸福的无知中。

如今,在线世界已经大不相同,快速浏览一下新闻就会发现,它不仅是全球性的,而且正如安全界人士所说,不良行为者也在线,并且可以访问您的系统。任何在线设备都容易受到攻击的观点现在比计算机历史上的任何时候都更加真实。

幸运的是,许多聪明的人都在为网络和物联网设备的安全而努力。我们不希望黑客控制我们的智能家居或自动驾驶汽车。

无论您的笔记本电脑或旧式 PC 文件服务器上运行的是 Linux,还是您正在管理数据中心,您的系统都容易受到恶意用户的攻击。在本文中,我无法提供任何强大的解决方案,但让我们来看看您可以使用 shell 脚本来监视系统的一些基本方法。

首先,最重要的是,确保您的所有帐户,尤其是您的管理帐户或 root 帐户,都使用复杂的、难以猜测的密码。检查现有密码很困难,因为它们都以加密方式存储,而无需花费大量的 CPU 周期进行暴力破解,但是如果有一个脚本,用户可以输入他们的密码,并且脚本会确认密码是否难以猜测呢?

密码测试器

现代密码创建的一般经验法则是,您应该使用大写字母、小写字母、数字以及一个或多个标点符号的组合,并且密码应该更长,而不是更短。“meow”作为一个密码是很糟糕的,“Passw0rd!”还不错,但“F#g_flat_33”是一个安全的选择。

但首先要做的是。检查密码的脚本应该让用户隐形地输入他们选择的密码。您可以使用 stty 命令来做到这一点


stty -echo
echo -n "Enter password: "
read password
stty echo

现在,测试特定类型字符的算法方法很简单。删除用户输入中每次出现的该特定字符,并将其与原始字符进行比较。如果它们相同,则用户实际上没有使用该特定类别的字符。

例如,这是测试是否存在小写字母的代码


chop=$(echo "$password" | sed -E 's/[[:lower:]]//g')
echo "chopped to $chop"

if [ "$password" == "$chop" ] ; then
  echo "Fail: You haven't used any lowercase letters."
fi

这里值得注意的是使用了所谓的括号表达式。请注意,我上面没有指定 [a-z],而是使用了与区域设置相关的范围 :lower:。在正则表达式中,它会有一对放在它周围的方括号:[:lower:]。但是,由于它是 sed 的搜索和替换模式的一部分,因此它也会拾取第二对方括号:[[:lower:]]

事实证明,有很多括号表达式,因此您可以使用 :upper: 来测试大写字母,:lower: 来测试小写字母,:digit: 来测试数字,:punct: 来测试标点符号。还有很多,但这些就足以满足本文的范围了。

好消息是,编写一个函数来检查指定的括号表达式并输出适当的错误(如果合适)非常简单


checkfor()
{
  pattern="$1"
  errormsg="$2"

  sedpattern="s/$pattern//g"

  chop=$(echo "$password" | sed -E $sedpattern)

  if [ "$password" == "$chop" ] ; then
    echo "Fail: You haven't used any ${errormsg}."
  fi
}

然后你可以像这样调用它


checkfor "[[:lower:]]" "lowercase letters"
checkfor "[[:upper:]]" "uppercase letters"
checkfor "[[:digit:]]" "digits"
checkfor "[[:punct:]]" "punctuation"

简洁明了。那么,让我们在命令行中使用密码“B3g”快速测试一下这个脚本


$ sh checkpw.sh
Enter password:
You entered B3g
Fail: You haven't used any punctuation.

一个准确的错误消息。当然,在最终脚本中,您不会回显输入的密码,因为从隐私和安全的角度来看,这不太好。

要测试长度,很容易使用 wc -c,但是 shell 脚本中有一种特殊的变量引用格式,也可以访问字符数:${#xxx}。例如,考虑以下简短的代码片段


$ test="Hi Mom"
$ echo ${#test}
6

考虑到这一点,测试指定的示例密码是否至少有八个字符长很容易编码为


if [ ${#password} -lt $minlength ] ; then
  echo "Fail: Password must be $minlength characters."
fi

在脚本顶部将 $minlength 变量设置为合理的值。我建议 8 作为良好的最小长度。

我在这里设计的脚本纯粹是为了提供信息,如果您使用像“kitty”这样糟糕的密码,您将看到很多错误


$ sh checkpw.sh
Enter password:
You entered kitty
Fail: You haven't used any uppercase letters.
Fail: You haven't used any digits.
Fail: You haven't used any punctuation.
Fail: Password must be at least 8 characters.

如果您愿意,可以进行很多调整,从设置一个计数器来判断是否有多个错误,并在所有测试成功后显示成功的消息,到让脚本在遇到第一个错误条件时立即退出。

现在,有了这个脚本作为一个简单的密码测试工具,很容易要求每个用户设置一个新的、安全的密码,并通过所有这些测试。

新帐户创建

监视系统的另一种方法是在每次创建新帐户时收到通知。无论您是否是唯一的管理员,这都不应该经常发生。但是,如果您是唯一的管理员,并且这种情况在您不知情的情况下发生?危险了,威尔·罗宾逊!

在过去,加盐(加密)密码是存储在 /etc/passwd 中的一部分,但现代系统将加密数据更安全地隐藏在 /etc/shadow 中。但是,用户帐户仍然显示在 /etc/passwd 文件中,因此您可以将其用作这个简单脚本的基础。

这个想法是,您将抓取所有用户帐户名并将它们保存到一个隐藏文件中,并且每次运行脚本时,您都会将最新的帐户名与保存的帐户名进行比较。如果有新条目,那就糟了!

当然,这种方法绝对不稳健,我不会信任使用这种轻量级工具的信用报告数据服务器,但这仍然是一个值得考虑的有趣脚本。

让我们看看如何从文件中提取用户帐户名


$ cat /etc/passwd | cut -d: -f1
root
bin
daemon
adm
. . .

关键在于 cut 命令!-d 标志指定字段分隔符,-f1 请求仅输出第一个字段。给定像这样的输入行


root:x:0:0:root:/root:/bin/bash

您可以看到输出只变成了帐户名。这个脚本可以比较完整的文件——实际上,甚至有一个 Linux 命令可以完成这项工作——但是如果您用户更改了用户名,但其他方面保持帐户不变,您不希望得到误报。此外,我喜欢干净、可读的输出,这就是它将产生的。

这是完整的脚本


#!/bin/sh

# watch accounts - keep an eye on /etc/passwd,
#                  report if accounts change

secretcopy="$HOME/.watchdb"
tempfile="$HOME/.watchdb.new"
passwd="/etc/passwd"
compare=0               # by default, don't compare

trap "/bin/rm -f $tempfile" 0

if [ -s "$secretcopy" ] ; then
  lastrev="$(cat $secretcopy)"
  compare=1
fi

cat $passwd | cut -d: -f1 > $tempfile

current="$(cat $tempfile)"

if [ $compare -eq 1 ] ; then
  if [ "$current" != "$lastrev" ] ; then
    echo "WARNING: password file has changed"
    diff $secretcopy $tempfile | grep '^[<>]' |
        sed 's/</Removed: /;s/>/Added:/'
  fi
else
   mv $tempfile $secretcopy
fi

exit 0

总的来说,这是一个非常简单的脚本。仔细检查会发现,帐户的秘密副本将保存在 $HOME/.watchdb 中。trap 命令用于确保在脚本完成时删除临时文件。

$compare 变量与您第一次运行脚本的情况有关。在这种情况下,没有 .watchdb,因此无法使用它进行测试或比较。否则,该文件的内容将存储在局部变量 $secretcopy 中,并且 $compare 设置为 1。

第二块是实际的比较,唯一有趣的部分是调用 diff 来比较两个文件


diff $secretcopy $tempfile | grep '^[<>]' |
   sed 's/</Removed: /;s/>/Added:/'

diff 默认输出用于旧版 ed 编辑器的命令,因此您可以通过仅考虑以 < 或 > 开头的行来屏蔽它。这些行表示仅在旧版本密码文件中的条目(在当前的实时副本中已删除)以及仅在新的实时版本中的条目(添加的帐户)。

就是这样。让我们运行一次来创建秘密存档文件,然后我将更改密码文件以删除一个帐户并创建另一个帐户,然后再次运行该脚本


$ sh watchaccounts.sh
$
edit password file
$ sh watchaccounts.sh
WARNING: password file has changed
Removed: johndoe
Added: hack3r666

不错吧?现在,您可以考虑对脚本进行一些有用的补充,特别是加密 .watchdb 以提高安全性,以及添加提示或命令标志以在报告更改后更新秘密密码文件。

Dave Taylor 在 UNIX 和 Linux 系统上破解 shell 脚本已经很长时间了。他是 Learning Unix for Mac OS XWicked Cool Shell Scripts 的作者。您可以在 Twitter 上找到他 @DaveTaylor,您可以通过他的技术问答网站联系他:Ask Dave Taylor

加载 Disqus 评论