Как проверить, что введенный пароль является действительным паролем для этого пользователя?

Сценарий:

В скрипте bash я должен проверить, является ли пароль, заданный пользователем, действительным паролем пользователя.

Т.е. предположим, что у меня есть пользователь A с паролем PA.. В сценарии я попросил пользователя A ввести свой пароль, так как проверить, действительно ли введенная строка является его паролем?...

5 ответов

Решение

Поскольку вы хотите сделать это в сценарии оболочки, пара вкладов в Как проверить пароль в Linux? (на Unix.SE, предложенный AB) особенно актуальны:

Чтобы вручную проверить, является ли строка действительно паролем какого-либо пользователя, вы должны хешировать ее с помощью того же алгоритма хеширования, что и в теневой записи пользователя, с той же солью, что и в теневой записи пользователя. Затем его можно сравнить с хешем пароля, хранящимся там.

Я написал полный рабочий скрипт, демонстрирующий, как это сделать.

  • Если вы назовете это chkpass, Вы можете запустить chkpass user и он будет читать строку из стандартного ввода и проверить, если это user пароль.
  • Установите 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

Примечания по безопасности

Это может быть не правильный подход.

Вопрос о том, следует ли считать такой подход безопасным или иным образом подходящим, зависит от деталей вашего варианта использования, которые вы не предоставили (на момент написания статьи).

Это не было проверено.

Хотя я пытался соблюдать осторожность при написании этого сценария, он не был должным образом проверен на наличие уязвимостей в безопасности. Он предназначен для демонстрации и будет "альфа" программным обеспечением, если будет выпущен как часть проекта. Более того...

Другой пользователь, который "смотрит", может обнаружить соль пользователя.

Из-за ограничений в том, как mkpasswd принимает солидные данные, этот сценарий содержит известный недостаток безопасности, который вы можете или не можете считать приемлемым в зависимости от варианта использования. По умолчанию пользователи в Ubuntu и большинстве других систем GNU/Linux могут просматривать информацию о процессах, выполняемых другими пользователями (включая root), включая их аргументы командной строки. Ни пользовательский ввод, ни сохраненный хэш пароля не передаются в качестве аргумента командной строки какой-либо внешней утилите. Но соль, извлеченная из shadow базы данных, задается в качестве аргумента командной строки для mkpasswd, поскольку это единственный способ, которым утилита принимает соль в качестве входных данных.

Если

  • другой пользователь в системе, или
  • любой, кто имеет возможность создать любую учетную запись пользователя (например, www-data) запустить их код, или
  • любой, кто в противном случае может просматривать информацию о запущенных процессах (в том числе вручную проверяя записи в /proc)

может проверить аргументы командной строки mkpasswd так как он запускается этим сценарием, они могут получить копию соли пользователя из shadow база данных. Возможно, им придется угадывать, когда выполняется эта команда, но это иногда достижимо.

Атакующий с вашей солью не так плох, как атакующий с вашей солью и хэшем, но это не идеально. Соль не дает достаточно информации, чтобы кто-то мог найти ваш пароль. Но это позволяет кому-то генерировать радужные таблицы или предварительно вычисленные хеши словаря, специфичные для этого пользователя в этой системе. Это изначально бесполезно, но если ваша безопасность будет скомпрометирована на более позднем этапе и будет получен полный хэш, его можно будет взломать быстрее, чтобы получить пароль пользователя, прежде чем он получит возможность изменить его.

Таким образом, этот недостаток безопасности является обостряющим фактором в более сложном сценарии атаки, а не полностью уязвимой уязвимостью. И вы могли бы рассмотреть вышеуказанную ситуацию надуманным. Но я не хочу рекомендовать любой метод для общего, реального использования, который пропускает любые непубличные данные из /etc/shadow для пользователя без полномочий root.

Вы можете полностью избежать этой проблемы:

  • написание части вашего скрипта на Perl или другом языке, который позволяет вам вызывать функции C, как показано в ответе Жиля на связанный вопрос Unix.SE, или
  • написание всего сценария / программы на таком языке, а не использование bash. (Судя по тому, как вы пометили вопрос, похоже, вы предпочитаете использовать bash.)

Будьте осторожны, как вы называете этот сценарий.

Если вы позволяете ненадежному пользователю запускать этот сценарий от имени пользователя root или запускать любой процесс от имени пользователя root, который вызывает этот сценарий, будьте осторожны. Изменяя среду, они могут заставить этот скрипт - или любой скрипт, который запускается от имени root - делать что угодно. Если вы не можете предотвратить это, вы не должны разрешать пользователям повышенные привилегии для запуска сценариев оболочки.

Смотрите 10.4. Языки сценариев оболочки (производные sh и csh) в книге Дэвида А. Уилера по безопасному программированию для Linux и Unix HOWTO для получения дополнительной информации об этом. Хотя его презентация сосредоточена на сценариях 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'), вы могли бы в конечном итоге заставить свой скрипт вести себя довольно странно, на первый взгляд.

Вы можете неправильно использовать 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
  • и т.п.

Другие проблемы включают (спасибо Элия):

  • Неправильные попытки будут зарегистрированы /var/log/auth.log
  • sudo должен быть запущен как пользователь, для которого вы аутентифицируетесь. Если у вас нет sudo привилегии, так что вы можете запустить sudo -u foo sudo -lS это означает, что вам придется запускать скрипт как целевой пользователь.

Итак, причина, по которой я использовал здесь-документы, состоит в том, чтобы препятствовать подслушиванию. Переменная, используемая как часть командной строки, легче обнаружить с помощью 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:::
  • 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

После некоторых поисков я обнаружил простой способ проверить правильность пароля пользователя с помощью . Вот небольшой демонстрационный сценарий. Вы можете сохранить его в файл, добавить разрешения на выполнение, а затем вызвать его, используя ./pw_check.sh username.

      #!/bin/bash

# This script accepts a username as a CLI argument,
# requests the password for that user, and
# then tests the validity of the given password for the given user

function show_usage() {
    echo "Usage: ${0} username"
    exit 1
}

if [[ $# -ne 1 ]]; then
    show_usage
fi

username=$1

# IFS=<space> - set IFS to an empty string (no character will be used to split,
#   therefore no splitting will occur)
#   so that read will read the entire line 
#   and see it as one word that will be assigned to the line variable
# -r - do not allow backslashes to escape any characters
# -s - do not echo input coming from a terminal
# -p - output the string PROMPT without a trailing newline before attempting to read
IFS= read -r -s -p "Enter ${username}'s password: " password;
echo -e "\n";

# Test that the given password is correct by trying to run a command using it
echo $password | /bin/su --command true - ${username};
retval=$?;

if [ $retval -eq 0 ]; then
    echo "Yay - the password is valid!";
else
    echo "Password is not valid";
fi

По сути, он принимает имя пользователя в качестве аргумента, запрашивает пароль этого пользователя, а затем пытается запустить команду от имени этого пользователя, используя su. Затем мы проверяем возвращаемое значение попытки запустить trueвведите команду от имени данного пользователя и используйте возвращаемое значение для определения успеха/неудачи и, следовательно, достоверности данного пароля.

Для этого я использую следующую программу, которую я создал:

passwordIsValid [пользователь] [пароль]

Другие вопросы по тегам