Сценарий:
В скрипте bash я должен проверить, является ли пароль, заданный пользователем, действительным паролем пользователя.
Т.е., предположим, у меня есть пользователь A с паролем PA .. В сценарии я попросил пользователя A ввести свой пароль, так как проверить, действительно ли введенная строка является его паролем? ...
Так как вы хотите сделать это в сценарии оболочки, пара вкладов в Как проверить пароль в Linux? (на Unix. SE, , предложенный А.Б.), особенно важен:
/etc/shadow
дает часть решения. Комментарий mkpasswd
, присутствующей в Debian (и Ubuntu).Чтобы вручную проверить, действительно ли строка является паролем какого-то пользователя, вы должны хэшировать её тем же хэш-алгоритмом, что и в теневой записи пользователя, той же солью , что и в теневой записи пользователя. Затем его можно сравнить с хранящимся там хэшем пароля.
Я написал полный рабочий скрипт, демонстрирующий, как это сделать.
chkpass
, то можете запустить chkpass user
и он прочитает строку со стандартного входа и проверит, есть ли пароль user
.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
Считать ли такой подход безопасным и приемлемым или нет, зависит от деталей вашего случая использования, которые вы не предоставили (на момент написания этой статьи).
Хотя я пытался проявлять осторожность при написании этого сценария, он не был должным образом проверен на наличие уязвимостей в системе безопасности . Он предназначен для демонстрации, и был бы "альфа" программным обеспечением, если бы был выпущен в рамках проекта. Более того...
Из-за ограничений в том, как mkpasswd
принимает данные о соли, этот сценарий содержит известный дефект безопасности , который вы можете считать или не считать приемлемым в зависимости от случая использования. По умолчанию пользователи Ubuntu и большинства других систем GNU/Linux могут просматривать информацию о процессах, выполняемых другими пользователями (включая root), в том числе аргументы командной строки. Ни входные данные пользователя, ни хранимый в памяти хэш пароля не передаются в качестве аргумента командной строки ни одной внешней утилите. Но соль salt, извлечённая из базы данных shadow
, дана в качестве аргумента командной строки mkpasswd
, так как только так утилита принимает соль в качестве входного параметра.
Если
www-data
) запускают свой код, или /proc
), может проверить аргументы командной строки в mkpasswd
, так как она запущена этим сценарием, то они могут получить копию соли пользователя из базы данных shadow
. Они могут иметь возможность угадать , когда эта команда выполняется, но иногда это достижимо.
Атакующий с вашей солью не так плох, как атакующий с вашей солью и хэшем , но это не идеально. Соль не дает достаточно информации для того, чтобы кто-то мог узнать ваш пароль. Но она позволяет кому-то генерировать радужные таблицы или предварительно вычисленные хэши словарей , специфичные для данного пользователя в данной системе. Изначально это бесполезно, но если ваша безопасность будет скомпрометирована позднее и будет получен полный хэш, то можно быстрее взломать , чтобы получить пароль пользователя до того, как он получит возможность изменить его.
Таким образом, этот недостаток безопасности является усугубляющим фактором в более сложном сценарии атаки, а не полностью используемой уязвимостью. И данную ситуацию можно считать надуманным. Однако я не рекомендую для общего, реального использования любой метод, который приводит к утечке любых непубличных данных из /etc/shadow
для не корневого пользователя.
Вы можете полностью избежать этой проблемы, написав:
Если вы позволите недоверенному пользователю запустить этот скрипт от имени root или запустить любой процесс от имени root, который вызывает этот скрипт, будьте внимательны. Изменяя окружение, они могут сделать этот сценарий-- или любой сценарий, который работает как root-до что-нибудь . Если вы не можете предотвратить это, вы не должны разрешать пользователям повышенные привилегии для запуска shell-скриптов.
Смотрите 10.4. Shell Scripting Languages (sh и csh Derivatives) в книге Дэвида Уилера Secure Programming for Linux and Unix HOWTO для получения дополнительной информации по этому вопросу. В то время как его презентация сфокусирована на скриптах setuid, другие механизмы могут стать жертвами некоторых из тех же проблем, если они неправильно дезинфицируют окружающую среду.
shadow
.Для работы этого скрипта пароли должны быть затенены (т.е, их хэши должны быть в отдельном файле /etc/shadow
, который может читать только корень, а не /etc/passwd
).
Так всегда должно быть в Ubuntu. В любом случае, при необходимости скрипт может быть тривиально расширен для чтения хэшей паролей из passwd
, а также shadow
.
IFS
при модификации этого скрипта.Я установил IFS=$
в начале, так как три данных в хэш-поле записи shadow разделены на $
.
$
, поэтому тип хэша и соль "${ент[1]}"
и "${ент[2]}"
, а не "${ент[0]}"
и "${ент[1]}"
, соответственно. Единственные места в этом сценарии, где $IFS
определяет, как оболочка разделяется или объединяет слова, это
, когда эти данные разбиваются на массив, инициализируя его из нецитируемой $(
)
команды, подставляемой внутрь:
set -f; ent=($(getent shadow "$1" | awk -F: '{print $2}')); set +f
, когда массив восстанавливается в строку для сравнения с полным полем из shadow
, выражение "${ent[*]}"
в:
, если [[[ "${ent[*]}"] = "$(mkpasswd -sm $hashtype -S "${ent[2]}" <<"$pass")". ]]
Если вы модифицируете сценарий и хотите, чтобы он выполнял разделение слов (или объединение слов) в других ситуациях, вам нужно будет установить IFS
на разные значения для разных команд или разных частей сценария.
Если вы не помните об этом и предполагаете, что $IFS
установлен на обычный пробел ($' \t\n'
), то в конечном итоге вы можете заставить ваш сценарий вести себя довольно странно.
Вы можете неправильно использовать sudo
для этого. sudo
имеет опцию -l
для проверки привилегий sudo, которыми обладает пользователь, и -S
для чтения в пароле из stdin. Однако, независимо от уровня привилегий пользователя, в случае успешной аутентификации -S
возвращается со статусом выхода 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
является никогда
Другие проблемы включают (спасибо Элиа):
/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/
: шифрование пароляh4vuzZVBwIE1Z6vT7N: 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