Использование SO_REUSEADDR и SO_EXCLUSIVEADDRUSE
Разработка безопасной высокоуровневой сетевой инфраструктуры является приоритетом для большинства разработчиков сетевых приложений. Однако безопасность сокетов часто упускается из виду, несмотря на то, что она очень важна при рассмотрении полностью безопасного решения. Безопасность сокетов, в частности, связана с процессами, которые привязываются к тому же порту, ранее привязанному другим процессом приложения. В прошлом сетевое приложение могло "захватить" порт другого приложения, что может легко привести к атаке типа "отказ в обслуживании" или краже данных.
Как правило, безопасность сокетов применяется к процессам на стороне сервера. В частности, безопасность сокетов применяется к любому сетевому приложению, которое принимает подключения и получает трафик ip-датаграмм. Эти приложения обычно привязываются к хорошо известному порту и являются распространенными целевыми объектами для вредоносного сетевого кода.
Клиентские приложения с меньшей вероятностью будут объектами таких атак— не потому, что они менее восприимчивы, а потому, что большинство клиентов привязаны к "временным" локальным портам, а не к статическим "служебным" портам. Клиентские сетевые приложения всегда должны выполнять привязку к временным портам (указывая порт 0 в структуре SOCKADDR , на которую указывает параметр name при вызове функции привязки ), если нет веских причин архитектуры. Временные локальные порты состоят из портов, превышающих порт 49151. Большинство серверных приложений для выделенных служб привязываются к известному зарезервированному порту, который меньше порта 49151 или равен 49151. Поэтому для большинства приложений обычно не возникает конфликта запросов привязки между клиентскими и серверным приложениями.
В этом разделе описывается уровень безопасности по умолчанию на различных платформах Microsoft Windows, а также то, как конкретные параметры сокета SO_REUSEADDR и SO_EXCLUSIVEADDRUSE влиять на безопасность сетевых приложений. В Windows Server 2003 и более поздних версиях доступна дополнительная функция, называемая усиленной безопасностью сокетов. Доступность этих параметров сокетов и повышенная безопасность сокетов зависят от версий операционных систем Майкрософт, как показано в таблице ниже.
Платформа | SO_REUSEADDR | SO_EXCLUSIVEADDRUSE | Улучшенная безопасность сокетов |
---|---|---|---|
Windows 95 | Доступно | Недоступно | Недоступно |
Windows 98 | Доступно | Недоступно | Недоступно |
Windows Me | Доступно | Недоступно | Недоступно |
Windows NT 4.0 | Доступно | Доступно в пакете обновления 4 (SP4) и более поздних версий | Недоступно |
Windows 2000 | Доступно | Доступно | Недоступно |
Windows XP | Доступно | Доступно | Недоступно |
Windows Server 2003 | Доступно | Доступно | Доступно |
Windows Vista | Доступно | Доступно | Доступно |
Windows Server 2008 | Доступно | Доступно | Доступно |
Windows 7 и более поздних версий; | Доступно | Доступно | Доступно |
Использование SO_REUSEADDR
Параметр сокета SO_REUSEADDR позволяет сокету принудительно привязаться к порту, используемому другим сокетом. Второй сокет вызывает setsockopt с параметром optname , равным SO_REUSEADDR , а параметр optval имеет логическое значение TRUE перед вызовом привязки к тому же порту, что и исходный сокет. После успешной привязки второго сокета поведение всех сокетов, привязанных к порту, будет неопределенным. Например, если все сокеты на одном порту предоставляют службу TCP, то все входящие запросы tcp-подключения через порт не могут быть гарантированно обработаны правильным сокетом — поведение не детерминировано. Вредоносная программа может использовать SO_REUSEADDR для принудительной привязки сокетов, уже используемых для стандартных служб сетевых протоколов, чтобы запретить доступ к этим службам. Для использования этого параметра не требуются специальные привилегии.
Если клиентское приложение привязывается к порту до того, как серверное приложение сможет выполнить привязку к тому же порту, могут возникнуть проблемы. Если серверное приложение принудительно привязывается с помощью параметра сокета SO_REUSEADDR к одному порту, поведение для всех сокетов, привязанных к порту, является неопределенным.
Исключением из этого недетерминированного поведения являются сокеты многоадресной рассылки. Если два сокета привязаны к одному интерфейсу и порту и являются членами одной группы многоадресной рассылки, данные будут доставляться в оба сокета, а не в произвольно выбранный.
Использование SO_EXCLUSIVEADDRUSE
До появления параметра сокета SO_EXCLUSIVEADDRUSE разработчик сетевого приложения мог сделать очень мало, чтобы предотвратить привязку вредоносной программы к порту, на котором сетевое приложение было привязано к собственным сокетам. Чтобы устранить эту проблему безопасности, в windows Sockets появился параметр сокета SO_EXCLUSIVEADDRUSE, который стал доступен в Windows NT 4.0 с пакетом обновления 4 (SP4) и более поздних версий.
Параметр сокета SO_EXCLUSIVEADDRUSE может использоваться только участниками группы безопасности "Администраторы" в Windows XP и более ранних версиях. Причины, по которым это требование было изменено в Windows Server 2003 и более поздних версиях, рассматриваются далее в этой статье.
Параметр SO_EXCLUSIVEADDRUSE задается путем вызова функции setsockopt с параметром optname , равным SO_EXCLUSIVEADDRUSE , а для параметра optval — логическое значение TRUE до привязки сокета. После установки параметра поведение последующих вызовов привязки отличается в зависимости от сетевого адреса, указанного в каждом вызове привязки .
В таблице ниже описано поведение, которое происходит в Windows XP и более ранних версиях, когда второй сокет пытается привязаться к адресу, ранее привязанным к первому сокету, используя определенные параметры сокета.
Примечание
В таблице ниже "подстановочный знак" обозначает подстановочный адрес для заданного протокола (например, "0.0.0.0" для IPv4 и "::" для IPv6). "Конкретный" обозначает определенный IP-адрес, назначенный интерфейсу. Ячейки таблицы указывают, является ли привязка успешной ("Успешно") или возвращается ошибка ("INUSE" для ошибки WSAEADDRINUSE ; "ACCESS" для ошибки WSAEACCES ).
Первый вызов привязки | Второй вызов привязки | ||||||
Значение по умолчанию | SO_REUSEADDR | SO_EXCLUSIVEADDRUSE | |||||
Подстановочный знак | Specific | Подстановочный знак | Specific | Подстановочный знак | Specific | ||
Значение по умолчанию | Подстановочный знак | INUSE | INUSE | Успех | Успех | INUSE | INUSE |
Specific | INUSE | INUSE | Успех | Успех | INUSE | INUSE | |
SO_REUSEADDR | Подстановочный знак | INUSE | INUSE | Успех | Успех | INUSE | INUSE |
Specific | INUSE | INUSE | Успех | Успех | INUSE | INUSE | |
SO_EXCLUSIVEADDRUSE | Подстановочный знак | INUSE | INUSE | ACCESS; | ACCESS; | INUSE | INUSE |
Specific | INUSE | INUSE | ACCESS; | ACCESS; | INUSE | INUSE |
Если два сокета привязаны к одному номеру порта, но в разных явных интерфейсах, конфликт не возникает. Например, если компьютер имеет два IP-интерфейса, 10.0.0.1 и 10.99.99.99, если первый вызов привязки выполняется по адресу 10.0.0.1 с портом 5150 и SO_EXCLUSIVEADDRUSE указан, то второй вызов привязки на 10.99.99.99.99 с портом 5150 и никакие параметры не будут указаны. Однако если первый сокет привязан к адресу с подстановочными знаками и порту 5150, то любой последующий вызов привязки к порту 5150 с SO_EXCLUSIVEADDRUSE заданным значением завершится сбоем с WSAEADRINUSE или WSAEACCES , возвращенных операцией привязки .
В случае, когда первый вызов привязки наборов SO_REUSEADDR или вообще без параметров сокета, второй вызов привязки будет "перехватывать" порт, и приложение не сможет определить, какой из двух сокетов получил определенные пакеты, отправленные на "общий" порт.
Обычное приложение, которое вызывает функцию bind , не выделяет привязанный сокет для монопольного использования, если только параметр сокета SO_EXCLUSIVEADDRUSE не вызывается в сокете перед вызовом функции bind . Если клиентское приложение привязывается к эфемерному порту или конкретному порту до привязки серверного приложения к тому же порту, могут возникнуть проблемы. Серверное приложение может принудительно привязаться к тому же порту с помощью параметра сокета SO_REUSEADDR в сокете перед вызовом функции bind , но поведение для всех сокетов, привязанных к порту, будет неопределенным. Если серверное приложение попытается использовать параметр сокета SO_EXCLUSIVEADDRUSE для монопольного использования порта, запрос завершится ошибкой.
И наоборот, сокет с набором SO_EXCLUSIVEADDRUSE не обязательно повторно использоваться сразу после закрытия сокета. Например, если прослушивающий сокет с SO_EXCLUSIVEADDRUSE набором принимает подключение и затем закрывается, другой сокет (также с SO_EXCLUSIVEADDRUSE) не сможет привязаться к тому же порту, что и первый сокет, пока исходное подключение не станет неактивным.
Эта проблема может усложняться, так как базовый транспортный протокол может не завершить подключение, даже если сокет был закрыт. Даже после закрытия сокета приложением система должна передать все буферизированные данные, отправить корректное сообщение об отключении одноранговому элементу и дождаться соответствующего сообщения корректного отключения от однорангового узла. Вполне возможно, что базовый транспортный протокол может никогда не разблокировать подключение; Например, одноранговый узел, участвующий в исходном подключении, может объявить окно нулевого размера или какую-либо другую конфигурацию атаки. В этом случае клиентское подключение остается в активном состоянии, несмотря на запрос на его закрытие, так как непризнанные данные остаются в буфере.
Чтобы избежать этой ситуации, сетевые приложения должны обеспечить корректное завершение работы, вызвав завершение работы с установленным флагом SD_SEND, а затем ожидать в цикле recv , пока не будет возвращено ноль байтов через подключение. Это гарантирует, что все данные будут получены одноранговым элементом, а также подтверждает с одноранговым элементом, что он получил все передаваемые данные, а также позволяет избежать упомянутой выше проблемы повторного использования порта.
Параметр сокета SO_LINGER может быть установлен для сокета, чтобы предотвратить переход порта в состояние ожидания "активно". однако это не рекомендуется, так как это может привести к нежелательным эффектам, таким как сброс подключений. Например, если одноранговый узел получает данные, но остается непризнанным, а локальный компьютер закрывает сокет с установленными SO_LINGER, соединение между двумя компьютерами сбрасывается, а непризнанные данные удаляются одноранговым элементом. Выбрать подходящее время для ожидания трудно, так как меньшее значение времени ожидания часто приводит к внезапному прерыванию подключений, в то время как большие значения времени ожидания оставляют систему уязвимой для атак типа "отказ в обслуживании" (путем установки большого количества подключений и потенциальной остановки или блокировки потоков приложений). Закрытие сокета с ненулевым значением времени ожидания может также привести к блокировке вызова closesocket .
Расширенная безопасность сокетов
Улучшенная безопасность сокетов была добавлена в выпуске Windows Server 2003. В предыдущих выпусках серверной операционной системы Microsoft безопасность сокетов по умолчанию позволяла процессам перехватывать порты из не подозревающих приложений. В Windows Server 2003 сокеты по умолчанию не находятся в состоянии общего доступа. Поэтому, если приложение хочет разрешить другим процессам повторно использовать порт, к которому уже привязан сокет, оно должно специально включить его. В этом случае первый сокет, вызывающий привязку на порту, должен иметь SO_REUSEADDR , заданный в сокете. Единственное исключение в этом случае возникает, когда второй вызов привязки выполняется той же учетной записью пользователя, которая выполнила исходный вызов привязки. Это исключение существует исключительно для обеспечения обратной совместимости.
В таблице ниже описано поведение, которое происходит в операционных системах Windows Server 2003 и более поздних версий, когда второй сокет пытается привязаться к адресу, ранее привязанным к первому сокету, используя определенные параметры сокета.
Примечание
В таблице ниже подстановочный знак обозначает подстановочный адрес для заданного протокола (например, "0.0.0.0" для IPv4 и "::" для IPv6). "Конкретный" обозначает определенный IP-адрес, назначенный интерфейсу. Ячейки таблицы указывают, успешно ли выполнена привязка ("Успешно") или возвращена ошибка ("INUSE" для ошибки WSAEADDRINUSE ; "ACCESS" для ошибки WSAEACCES ).
Кроме того, обратите внимание, что в этой конкретной таблице оба вызова привязки выполняются под одной учетной записью пользователя.
Первый вызов привязки | Второй вызов привязки | ||||||
Значение по умолчанию | SO_REUSEADDR | SO_EXCLUSIVEADDRUSE | |||||
Подстановочный знак | Specific | Подстановочный знак | Specific | Подстановочный знак | Specific | ||
Значение по умолчанию | Подстановочный знак | INUSE | Успешное завершение | ACCESS; | Успешное завершение | INUSE | Успешное завершение |
Specific | Успешное завершение | INUSE | Успешное завершение | ACCESS; | INUSE | INUSE | |
SO_REUSEADDR | Подстановочный знак | INUSE | Успех | Успех | Успех | INUSE | Успешное завершение |
Specific | Успешное завершение | INUSE | Успех | Успех | INUSE | INUSE | |
SO_EXCLUSIVEADDRUSE | Подстановочный знак | INUSE | ACCESS; | ACCESS; | ACCESS; | INUSE | ACCESS; |
Specific | Успешное завершение | INUSE | Успешное завершение | ACCESS; | INUSE | INUSE |
Несколько записей в приведенной выше таблице заслуживают пояснения.
Например, если первый вызывающий объект задает SO_EXCLUSIVEADDRUSE по определенному адресу, а второй пытается вызвать привязку с подстановочными знаками в том же порту, второй вызов привязки будет выполнен успешно. В этом конкретном случае второй вызывающий объект привязан ко всем интерфейсам, кроме конкретного адреса, к которому привязан первый вызывающий объект. Обратите внимание, что в обратном случае это не так: если первый вызывающий объект задает SO_EXCLUSIVEADDRUSE и вызывает привязку с помощью флага с подстановочными знаками, второй вызывающий объект не сможет вызвать привязку с тем же портом.
Поведение привязки сокета изменяется, когда вызовы привязки сокета выполняются в разных учетных записях пользователей. В таблице ниже указано поведение, которое происходит в операционных системах Windows Server 2003 и более поздних версий, когда второй сокет пытается привязаться к адресу, ранее привязанному к первому сокету, используя определенные параметры сокета и другую учетную запись пользователя.
Первый вызов привязки | Второй вызов привязки | ||||||
По умолчанию | SO_REUSEADDR | SO_EXCLUSIVEADDRUSE | |||||
Подстановочный знак | Specific | Подстановочный знак | Specific | Подстановочный знак | Specific | ||
Значение по умолчанию | Подстановочный знак | INUSE | ACCESS; | ACCESS; | ACCESS; | INUSE | ACCESS; |
Specific | Успешное завершение | INUSE | Успешное завершение | ACCESS; | INUSE | INUSE | |
SO_REUSEADDR | Подстановочный знак | INUSE | ACCESS; | Успех | Успех | INUSE | ACCESS; |
Specific | Успешное завершение | INUSE | Успех | Успех | INUSE | INUSE | |
SO_EXCLUSIVEADDRUSE | Подстановочный знак | INUSE | ACCESS; | ACCESS; | ACCESS; | INUSE | ACCESS; |
Specific | Успешное завершение | INUSE | Успешное завершение | ACCESS; | INUSE | INUSE |
Обратите внимание, что поведение по умолчанию отличается, когда вызовы привязки выполняются в разных учетных записях пользователей. Если первый вызывающий объект не задает параметры в сокете и привязывается к адресу с подстановочными знаками, то второй вызывающий объект не может задать параметр SO_REUSEADDR и успешно привязаться к тому же порту. Поведение по умолчанию без заданных параметров также возвращает ошибку.
В Windows Vista и более поздних версиях можно создать сокет с двумя стеками, который работает по протоколам IPv6 и IPv4. Если сокет с двумя стеками привязан к адресу с подстановочными знаками, заданный порт резервируется в сетевых стеках IPv4 и IPv6 и выполняются проверки, связанные с SO_REUSEADDR и SO_EXCLUSIVEADDRUSE (если они заданы). Эти проверки должны быть успешными в обоих сетевых стеках. Например, если сокет TCP с двойным стеком задает SO_EXCLUSIVEADDRUSE , а затем пытается выполнить привязку к порту 5000, то другие TCP-сокеты не могут быть ранее привязаны к порту 5000 (подстановочный знак или конкретный). В этом случае, если сокет TCP IPv4 был ранее привязан к адресу замыкания на себя на порту 5000, вызов привязки для сокета с двойным стеком завершится ошибкой WSAEACCES.
Стратегии приложений
При разработке сетевого приложения, работающего на уровне сокета, важно учитывать необходимый тип безопасности сокета. Клиентским приложениям, которые подключаются к службе или отправляют данные, редко требуются дополнительные действия, так как они привязываются к случайному локальному (эфемеральному) порту. Если клиенту требуется определенная привязка локального порта для правильной работы, необходимо учитывать безопасность сокета.
Параметр SO_REUSEADDR имеет очень мало применений в обычных приложениях, кроме многоадресных сокетов, где данные доставляются во все сокеты, привязанные к одному порту. В противном случае любое приложение, задающее этот параметр сокета, должно быть переработано, чтобы удалить зависимость, так как оно в значительной степени уязвимо для перехвата сокетов. До тех пор, пока SO_REUSEADDR параметр сокета можно использовать для потенциального перехвата порта в серверном приложении, приложение должно рассматриваться как небезопасное.
Все серверные приложения должны задавать SO_EXCLUSIVEADDRUSE для надежного уровня безопасности сокетов. Это не только предотвращает перехват порта вредоносными программами, но и указывает, привязано ли другое приложение к запрошенным портам. Например, вызов привязки к адресу с подстановочными знаками процессом с набором параметров сокета SO_EXCLUSIVEADDRUSE завершится ошибкой, если другой процесс в данный момент привязан к тому же порту в определенном интерфейсе.
Наконец, несмотря на то, что в Windows Server 2003 была улучшена безопасность сокетов, приложение всегда должно задавать параметр SO_EXCLUSIVEADDRUSE сокета, чтобы обеспечить привязку к всем конкретным интерфейсам, запрошенным процессом. Безопасность сокетов в Windows Server 2003 повышает уровень безопасности для устаревших приложений, но разработчики приложений должны по-прежнему проектировать свои продукты с учетом всех аспектов безопасности.