Як перевірити, чи введений пароль є дійсним паролем для цього користувача?


15

Сценарій:

У сценарії bash я повинен перевірити, чи вказаний користувачем пароль є дійсним паролем користувача.

Припустимо, у мене є користувач A з паролем PA .. У скрипті я попросив користувача A ввести його пароль, тож як перевірити, чи введена рядок справді є його паролем? ...


Що ви маєте на увазі з дійсним паролем? Хочете перевірити, чи дійсно це пароль користувача?
AB

@AB дійсний пароль = пароль для входу, що б ви хотіли зателефонувати ... Я маю на увазі його пароль для цього користувача
Maythux

@AB Я знаю, що це буде отвір у безпеці, але тут ви вже знаєте ім'я користувача ... Іншими словами, це просто тест, якщо пароль для цього користувача ..
Maythux

3
Ось ваша відповідь: unix.stackexchange.com/a/21728/107084
AB

Розчин на основі expectв stackoverflow.com/a/1503831/320594 .
Хайме Хаблуцель

Відповіді:


15

Оскільки ви хочете зробити це в сценарії оболонки, декілька внесків у розділі Як перевірити пароль за допомогою Linux? (на Unix.SE , запропонований AB ) особливо актуальні:

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

Я написав повний робочий сценарій, який демонструє, як це зробити.

  • Якщо ви дасте ім'я 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до некористувального користувача.

Ви можете повністю уникнути цієї проблеми:

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

Будьте уважні, як ви називаєте цей сценарій.

Якщо ви дозволите ненадійному користувачу запустити цей скрипт як root або запустити будь-який процес як root, який викликає цей скрипт, будьте обережні . Змінюючи середовище, вони можуть зробити цей скрипт - або будь-який сценарій, що працює як root - робити все, що завгодно . Якщо ви не можете запобігти цьому, ви не повинні дозволяти користувачам підвищених привілеїв для запуску скриптів оболонки.

Див. 10.4. Мови сценаріїв оболонки (похідні та шш-шш) в Безпечному програмуванні Девіда А. Уілера для Linux та Unix HOWTO для отримання додаткової інформації про це. Хоча його презентація зосереджена на встановлених сценаріях, інші механізми можуть стати здобиччю деяких тих же проблем, якщо вони неправильно не санітують навколишнє середовище.

Інші примітки

Він підтримує хеші для читання лише з 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". Btw (не пов’язана між собою), хоча це об'єктивно краще не робити, використовуючи рядок тут замість документа тут, досягається та сама ціль безпеки і є більш компактною. Оскільки сценарій вже використовує башизми read -sі &>, використовуючи, if sudo -lS &>/dev/null <<<"$PASSWD"; thenвін не менш портативний. Я не пропоную вам змінити те, що у вас є (це просто чудово). Швидше, я згадую це, щоб читачі знали, що у них є і такий варіант.
Елія Каган

@EliahKagan ах. Я подумав, що sudo -lщось відключено - можливо, це було повідомлення про те, коли користувач намагається виконати команду, яку їм заборонено.
муру

@EliahKagan Дійсно. Я раніше використовував герестини . Я працював тут з оманою, що призвело до гередоків, але тоді я вирішив їх утримати.
муру

5

Інший метод (напевно, цікавіший за своїм теоретичним змістом, ніж для практичних застосувань).

Паролі користувача зберігаються в /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 .... Інакше приємно. Це те, що я спочатку думав, потім занадто лінивий.
муру

@muru Спасибі Однозначно відчуває себе приємніше, настільки оновлений, але все ж я не впевнений, чи в цьому випадку getent+ grep+ cut> grep+cut
kos

1
Еек. getentможе здійснити пошук для вас. Я взяв на себе сміття редагувати.
муру

@muru Так, ти мав би насправді, тепер я бачу сенс!
kos

2
Я думаю, що це насправді цілком практично, хоча я можу бути упередженим: це той самий метод, який я використовував. Ваша реалізація та презентація є зовсім різними - це, безумовно, цінна відповідь. В той час, як я використовував mkpasswdхеш із введення користувачем і наявної солі (і включав додаткові функції та діагностику), ви замість цього зашифрували просту програму Python, що реалізує mkpasswdнайважливіший функціонал, і використали це. Я рекомендую вам встановити, IFS=читаючи введення пароля, і цитувати ${password}їх ", щоб спроби пароля з пробілом не порушували сценарій.
Елія Каган
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.