Как бороться с нарушениями сетевого порта в сокетах

У меня есть веб-приложение, написанное на С++ с использованием TCP/IP через стандартные сетевые сокеты, работающие в Linux. Услуга открыта для дикого и woolly интернета.

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

close( mSocket );

Но иногда закрытие сокета обычно уведомляет спам script о завершении соединения сокета и немедленно инициирует другой мошеннический запрос.

Каков наилучший способ прекратить соединение TCP/IP, которое очищает открытый сокет в моей системе, но не позволяет удаленной стороне. То есть я хочу закрыть сокет таким образом, который является самой низкой стоимостью для меня, но с максимальной стоимостью для них.

@Николас Уилсон:

Использование TCP_REPAIR кажется хорошей идеей. Когда сокет закрыт в режиме TCP_REPAIR, пакет FIN или RST не отправляется. Удаленный разъем остается висящим. Я попробую и отчитаю. Вот мой (непроверенный) код:

if ( abuse )
{
   int aux = 1;
   if ( setsockopt( mSocket, SOL_TCP, TCP_REPAIR, &aux, sizeof( aux )) < 0 )
      reportError( "Tried to do a rude socket close... but could not turn on repair mode.\n" );
}
close( mSocket );

Я отчитаю, если это сработает. (@edit: проверенный ответ ниже)

@Идея "оставить открытую розетку":

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

Тогда возникает проблема с управлением открытыми сокетами:

  • Просто не закрывай. Открытые сокеты продолжаются вечно. Стоимость для злоумышленника: высокая - у них нет плавников. Стоимость для меня: выше. Все мои файловые дескрипторы в конечном итоге будут использоваться.
  • Создайте поток на сокет, чтобы спать 10 минут, а затем закройте сокет. Стоимость для злоумышленника: высокая - у них нет плавников. Стоимость для меня: выше. Хотя я в конце концов закрываю сокет, для каждого запроса у меня есть сокет, используемый дольше, чем у злоумышленника, и у меня есть накладные расходы на поток.
  • Создайте поток, который обрабатывает все уничтоженные сокеты. Стоимость для злоумышленника: высокая - у них нет плавников. Стоимость для меня: выше. Как и 2, было открыто много гнезд. Накладные расходы одного потока для управления им. Сложность кода, раздражение.

Ответ 1

Хорошо, сделал некоторые исследования, и у меня есть ответ, который работает для меня, на основе TCP_REPAIR. Это немного сложнее, чем я думал сначала:

if ( abuse )
{
   // read some bytes from the spammer - to establish the connection
   u32 tries = 20;
   while ( tries )
   {
      sleep( 1000 );
      char tmpBuf[32];
      s32 readCount = recv( mSocket, &tmpBuf[0], 32, 0 );
      if ( readCount > -1 ) break;
      tries--;
   }
#ifdef TCP_REPAIR
   int aux = 1;
   if ( setsockopt( mSocket, SOL_TCP, TCP_REPAIR, &aux, sizeof( aux )) < 0 )
   {
     reportError( "could not turn on repair mode" );
   }
#else // !TCP_REPAIR
   // no TCP REPAIR - best we can do is an abort close
   struct linger so_linger;
   so_linger.l_onoff = 1;
   so_linger.l_linger = 0;
   if ( setsockopt( mSocket, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger ) < 0  )
   {
      reportError( "Cannot turn off SO_LINGER" );
   }
#endif // TCP_REPAIR
}
close( mSocket );

На уровне ядра стек TCP либо отправит пакет FIN, либо RST, если вы закроете соединение, независимо от того, как вы это делаете (с закрытием или завершением работы). В любом случае злоумышленник уведомляется о том, что вы закрыли соединение.

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

TCP_REPAIR - это новый API сокетов, который позволяет вам "заморозить" сокет, сохранить его состояние и перезагрузить состояние сокета на другом процессе или даже на другой системе. При нормальном использовании клиент никогда не узнает, что их соединение было перенесено в другое место.

Но мы можем злоупотреблять этим API, мы помещаем сокет в режим восстановления, но не сохраняем его состояние и никогда его не восстанавливаем. Когда мы закрываем сокет в режиме восстановления, он автоматически удаляется.

Это работает для оскорбительных запросов, которые уже начались. То есть мы прочли запрос спамера и решили, что это мошенничество, и TCP_REPAIR убил его.

Но если вы блокируете запросы по IP, сразу после подключения, без предварительного чтения сокета, каким-то образом уведомляется удаленная сторона. Они получают RST. Или, возможно, что-то в соединении никогда не завершается, и удаленная система почти полностью прерывает запрос.

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

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

TCP_REPAIR доступен только в ядрах Linux 3.5 и выше. Ниже, что лучшее, что я могу сделать, это "грязный" сокет. Именно здесь вместо отправки ему FIN вы отправляете его и RST. Он будет смотреть на него, как действительное соединение никогда не было установлено. Чтобы сделать это, вы отключите SO_LINGER, чтобы по существу разорвать соединение сокета, прекратите квитирование, а затем вызовите закрыть.

Работает как шарм, укажи свой браузер здесь:

http://oroboro.com/fail

Chrome, по крайней мере, будет висеть там в течение 5-10 секунд. Глядя на мои журналы, где я получал 10 ударов в секунду - он мог ударить меня каждые 10 секунд или около того. Загрузите мою систему из этого: 0.

Посмотри на ящика!

Ответ 2

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

Что вы можете сделать, по крайней мере, это черный список IP-адресов в вашем приложении. Сохраните список запрещенных IP-адресов и немедленно закройте любой принятый сокет, который исходит от IP-адреса в этом списке.

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

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

Ответ 3

Используйте это:

#include <sys/socket.h>

int shutdown(int socket, int how);

Он немедленно отправит RST и закроет (соединение). Это заставит вас казаться, что в этом порту нет сервиса, и злоумышленник, надеюсь, прекратит спам в этом порту. Вызовите close(), чтобы освободить дескриптор.

Ответ 4

Когда вы обнаруживаете abuse

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

если соединение квалифицируется как !abuse, чтобы использовать соответствующий сокет.