Защита gitlab и gitolite от подбора паролей и ключей

в 7:14, , рубрики: CentOS, Git, gitlab, gitolite, iptables, nginx, ssh, информационная безопасность, метки: , , , , , ,

Совсем недавно на мой сервер с git репозиторием началась атака по подбору паролей к gitlab и ключей к ssh. Намерения злоумышлеников понятны — вытащить исходный код проприетарного приложения хранящегося в git.

Мне не совсем понятны попытки подбора 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-приложение для того чтобы ошибки ввода разных парлей в разных местах не накапливались и реальным пользователям не выдавалось «Сервис недоступен».

Автор: piromanlynx

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


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js