Набор заметок. Производительность и защита, от сайта до системы

в 17:23, , рубрики: framework, linux, systemd, web-разработка, авторизация, базы данных, Веб-разработка, защита, Песочница, производительность, сервер, метки: , , , , , , , ,

Краткий анонс

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

Приступим

Собственно обычно почти каждая статья найденная мной в интернете рассказывает то ли об механизме регистрации/авторизации на сайте, то ли о безопасности такого механизма. Эта запись достаточно условная и возможно включает основные только моменты логики механизма регистрации/авторизации на сайте.

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

Храним пароль в зашифрованном виде, но добавляя несколько случайных символов, уникальных для каждого пользователя (так называемая соль). А еще лучше хранить двойной md5 с солью. Взломать такой вышеупомянутым способом практически невозможно. В таблице user нам потребуется два поля:
1. поле salt для хранения соли;
2. поле password для хранения хеша от md5(md5(пароль)+соль).

Например, авторизация пользователя:

// $user – информация о пользователе из таблицы user
if (md5(md5($_POST['password']).$user['salt']) == $user['password']) {
// проверка прошла успешно. логиним!
}

Где брать соль? Необходимо в скрипте регистрации генерировать соль для каждого нового пользователя. Для этого можно воспользоваться следующей функцией:

function generateSalt() {
$salt = '';
$length = rand(5,10); // длина соли (от 5 до 10 символов)
for($i=0; $i<$length; $i++) {
$salt .= chr(rand(33,126)); // символ из ASCII-table
}
return $salt;
}

Различные варианты соления в известных движках:

md5($pass.$salt) — применяется в Joomla
md5(md5($pass).$salt) — применяется в vBulletin
md5(md5($salt).md5($pass)) — применяется в новых IP.Board

Данный метод вносит существенное увеличение безопасности в случае утечки БД на сторону.

2) Метод подбора пароля так же существенно и методично применяется для получения привилегий админ. пользователя. Но чтобы такой метод не приносил особого наслаждения (многие вспомнят капчу и верно сделают, но есть тенденция к тому что какой бы механизм капчи не делали, всегда находятся алгоритмы их обхода/опознавания и т.д.). Но чтобы превратить подбор пароля в адски долгий и мучительный процесс достаточно в функции авторизации добавить задержку в виде функции sleep() до самого кода проверки данных пользователя. Кто-то добавляет циклы и т.д., но не думаю что стоит городить велосипеды. Каких-то 10 секунд для отдельного пользователя не существенно, зато при переборе каждая попытка растягивается на +10 секунд. При этом 1000 попыток это уже 10 000 секунд, что будет уже почти три часа.
Но в данном случае нужно опять же учесть распараллеливание запросов на авторизацию. То есть по сути человек может сразу кидать по несколько запросов на авторизацию, что существенно ускорит подбор. По этому нужно ограничить такую возможность, но так чтобы несколько пользователей сидящих из-под одного провайдера все же могли залогиниться. По сути мы должны держать каким-то образом в памяти количество IP которые только что авторизовывались и удалять их по прошествии определенного таймаута. То есть по сути к примеру авторизация в течении 15 секунд с одного IP, в трех экземплярах. Если три экземпляра одного и того же IP присутствуют записями в БД, засыпаем на несколько секунд функцией sleep и снова перепроверяем, после чего выдаем либо страницу с ошибкой «Превышение количества попыток авторизации с одного IP. Попробуйте через пару минут», либо засыпаем и снова перепроверяем. Тут уже по желанию. Главное помнить что перегружать механизм излишками тоже не стоит, отзовется прямо пропорционально нагрузкой на сервер. Собственно можно действовать еще хитрее, на уровне firewall сервера, к примеру тот же iptables может лимитировать количество соединений от одного и того же IP и отбрасывать превышающие лимит. Приводить правила не буду ввиду того что заметка носит скорее рассудительный характер, чем техническую реализацию.

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

Еще одним моментом реализация блокировки аккаунта после нескольких неудачных попыток авторизации пользователем, что уже наверное всем известно, но забывать не стоит.

3) Ввиду того что я решил порассуждать какими методами можно завалить сайт, следующей возникшей у меня идеей была мысль того что вышеуказанный метод нужно применить и на регистрацию, ввиду того что можно здорово поиздеваться над базой и забить все популярные никнеймы на сайте, чтобы пользователи не могли зарегистрироваться. Таким образом чтобы уничтожить сайт, лишить его жизни, лишаем его новых пользователей. Боремся с этим так же как раньше. Ограничиваем по времени и по IP регистрацию. Регистрируем на сайт скриптом пользователей выдираемых из файла с какой-нибудь слитой БД. Как защиту, чтобы откатить такую БД на сайте лучше указывать IP с которым регистрировался никнейм, к примеру в поле LastIp и естественно регистрировать с указанием уникальных email, которые нужно подтверждать. Тогда подобные зарегистрированные ники можно быстро удалить из БД, освободив таким образом пространство имен для обычных пользователей.

4) Чтобы упростить момент с подтверждением почтового ящика через ссылку при регистрации проще реализовать отправку автоматически сгенерированного пароля на указанный почтовый ящик, чтобы пользователь мог изменить пароль на собственный уже войдя на сайте.

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

6) Естественно кроме соли и хеширования, а не хранения в открытом виде пароля наряду нужно реализовать проверку не украли ли куки у пользователя сайта. По сути проверяем определенным алгоритмом, к примеру:

if ($sess_key == md5(md5($ip).md5($uagent)) )
Проверяем браузер пользователя и его IP адрес, если что-то изменилось уничтожаем сессию:
session_destroy();

7) Обязательно проверяем вводимые пользователем поля на внедрение кода или SQL-инъекций, для этого есть разные функции, к примеру:
strip_tags()
stripslashes()
htmlentities()

Если нужно оставить часть HTML кода есть функция:

string strip_tags (string str [, string allowable_tags])

Эта функция пытается вернуть строку str с вырезанными тегами HTML и PHP. Выдает ошибку с предупреждением в случае наличия неполных или ложных тегов.

Так же в сети можно найти без особых проблем библиотеки которые вычищают ненужные теги, определяют незакрытые и закрывают, удаляют ненужные куски такие как попытка XSS методом внедрения JS кода, ну и другие функции.

8) Использовать GET запросы только при действительной необходимости, если такие вообще могут быть. То есть используем в основном POST, преимущества очевидны, как основные то что ничего лишнего не видно и данные передаются так же спрятано от постороннего взгляда, да url при этому выглядит аккуратно и не мешает пользователю в случае чего кинуть пользователю чистую ссылку. Это не единственные преимущества.

9) Передача идентификатора сессии через кукисы, в противном случае она указывается в url, что по сути «пачкает» ссылку, что не нравиться поисковикам, так как для них каждый такой url будет уже новым, что сулит последствия для сайта.

10) Не забывать логировать ошибки. Каждое исключение должно быть отмечено в логах чтобы потом видно было с чем разбираться и если кто-то пытает ваш сайт.

11) Только нужные данные из БД. Если вы делаете так: select * from user; и данные получиться выманить или вы по привычке будете писать так везде, рано или поздно ошибетесь и через сеть начнут утекать данные пользователей. Во-вторых чем больше данных вы вытягиваете из БД, тем дольше выполняется запрос, и все это непременно отразиться на производительности под нагрузкой. Нужно привыкнуть работать не как легче, а так как положено и тогда проблем не возникнет.

12) Генерация XML-файлов карты сайта для поисковиков и мета-тегов. Это повысит поисковую выдачу в некоторой степени. В сети есть уже готовые алгоритмы генерации мета-тегов из материала.

13) Использовать фреймворк или CMS/CMF соответственно задаче. Каждый сайт это достаточно большой объем работы, хороший фундамент для этих целей существенно упрощает жизнь и портируемость вашего каркаса (наработанного за годы) на другие ваши проекты в которых возникает нужда. Если к примеру это просто блог можно взять одну из CMS, к примеру drupal, wordpress, прочие, если же задача более глобальна и переделывать движок достаточно сложно, лучше взять фреймворк типа kohana, yii, zend, прочие. Такие фреймворки уже содержат конструкторы запросов к БД, систему кэширования (что положительно влияет на производительность), систему роутинга, обработку картинок, ORM, валидацию данных и возможность подключения дополнительных, разработанных авторами или сообществом модулей. Это существенно сократит время разработки и отладки, за счет отлаженности кода и достаточно уделенного внимания производительности.

14) Вместо использования атрибутов высоты и ширины для картинок в теге img, лучше при загрузке сохранять несколько образцов картинки с разными параметрами, к примеру full_img.png (полный), preview_img.png(уменьшенная копия), в таком случае вам не придется передавать без нужды большие картинки (что уменьшит скорость загрузки страницы и занятость канала) и браузеру не придется ее масштабировать, что повлияет на скорость отображения страницы.

15) Индексирование полей в БД. Создание индексированных полей очень сильно сказывается на производительности как БД так и сайта в целом. При большом количестве запросов что тоже сильно снизит нагрузку на сервер и сократит расходы.

16) Использовать кэширование как данных, так и запросов. К примеру в kohana 3 можно кешировать запрос к БД, добавив ->cached(30) к построению запроса, что закэширует запрос на 30 секунд, так и хранить целые объемы данных закэшироваными по вашему выбору либо файловым кэшем, либо memcached, либо sqlite, прочее. Но не забываем что с кэшем нужно придерживаться золотой середины, ввиду того что данные могут устаревать, к примеру пользователь внес корректировку в статью и мы должны в данном случае сбросить данное сохраненное значение кэша, чтобы не отдавать пользователю старые данные. Такой реакции он точно уж не поймет.

17) Если у вас выделенный сервер, использовать nginx как фронтэнд, а apache2(либо альтернативу) как бэкэнд, разделив нагрузку. Об этом достаточно много написано в сети.

18) Использовать xcache/eaccelerator/другое, для кэширование опкодов, что в некоторой толике тоже повлияет на производительность.

19) Одно из основных правил заключается в том, что нормализация БД и проектирование онной не просто слова. Это своего рода наука и золотое правило тех кто занимается этим профессионально. Не стоит делать сайт на БД, в одной таблице которой находиться к примеру 80 полей на все случаи жизни. Это изначально уже можно считать мертвым сайтом. Логическое разбиение данных на таблицы, использование ключей и остальные правила нормализации (естественно не стоит с этим тоже перегибать) обязательно влияют на производительность самым прямым образом.

20) Одно из самых главных правил администрирования звучит приблизительно так «Сперва все запретить и только потом разрешить только то что нужно». Противоположность для многих легче «Разрешить все, а потом запрещать», но при таком подходе вы обязательно что-нибудь забудете, что серьезно подкосит безопасность сайта.

21) Использование JOIN в запросах лучше чем несколько отдельных запросов. Если вы хорошо разобрались с оператором JOIN, думаю остальное объяснять бессмысленно. Чем меньше запросов к БД, тем быстрее работает сайт и меньше нагрузка на сервер/хостинг. Тем более что объединение запросов оператором JOIN должно проводиться по индексированным полям.

22) Целостность данных в БД. Всегда нужно следить за данными в БД, чтобы не было так что вы удалили пользователя, но не удалили его статьи, или же удалили саму статью не удалив комментарии к ней. Таким образом база расширяется и в ней накапливается хлам, который потом сложнее отследить и почистить чем просчитать всю логическую цепочку сразу.

23) Данные фрагментируются, что влияет на производительность, а следовательно те же данные в БД время от времени нужно дефрагментировать. В том же MySQL для этого уже предусмотрено все что нужно.
Ссылка на FAQ.

Q. Как произвести оптимизацию хранилища в MySQL?
Почистить «дырки» (дефрагментация), обновить статистику и отсортировать индексы:

OPTIMIZE TABLE имя_таблицы;
или использовать: myisamchk --quick --check-only-changed --sort-index --analyze
Внимание, myisamchk нужно запускать при _не_ запущенном mysqld, иначе нужно использовать утилиту mysqlcheck
(mysqlcheck --repair --analyze --optimize --all-databases --auto-repair)

Апдейт статистики оптимизатора:
ANALYZE TABLE имя_таблицы;
или использовать: myisamchk --analyze

Рекомендуется регулярно выполнять:
isamchk -r --silent --sort-index -O sort_buffer_size=16M db_dir/*.ISM
myisamchk -r --silent --sort-index -O sort_buffer_size=16M db_dir/*.MYI

24) Отслеживать медленные запросы и пытаться их максимально оптимизировать:
В конфиге mysql(my.cnf) нужно прописать следующих две строчки:

log_slow_queries = /var/log/mysql/mysql-slow.log
long_query_time = 1

Поясняем:
log_slow_queries — файл в который сохраняем
long_query_time — время выполнения запроса которое мы считаем уже достаточным чтобы залогировать. Количество секунд.

25) Рассматриваем разные варианты… Скорее не техническая деталь, но… В текущее время существует множество СуБД, как SQL, так и NoSQL, фреймворков, библиотек и прочего. Все это крайне желательно рассматривать на этапе проектирования. Если ваш проект перерастет в нечто мощное, все ваши ошибки обязательно всплывут. Взять даже те же движки БД в СуБД mysql, их достаточное количество чтобы рассмотреть преимущества тех и других. А взять к примеру MariaDB… PostgreSQL. У каждого свои плюсы и минусы, кажущиеся сразу не очевидными… Масштабирование? Кластеризация? Сделали сайт и вздохнули, работает. Разросся проект и одного сервера уже не хватает, нужно разносить… Делить на логические уровни, прослойки, где должна работать SQL СуБД ввиду отслеживания целостности данных и не только, где NoSQL ввиду быстрого доступа к данным.

26) Производительность из коробки. Отсутствует в основном. Все нужно настраивать, тот же MySQL и Apache2 с Nginx. Настроенное ПО под конкретный проект очень сильно разнится с настройками по-умолчанию того же ПО.

27) Кэширование страниц для анонимных пользователей на frontend-е. Можно взглянуть где-то на соотношение анонимных посетителей и авторизованных пользователей. Тот же nginx может кэшировать страницы и отдавать их не авторизованным пользователям. Если пользователь авторизовался то ставить в кукисах отметку об этом и отдавать не кэшированную страницу. Производительность сервера вырастет.

28) Ну это уже наверное тонкости, но все же, в зависимости от проекта опять же, стоит выбрать соответствующую файловую систему, благо их сейчас разнообразие манит. У каждой свои особенности. И о да, файловая система тоже может настраиваться. Ext2/3/4, ReiserFS, FAT, NTFS, еще десятка два других.

29) Настройка ядра, планировщик ввода/вывода. Снова тонкости, но ввиду того что чем больше нагружен сервер, тем каждая мелочь дается небольшим облегчением, поэтому рассмотрим и этот момент. С помощью sysctl в линуксе(по-моему не только в Linux, но и BSD, поправьте меня) можно настраивать параметры ядра в реальном времени. Список настроек можно получить командой:

sysctl -a
Сами же настройки применяются так:
sysctl net.ipv4.ip_forward=1
Ту же команду можно прописать в файл:
/etc/rc.local

И проверить, чтобы файл стоят в загрузке при запуске системы.
Настроект там собственно валом, обьяснения к ним можно так же найти в инете.

Вторым пунктом данного этапа у нас планировщик ввода/вывода.
Их несколько в Nix-подобных системах. Одни рассчитаны на пользовательскую систему, другие на сервер, и т.д. Собственно информации о них так же хватает, к примеру:
Планировщики IO

30) Systemd как система запуска/слежения за процессами. В данном пункте скорее реклама так как мне очень понравился демон, а в Debian пока он по умолчанию не стоит, но поставить его не проблема. Сам SystemD я описал по ссылке: Здесь

В чем преимущества SystemD, если вкратце:

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

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

— Скрипты сценариев запуска SysV достаточно медленные за счет того что обрастают кодом и используют множество других утилит, таких как grep, find и прочие. Файлы systemd прозаично коротки, эдак иногда в несколько десятков раз.
— Для того чтобы писать сценарии запуска SysV нужно достаточно неплохо знать sh-скриптинг, в отличии от файлов systemd.
— Замечательная плюшка рас! cgroups сортирует ресурсы по полочкам и шкафчикам. Если процесс порождает форк, который в последствии отрекается от родителя, такой процесс можно легко найти. Плюс это дает возможности задавать лимиты процессам.
— Замечательная плюшка два! systemd может отслеживать и перезапускать упавшие демоны. Да! Не нужен monit с его ручками прописанными конфигами слежения за наличием файла pid, свидетельствующем что процесс не вылетел! Все в этом комбайне, поставил, настроил и юзай.
— Systemd может монтировать файловые системы при старте и куда еще только свои щупальца не запустил.
— Планировалось что systemd может отслеживать время и запуска/останавливать/делать_что-то_еще вместо крона.
— Замечательная плюшка три! Для того чтобы им пользоваться не нужно кучу всего перенастраивать и т.д., в нем работает режим совместимости с сценариями SysV, читает fstab и спокойно работает с привычными нам /etc/init.d/какой-то_демон start/stop
— Работает с системной шиной D-Bus.

31) Архитектура процессора и сборные вручную пакеты. Дистрибутивы в основном идут с уже скомпилированными пакетами под сразу самые распространенные архитектуры. И далеко не с самой большой оптимизацией. Про оптимизацию лучше всего знают пользователи дистрибутивов Gentoo и некоторых других. Информации об флагах оптимизации в интернете тоже достаточно. В Debian известен пакет apt-build, но пересобрать все он вряд ли даст, а от маленькой части не уверен что будет много проку. Снова же можно использовать старый добрый make, но им нужно уметь пользоваться и следить за процессами сборки и конфигурирования чтобы ничего не пропустить до самой сборки. Тоже самое и с ядром, при сборке собственноручно ядра у вас в ассортименте море настроек, можно выбрать тот же планировщик ввода/вывода, подключить/отключить/сделать_подгружаемыми модули ядра и собрать под свою архитектуру с нужной оптимизацией, отключив отладку и остальное ненужное чем и облегчив ядро. Главное не запутаться, иначе после сборки ядро просто откажет загружаться, к примеру не найдя драйвера вашей файловой системы. В общем потеряли время.

32) Защита самого сервера. Популярен он или нет, но если сервер в сети, скорее всего он хоть пару раз да засветился уже. А если он еще и достаточно посещаем, возрастает риск подбора пароля к ssh, поиску уязвимостей в ПО и прочих лазейках. Бесплатного ПО для защиты тоже хватает, fail2ban отслеживает неудачные попытки авторизации через ssh и имеет собственно достаточно правил, которые к тому же можно достаточно легко писать самому, умеет добавлять в iptables, или же в host.deny IP злоумышленника и удалять их оттуда по прошествии заданного времени. Так же умеет отправлять сообщения на почту. Старый знакомый многим Portsentry вполне может имитировать открытые порты, а при сканировании нескольких из них либо блокировать, либо логировать и отправлять сообщения. chkrootkit — пакет для сканирования на наличие в системе руткитов. Да чего только стоит правильно настроенный iptables, который дает очень широкие возможности для настройки безопасности системы. Ограничение на выполнение PHP скриптов в отведенной им директории и ограничение на выполнение интерпретатором системных команд.

P.S. Жду Ваших замечания и дополнений в комментариях.

Автор: Nelex

* - обязательные к заполнению поля