Совсем недавно на мой сервер с git репозиторием началась атака по подбору паролей к gitlab и ключей к ssh. Намерения злоумышлеников понятны — вытащить исходный код проприетарного приложения хранящегося в git.
Мне не совсем понятны попытки подбора ssh-ключей, т.к. проблематично подобрать RSA-ключ (это займет десятки лет), но я всё же сделал некоторые ограничения для того что бы не так сильно «загаживались» логи.
Кому интересно как защитить gitolite и gitlab (работает за nginx) от подбора паролей — добро пожаловать под кат.
Многим извесно что сам по себе sshd в Linux не умеет ограничивать количество соединений. Для нас это не явилось проблемой, мы ограничили их на уровне фаервола.
В стандартной поставке iptables под CentOS есть модуль hashlimit. Им мы и воспользуемся. Я написал следующие правила для iptables:
Что же мы сделали? Сперва мы добавили цепочку ssh_input. Потом добавляем правило которое ограничивает количество соединений до 5 в минуту (--hashlimit 5/m --hashlimit-burst 5). В нем же указываем параметры по которым стоит группировать соединения (--hashlimit-mode srcip,dstport). После добавляем правило которое запрещает доступ (-j REJECT). И добавляем цепочку в input с условиями что соединения должно быть новым и приходить на порт 22.
Как работают эти правила? Все пакеты с флагом нового соединения на порт 22 отправляются на обработку в цепочку ssh_input. Там при выполнении условия что количество таких коннектов с данного ip не превышает 5 в минуту происходит пропускание пакета (-j ACCEPT). Если условия не выполняется переходим к следующему правилу: -j REJECT.
Теперь наш злоумышленик может годами (десятками, сотнями лет ?) подбирать ssh ключ. И «загаживания» лога будет поменьше.
Так же злоумышленники пытаются подобрать пароль к web-интерфейсу gitlab. В качестве Front-end мы используем Nginx. Он там довольно старой версии 0.8.55 и обновлять его сейчас не времени и желания.
В первую очередь мы добавляем basic авторизацию и ограничиваем количество соединений в минуту (чтобы не так просто было подобрать этот пароль). Проблема ограничения в нашем случае такова, что загрузка web страницы вызывает еще около 15 обращений к серверу за статикой. Это заставит нас разрешить более 15 соединений в секунду. Это нас не устравиает т.к. имея 15 соединений в секунду на каждый ip злоумышленик сможет подобрать пароль и мы делаем следующий «финт ушами»:
Логин и пароль basic-авторизации у нас общий для всех пользователей и служит лишь помехой для подбора пароля от самого web-приложения. Раз так, то мы можем делать следующую проверку:
для всех url отличающихся от /. На самом / мы делаем basic авторизацию и ограничение на 5 попыток в минуту для 1 ip:
А теперь давайте представим что наш basic пароль всё же подобрали или украли. Защитим так же и форму входа в приложение:
И наконец основной локейшин для прочих адресов:
Таким образом при запросе без basic-авторизации на любой url кроме корня мы получаем 403. Basic-авторизация возможна только на корне и ограничена на 5 запросов в минуту. Даже если подберут basic-авторизацию, форма авторизации в web приложении ограничена на 5 запросов в минуту. Я выделил в разные зоны ограничения на попытки входа в basic и web-приложение для того чтобы ошибки ввода разных паролей в разных местах не накапливались и реальным пользователям не выдавалось «Сервис недоступен».
Мне не совсем понятны попытки подбора ssh-ключей, т.к. проблематично подобрать RSA-ключ (это займет десятки лет), но я всё же сделал некоторые ограничения для того что бы не так сильно «загаживались» логи.
Кому интересно как защитить gitolite и gitlab (работает за nginx) от подбора паролей — добро пожаловать под кат.
Защита ssh.
Многим извесно что сам по себе sshd в Linux не умеет ограничивать количество соединений. Для нас это не явилось проблемой, мы ограничили их на уровне фаервола.
В стандартной поставке iptables под CentOS есть модуль hashlimit. Им мы и воспользуемся. Я написал следующие правила для iptables:
iptables -N ssh_input iptables -A ssh_input \ -m hashlimit \ --hashlimit 5/m \ --hashlimit-burst 5 \ --hashlimit-mode srcip,dstport \ --hashlimit-name ssh \ --hashlimit-htable-expire 3600000 \ -j ACCEPT iptables -A ssh_input -p tcp -j REJECT --reject-with tcp-reset iptables -A INPUT -m state -m tcp -p tcp --dport 22 --state NEW -j ssh_input
Что же мы сделали? Сперва мы добавили цепочку ssh_input. Потом добавляем правило которое ограничивает количество соединений до 5 в минуту (--hashlimit 5/m --hashlimit-burst 5). В нем же указываем параметры по которым стоит группировать соединения (--hashlimit-mode srcip,dstport). После добавляем правило которое запрещает доступ (-j REJECT). И добавляем цепочку в input с условиями что соединения должно быть новым и приходить на порт 22.
Как работают эти правила? Все пакеты с флагом нового соединения на порт 22 отправляются на обработку в цепочку ssh_input. Там при выполнении условия что количество таких коннектов с данного ip не превышает 5 в минуту происходит пропускание пакета (-j ACCEPT). Если условия не выполняется переходим к следующему правилу: -j REJECT.
Теперь наш злоумышленик может годами (десятками, сотнями лет ?) подбирать ssh ключ. И «загаживания» лога будет поменьше.
Защита gitlab
Так же злоумышленники пытаются подобрать пароль к web-интерфейсу gitlab. В качестве Front-end мы используем Nginx. Он там довольно старой версии 0.8.55 и обновлять его сейчас не времени и желания.
В первую очередь мы добавляем basic авторизацию и ограничиваем количество соединений в минуту (чтобы не так просто было подобрать этот пароль). Проблема ограничения в нашем случае такова, что загрузка web страницы вызывает еще около 15 обращений к серверу за статикой. Это заставит нас разрешить более 15 соединений в секунду. Это нас не устравиает т.к. имея 15 соединений в секунду на каждый ip злоумышленик сможет подобрать пароль и мы делаем следующий «финт ушами»:
Логин и пароль basic-авторизации у нас общий для всех пользователей и служит лишь помехой для подбора пароля от самого web-приложения. Раз так, то мы можем делать следующую проверку:
if ($http_authorization != "Basic secretdsddsaadsdsasad=="){ return 403; break; }
для всех url отличающихся от /. На самом / мы делаем basic авторизацию и ограничение на 5 попыток в минуту для 1 ip:
limit_req_zone $binary_remote_addr zone=one:10m rate=5r/m; ... server { .... location = / { auth_basic "Top secret"; auth_basic_user_file /etc/nginx/conf.d/ssl/.htpasswd; limit_req zone=one burst=5 nodelay; ..... } .... }
А теперь давайте представим что наш basic пароль всё же подобрали или украли. Защитим так же и форму входа в приложение:
limit_req_zone $binary_remote_addr zone=two:10m rate=5r/m; ... server { ... location = /users/sign_in { if ($http_authorization != "Basic secretdsddsaadsdsasad=="){ //что бы даже не пытались без успешной basic авторизации return 403; break; } limit_req zone=two burst=5 nodelay; .... } .... }
И наконец основной локейшин для прочих адресов:
server { ... location / { if ($http_authorization != "Basic secretdsddsaadsdsasad=="){ //что бы даже не пытались без успешной basic авторизации return 403; break; } ... } }
Таким образом при запросе без basic-авторизации на любой url кроме корня мы получаем 403. Basic-авторизация возможна только на корне и ограничена на 5 запросов в минуту. Даже если подберут basic-авторизацию, форма авторизации в web приложении ограничена на 5 запросов в минуту. Я выделил в разные зоны ограничения на попытки входа в basic и web-приложение для того чтобы ошибки ввода разных паролей в разных местах не накапливались и реальным пользователям не выдавалось «Сервис недоступен».
