Как избежать атаки DOS с использованием сокетов Беркли в C++
Я пробираюсь через Том 1 по сетевому программированию UNIX Ричарда Стивенса и пытаюсь написать TCP Echo Client, который использует протокол Telnet. Я все еще на ранних стадиях и пытаюсь написать функции чтения и записи.
Я хотел бы написать его для использования мультиплексирования ввода / вывода и функции Select, потому что он должен быть мультиклиентным, и я не хочу пытаться заниматься изучением потоков C++, пока я пытаюсь изучить библиотеку Berkeley Sockets в то же время. В конце главы о мультиплексировании ввода / вывода Стивенс имеет небольшой раздел о атаках DOS, в котором он говорит, что метод, который я планировал использовать, уязвим для атак DOS, которые просто отправляют один байт после подключения и затем зависают. Затем он упоминает 3 возможных решения - неблокирующий ввод-вывод, многопоточность (out) и установка таймаута для операций ввода-вывода.
У меня вопрос, есть ли другие способы избежать такой атаки? А если нет, то какой из них лучше? Я просмотрел раздел о том, как установить таймаут для операций, но это не похоже на то, что я хочу сделать. Методы, которые он предлагает для этого, выглядят довольно сложными, и я не уверен, как превратить их в то, что у меня уже есть. Я только взглянул на главу о NIO, похоже, сейчас это путь, но я хотел бы узнать, есть ли другие способы обойти это, прежде чем я потрачу еще пару часов на просмотр этой главы.
Есть идеи?
5 ответов
... есть ли другие способы избежать такой атаки?
Да, асинхронный ввод-вывод - это еще один общий подход.
Если проблема в том, что блокировка read()
может приостановить ваше исполнение на неопределенный срок, тогда ваши общие контрмеры:
- Иметь несколько потоков исполнения
многопоточный, многопроцессный, оба.
- Ограничение по времени операции блокировки
например, мгновенный (неблокирующий ввод / вывод) или нет (
SO_RCVTIMEO
,alarm()
, так далее.) - Работать асинхронно
например,
aio_read
... какой из них лучший?
Для новичка я бы предложил неблокирующий ввод / вывод в сочетании с ограниченным по времени select()
/ poll()
, Ваше приложение может отслеживать, генерировало ли соединение "достаточно данных" (например, целую строку) за "достаточно короткое время".
Это мощная, в основном портативная и распространенная техника.
Тем не менее, лучший ответ: "это зависит". Поддержка платформы и, что более важно, последствия разработки этих вариантов должны оцениваться в каждом конкретном случае.
Основное чтение: проблема C10K
Использование потоков (или процессов) для каждого соединения делает код очень простым. Ограничение на количество соединений на самом деле является ограничением на количество потоков, которые ваша система может комфортно выполнять в нескольких задачах.
Использование асинхронного ввода-вывода для помещения всех сокетов в один поток - не такой простой код (приятно упакованный в libevent и libev2), но гораздо более масштабируемый - он ограничен числом открытых файловых дескрипторов, разрешенных вашей системой, и - например, в последних сборках Linux - это можно измерить миллионами! По этой причине большинство веб-серверов и других серверов используют асинхронный ввод-вывод.
Тем не менее, ваш сервер по-прежнему является ограниченным ресурсом, который можно исчерпать, и есть гораздо более противные атаки, чем просто нехватка ресурсов для обработки новых соединений.
Брандмауэры и ограничение ущерба, например, резервные копии, DMZ и т. Д., Являются важными элементами в реальных интернет-сервисах.
Если вы только начинаете изучать программирование сокетов, вам, вероятно, лучше сконцентрироваться на базовой функциональности сокетов и пока не беспокоиться о проблемах безопасности. Когда вы написали несколько клиент-серверных приложений и полностью поняли, как они работают, вы сможете лучше понять, как они ломаются.
Защита сетевого интернет-приложения от злонамеренных клиентов совсем не тривиальна и, вероятно, включает в себя все продвинутые методы, которые вы упомянули, а затем и некоторые! Например, обычно переносят некоторую ответственность с кода приложения на уровень маршрутизатора или брандмауэра. Вы можете ограничить доступ только к доверенным узлам, или обнаружить чрезмерные попытки подключения и ограничить или заблокировать их до того, как трафик попадет в ваше приложение.
Чтобы помочь в этом (около 1997 года:)), я потребовал, чтобы магическое число было отправлено в течение определенного периода времени, в противном случае соединение было закрыто.
Если у вас асинхронное соединение, сокет не будет заблокирован, и вам понадобится поток, который может опросить список текущих соединений, которые не отправили действительную команду, и если через 20 мс сообщение не было получено это означает допустимую команду, затем закройте это соединение и выполните необходимую очистку.
Это не идеально, но для вашей текущей проблемы это может помочь решить ее и позволить ресурсам не потребляться при слишком большом количестве соединений.
Поэтому для очистки требуется основной поток и второй поток, поэтому он не является однопоточным.
У меня вопрос, есть ли другие способы избежать такой атаки?
Для сервера я бы хотел таймер на уровне приложения:
- Буфер входных данных на соединение
- Тупой код чтения сокетов считывает данные из сокета во входной буфер
- Специфичный для приложения код анализирует содержимое входного буфера
Специфичный для приложения код может разорвать соединение, связанное с входными буферами, которым разрешено бездействовать слишком долго.
Это подразумевает асинхронный ввод-вывод или выделенный поток ввода-вывода.