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

Сценарий:

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

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

18
задан 7 May 2015 в 14:46

3 ответа

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

  • rozcietrzewiacz ответ на генерацию хэша пароля, который совпадает с записью в /etc/shadow дает часть решения. Комментарий
  • Daniel Alder объясняет разный синтаксис команды mkpasswd , присутствующей в Debian (и Ubuntu).

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

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

  • Если вы назовёте его chkpass, то можете запустить chkpass user и он прочитает строку со стандартного входа и проверит, есть ли пароль user.
  • Установите пакет whois Install 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), в том числе аргументы командной строки. Ни входные данные пользователя, ни хранимый в памяти хэш пароля не передаются в качестве аргумента командной строки ни одной внешней утилите. Но соль salt, извлечённая из базы данных shadow, дана в качестве аргумента командной строки mkpasswd, так как только так утилита принимает соль в качестве входного параметра.

Если

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

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

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

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

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

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

Будьте внимательны при вызове этого скрипта.

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

Смотрите 10.4. Shell Scripting Languages (sh и csh Derivatives) в книге Дэвида Уилера Secure Programming for Linux and Unix HOWTO для получения дополнительной информации по этому вопросу. В то время как его презентация сфокусирована на скриптах setuid, другие механизмы могут стать жертвами некоторых из тех же проблем, если они неправильно дезинфицируют окружающую среду.

Other Notes

Он поддерживает только чтение хэшей из базы данных 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'), то в конечном итоге вы можете заставить ваш сценарий вести себя довольно странно.

15
ответ дан 8 May 2015 в 00:46

Вы можете неправильно использовать 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 или других инструментов для проверки процессов.

10
ответ дан 8 May 2015 в 00:46

Другой метод (наверное, более интересный для теоретического содержания, чем для практического применения).

Пароли пользователей хранятся в /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
5
ответ дан 8 May 2015 в 00:46

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

Похожие вопросы: