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

15

Сценарий:

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

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

Maythux
источник
Что вы имеете в виду с действительным паролем? Хотите проверить, действительно ли это пароль пользователя?
AB
@AB действительный пароль = пароль для входа в систему, как хотите, чтобы позвонить ... Я имею в виду пароль для этого пользователя
Maythux,
@AB Я знаю, что это будет дыра в безопасности, но здесь вы уже знаете имя пользователя ... Другими словами, это просто проверка, если пароль для этого пользователя ..
Maythux,
3
Вот ваш ответ: unix.stackexchange.com/a/21728/107084
AB
Раствор на основе expectв stackoverflow.com/a/1503831/320594 .
Хайме Хаблутцель

Ответы:

14

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

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

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

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

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

Элия ​​Каган
источник
8

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

Мур
источник
2
Этот способ выглядит отлично. Хотя он не будет работать в системах с необычными sudoконфигами (как вы говорите) и загромождает /var/log/auth.logзаписи каждой попытки, это быстро, просто и использует существующую утилиту, а не изобретает колесо (так сказать). Я предлагаю установить IFS=для read, чтобы предотвратить ложные успехи для пользовательского ввода с пробелами и не дополненными паролями, а также ложные сбои для пользовательского ввода и паролей с пробелами. (У моего решения была похожая ошибка.) Вы также можете объяснить, как его следует запускать как пользователь, чей пароль проверяется.
Элия ​​Каган
@EliahKagan регистрируется каждая неудачная попытка, но да, вы правы во всем остальном.
Муру
Успешные аутентификации также регистрируются. sudo -lзаставляет запись быть написанной с " COMMAND=list". Между прочим (хотя это и не связано), хотя объективно лучше это делать, использование строки здесь вместо документа здесь позволяет достичь той же цели безопасности и является более компактным. Поскольку скрипт уже использует bashisms read -sи &>, использование if sudo -lS &>/dev/null <<<"$PASSWD"; thenне менее переносимо. Я не предлагаю вам изменить то, что у вас есть (это нормально). Скорее, я упоминаю об этом, чтобы читатели знали, что у них тоже есть эта опция.
Элия ​​Каган
@EliahKagan ах. Я думал, sudo -lчто отключил что-то - возможно, он сообщал, когда пользователь пытается выполнить команду, что ему не разрешено.
Муру
@EliahKagan Действительно. Я использовал эти строки раньше . Я работал здесь по заблуждению, что привело к наследственности, но потом я все равно решил оставить их.
Муру
4

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

Пароли пользователей хранятся в /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
кос
источник
1
sudo getent shadow>> sudo grep .... В остальном приятно. Это то, о чем я думал изначально, потом стало лень.
Муру
@ Муру Спасибо. Определенно чувствует себя лучше, так что обновляется, но все же я не уверен, что в этом случае getent+ grep+ cut> grep+cut
kos
1
Ик. getentмогу сделать поиск для вас. Я взял на себя смелость редактирования.
Муру
@ Муру Да, у тебя должно быть на самом деле, теперь я вижу смысл!
Кос
2
Я думаю, что это на самом деле довольно практично, хотя я могу быть предвзятым: это тот же метод, который я использовал. Ваша реализация и презентация совершенно разные, хотя - это, безусловно, ценный ответ. В то время как я использовал mkpasswdдля создания хеша из пользовательского ввода и существующей соли (и включал дополнительные функции и диагностику), вы вместо этого написали простую программу на Python, реализующую mkpasswdнаиболее важные функции, и использовали ее. Я рекомендую вам установить IFS=при чтении ввода пароля, и цитаты ${password}с ", поэтому попытки ввода пароля с пробелами не нарушают сценарий.
Элия ​​Каган