Как проверить, что введенный пароль является действительным паролем для этого пользователя?
Сценарий:
В скрипте bash я должен проверить, является ли пароль, заданный пользователем, действительным паролем пользователя.
Т.е. предположим, что у меня есть пользователь A с паролем PA.. В сценарии я попросил пользователя A ввести свой пароль, так как проверить, действительно ли введенная строка является его паролем?...
5 ответов
Поскольку вы хотите сделать это в сценарии оболочки, пара вкладов в Как проверить пароль в Linux? (на Unix.SE, предложенный AB) особенно актуальны:
- Ответ rozcietrzewiacz на создание хэша пароля, который соответствует записи в
/etc/shadow
дает часть решения. - Комментарий Даниэля Алдера объясняет другой синтаксис
mkpasswd
команда присутствует в Debian (и Ubuntu).
Чтобы вручную проверить, является ли строка действительно паролем какого-либо пользователя, вы должны хешировать ее с помощью того же алгоритма хеширования, что и в теневой записи пользователя, с той же солью, что и в теневой записи пользователя. Затем его можно сравнить с хешем пароля, хранящимся там.
Я написал полный рабочий скрипт, демонстрирующий, как это сделать.
- Если вы назовете это
chkpass
, Вы можете запуститьchkpass user
и он будет читать строку из стандартного ввода и проверить, если этоuser
пароль. - Установите 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 [пользователь] [пароль]