Pull to refresh

Пароли для ленивых

Reading time 4 min
Views 5.3K
Недавно один из провайдеров, где я арендую выделенные сервера, был взломан. Нанесенный ущерб был достаточно невелик, благо бэкапы делаются ежедневно. С другой стороны — менять пароли на несколько десятков аккаунтов бессонной ночью перед отпуском это тоже занятие не из радостных.

Идея автоматизации смены паролей свербила мозг довольно долго, несколько хабратопиков подтолкнули в нужном направлении. Идея была простой — менять пароли насколько можно часто (например — каждый день), чтобы даже если кто-то и заполучил пароль из переписки двухдневной давности — с ним уже ничего нельзя было сделать. В идеале — система должна генерировать и менять пароль сразу перед ежедневным бэкапом каждого аккаунта.

Легко сказать — автоматизировать смену паролей на линукс-сервере — я этот ваш bash в глаза не видел (не считая башорга, конечно), я вообще совсем другими вещами в этой жизни занимаюсь. Однако методика разбивания задачи на простейшие шаги и гугл помогли решить задачу буквально за один вечер.

Шаг первый — получаем MD5 hash от какой-нибудь строки, например от сегодняшней даты:

echo -n $(date +%F) | md5sum

Шаг второй — присваиваем это удовольствие какой-нибудь переменной, лучше сразу 2 раза, чтобы длиннее получилось (потом покажу зачем):

a=$(echo -n $(date +%F) | md5sum) && a=${a:0:32}${a:0:32}

Шаг третий — из получившейся строки длиной в 64 символа выбираем, например, 27 символов с позиции номер сегодняшний день (т.е. если сегодня третье августа, то начиная с третьей позиции). Таким образом на 31 день месяца мы гарантированно укладываемся в строку из 64 символов (31 + 27 = 58). Можно выбрать любое другое число, главное — уложится в 64 символа или сделать исходную строку длиннее.

hs=${a:$(date +%d):27}

Следующий, четвертый шаг — получить список всех аккаунтов на сервере. Меня устроил такой вариант (если кто-то предложит более точный — буду благодарен):

cat /etc/passwd | grep "/home" | cut -d: -f1

Самым сложным оказался пятый шаг — перебор строк в получившемся списке юзеров. После получаса экспериментов у меня заработал следующий (промежуточный) вариант, который выводил мне имя юзера и получившуюся заготовку для пароля:

lines=($(cat /etc/passwd | grep "/home" | cut -d: -f1))

for i in "${lines[@]}"
do
echo "$i" : $hs
done


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

salt="abracadabra"
lines=($(cat /etc/passwd | grep "/home" | cut -d: -f1))

for i in "${lines[@]}"
do
echo "$i" : $hs
s=$salt"$i"$hs
newhash=$(echo -n "$s" | md5sum)
done


Иными словами — к собранному из текущей даты хэшу мы добавляем имя аккаунта и «соль», после чего снова рассчитываем хэш от результата. Добавляя известную только нам «соль» мы «ломаем» структуру алгоритма, а добавляя еще и имя аккаунта мы делаем хэш результата уникальным для каждого из них. Раз уж мы собираемся менять пароли каждый день — дадим разгулятся нашей паранойе и не будем делать все пароли одинаковыми :)

В процессе поиска удобной команды смены пароля для юзера я наткнулся на упоминание о том, что у bash все ходы записаны. И действительно, файлик .bash_history самым наглым образом показал мне куски моих экспериментов. Поэтому пришлось еще изучить команду history и ключ -с в качестве седьмого шага.

Восьмой шаг, что очевидно, выглядел так:

cmd=`echo "$i":$newhash | chpasswd`
echo $cmd


Девятый, и последний шаг был — собрать все это вместе и добавить в cron сразу перед запуском ежедневного бэкапа. Скрипт получился примерно такой:

#!/bin/bash
#
# Prep some salt
salt="abracadabra"

# Calculate hash from today's date and add it to itself
a=$(echo -n $(date +%F) | md5sum) && a=${a:0:32}${a:0:32}

# Strip 27 characters starting from the character number of today's day of month
hs=${a:$(date +%d):27}

# Grab users into array
lines=($(cat /etc/passwd | grep "/home" | cut -d: -f1))

# For each user create a new pass with salt + user name + hash
#
for i in "${lines[@]}"
do
s=$salt"$i"$hs
newhash=$(echo -n "$s" | md5sum)
newhash=${newhash:0:27}
# echo "$i" : $salt"$i"$hs : $newhash # print this out if you want to
cmd=`echo "$i":$newhash | chpasswd`
echo $cmd
done
history -c


Явно видные недостатки:
— сильная алгоритмизация, т.е. в голове такой пароль не удержишь
— используются только маленькие буквы и цифры, т.е. словарь для перебора мал
— под раздачу паролей могут попасть аккаунты, которым менять пароли не нужно (сразу не соображу какие, но например на одном из серверов у меня есть shell аккаунт для использования PuTTy в качестве прокси — я бы не хотел чтобы у этого аккаунта пароль менялся каждый час).

Достоинства:
— пароль можно менять как угодно часто (если есть желание — можно вместо даты использовать дату/время и менять пароль раз в минуту или даже в секунду)
— частая смена и большая длина компенсируют простоту словаря (только маленькие буквы и цифры)
— зная алгоритм и «соль» пароль легко сгенерировать на любой машине не боясь за его сохранность — все равно на следующий день/час/минуту :) пароль будет уже другой
— простота автоматизации, настраиваемость (всегда можно подкрутить алгоритм под себя) и как результат — значительная экономия времени на смене паролей для большого числа аккаунтов.

Вопросы:
— можно ли подобным образом генерировать более сложные пароли, с использованием разных регистров и служебных символов?
— какие еще минусы есть у данной реализации?
— какие еще плюсы есть у данной реализации?
— можно ли указанные минусы скомпенсировать или вообще от них избавится (например чтобы нейтрализовать изменение паролей на каких-то аккаунтах можно создать стоп-лист и выбрасывать из общего списка аккаунты, которые есть в стоп-листе)?

UPD: Самые первые комментарии навели на необходимость пояснения. Клиенты изредка сами заходят в свой аккаунт (например — в cPanel) и именно им удобнее логин/пароль, а не логин по сертификату. Шелл для этих аккаунтов, как правило, отключен вообще (за очень редкими исключениями).
Tags:
Hubs:
+5
Comments 22
Comments Comments 22

Articles