Поделиться через


Использование SO_REUSEADDR и SO_EXCLUSIVEADDRUSE

Разработка безопасной высокоуровневой сетевой инфраструктуры является приоритетом для большинства разработчиков сетевых приложений. Однако безопасность сокетов часто игнорируется, несмотря на то, что очень важно при рассмотрении полностью безопасного решения. Безопасность сокета, в частности, связана с процессами, которые привязываются к одному порту, ранее привязанному к другому процессу приложения. В прошлом сетевое приложение может "перехватывать" порт другого приложения, что может легко привести к "отказу в обслуживании" или краже данных.

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

Клиентские приложения менее вероятно станут целью таких атак — не потому, что они менее уязвимы, а потому, что большинство клиентов привязываются к "эфемерным" локальным портам, а не к статическим "служебным" портам. Клиентские сетевые приложения всегда должны привязаться к временным портам (указав порт 0 в структуре SOCKADDRSOCKADDR, на которую указывает параметр имя при вызове функции привязки), если не существует убедительных архитектурных причин. Временные локальные порты состоят из портов больше порта 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 и более поздних версий Недоступно
Windows 2000 В наличии Доступен Недоступно
Windows XP В наличии Доступен Недоступно
Windows Server 2003 Доступен Доступен Доступен
Windows Vista Доступный Доступный Доступный
Windows Server 2008 Имеется в наличии Доступный Доступен
Windows 7 и более поздней версии Доступный Доступен Доступно

Использование SO_REUSEADDR

Опция SO_REUSEADDR позволяет сокету принудительно привязываться к порту, используемому другим сокетом. Второй сокет вызывает setsockopt с параметром optname, установленным в SO_REUSEADDR, и параметром optval, установленным в логическое значение TRUE, перед вызовом bind на том же порту, что и исходный сокет. После успешной привязки второго сокета поведение всех сокетов, привязанных к порту, является неопределенным. Например, если все сокеты на одном порту предоставляют службу TCP, любые входящие запросы TCP-подключения через порт не могут быть гарантированно обработаны правильным сокетом — поведение не детерминировано. Вредоносные программы могут использовать SO_REUSEADDR для принудительной привязки сокетов, уже используемых для стандартных сетевых протоколов, чтобы запретить доступ к этой службе. Для использования этого параметра не требуются специальные привилегии.

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

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

Использование SO_EXCLUSIVEADDRUSE

До появления параметра сокета SO_EXCLUSIVEADDRUSE разработчик сетевых приложений практически ничего не мог предпринять, чтобы предотвратить привязку вредоносной программы к порту, на котором у сетевого приложения были привязаны собственные сокеты. Чтобы устранить эту проблему безопасности, сокеты Windows представили вариант сокета 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.0" для IPv4 и "::" для IPv6). "Specific" обозначает определенный IP-адрес, назначенный интерфейсом. Ячейки таблицы указывают, успешно ли выполнена привязка ("Успешно") или возвращается ошибка (INUSE" для ошибки WSAEADDRINUSE; "ACCESS" для ошибки WSAEACCES).

Первый вызов привязки к Второй вызов привязки
По умолчанию SO_REUSEADDR SO_EXCLUSIVEADDRUSE
Подстановочный знак Специфический Подстановочный знак Специфический Подстановочный знак Специфический
По умолчанию Подстановочный знак В ИСПОЛЬЗОВАНИИ ЗАНЯТО Успех Успех В ИСПОЛЬЗОВАНИИ В ИСПОЛЬЗОВАНИИ
Специфический Используется В ИСПОЛЬЗОВАНИИ Успех Успех Используется Используется
SO_REUSEADDR Подстановочный знак В ИСПОЛЬЗОВАНИИ В ИСПОЛЬЗОВАНИИ Успех Успех В ИСПОЛЬЗОВАНИИ ЗАНЯТО
Специфический В использовании ИСПОЛЬЗУЕТСЯ Успех Успех В использовании INUSE
SO_EXCLUSIVEADDRUSE - Флаг, который запрещает повторное использование адреса сокета. Подстановочный знак ЗАНЯТО ЗАНЯТО ДОСТУП ДОСТУП В ИСПОЛЬЗОВАНИИ в использовании
Специфический ИСПОЛЬЗУЕТСЯ В использовании ДОСТУП ДОСТУП В ИСПОЛЬЗОВАНИИ В использовании

Если два сокета привязаны к одному номеру порта, но на разных явных интерфейсах, конфликта нет. Например, в случае, если компьютер имеет два IP-интерфейса, 10.0.0.1 и 10.99.99.99, и первый вызов функции bind совершается на 10.0.0.1 с портом 5150 и параметром SO_EXCLUSIVEADDRUSE, второй вызов функции bind на 10.99.99.99 также с портом 5150 без указания параметров будет успешным. Однако если первый сокет привязан к подстановочному адресу и порту 5150, то любой последующий вызов привязки к порту 5150 с установленным SO_EXCLUSIVEADDRUSE завершится ошибкой либо с WSAEADDRINUSE, либо с WSAEACCES, вернутой операцией привязки.

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

Обычное приложение, которое вызывает функцию bind, не резервирует привязанный сокет для монопольного использования, если только опция сокета SO_EXCLUSIVEADDRUSE не установлена до вызова функции bind. Если клиентское приложение привязывается к эфемерному порту или конкретному порту, прежде чем серверное приложение привязывается к тому же порту, могут возникнуть проблемы. Серверное приложение может форсированно привязаться к тому же порту, используя параметр сокета SO_REUSEADDR на сокете перед вызовом функции bind, но поведение всех сокетов, привязанных к этому порту, становится неопределенным. Если серверное приложение пытается использовать параметр сокета SO_EXCLUSIVEADDRUSE для эксклюзивного использования порта, запрос завершится ошибкой.

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

Эта проблема может стать сложной, так как базовый транспортный протокол может не завершить подключение, даже если сокет был закрыт. Даже после закрытия сокета приложением система должна передавать все буферные данные, отправлять корректное сообщение об отключении в одноранговый узел и ждать соответствующего корректного сообщения об отключении от однорангового узла. Возможно, базовый транспортный протокол никогда не освобождает подключение; Например, одноранговый узел, участвующий в исходном соединении, может объявлять окно нулевого размера или другую форму конфигурации "атаки". В таком случае подключение клиента остается активным, несмотря на запрос на закрытие, так как необнаруженные данные остаются в буфере.

Чтобы избежать этой ситуации, сетевые приложения должны обеспечить правильное завершение работы путем вызова завершения работы с установленным флагом SD_SEND, а затем ждать в цикле, пока не будет возвращено нулевое число байтов по подключению. Это гарантирует, что все данные получены одноранговым узлом и подтверждает, что он получил все переданные данные, а также избегает вышеупомянутой проблемы повторного использования портов.

Параметр SO_LINGER может быть установлен на сокете, чтобы предотвратить переход порта в состояние активного ожидания; однако, это не рекомендуется, так как может привести к нежелательным последствиям, таким как сброс подключений. Например, если данные получены пиром, но остаются неподтвержденными, а локальный компьютер закрывает сокет с установленным на нем SO_LINGER, соединение между двумя компьютерами сбрасывается, а неподтвержденные данные удаляются пиром. Выбор подходящего времени для ожидания трудно, так как меньшее значение времени ожидания часто приводит к внезапному прерыванию подключений, в то время как большие значения времени ожидания оставляют систему уязвимой для атак типа "отказ в обслуживании" (путем установления множества подключений и потенциально зависания или блокировки потоков приложений). Закрытие сокета, имеющего ненулевое значение времени задержки, также может привести к блокировке вызова closesocket.

Улучшенная безопасность сокета

Улучшенная безопасность сокета была добавлена с выпуском Windows Server 2003. В предыдущих выпусках операционной системы Microsoft Server безопасность сокетов по умолчанию позволяет процессам перехватывать порты из неуспеченных приложений. В Windows Server 2003 сокеты не находятся в состоянии совместного использования по умолчанию. Таким образом, если приложение хочет разрешить другим процессам повторно использовать порт, на котором уже привязан сокет, он должен специально включить его. Если это так, первый сокет, вызывающий привязку на порту, должен иметь установленное на сокете значение SO_REUSEADDR. Единственное исключение в этом случае возникает, когда второй вызов привязки выполняется той же учетной записью пользователя, что и исходный вызов привязки. Это исключение существует исключительно для обеспечения обратной совместимости.

В приведенной ниже таблице описывается поведение, которое происходит в операционных системах Windows Server 2003 и более поздних версий, когда второй сокет пытается привязаться к адресу, ранее привязанном к первому сокету с помощью определенных параметров сокета.

Заметка

В приведенной ниже таблице подстановочный знак обозначает подстановочный адрес для заданного протокола (например, "0.0.0.0.0" для IPv4 и "::" для IPv6). "Specific" обозначает определенный IP-адрес, назначенный интерфейсом. Ячейки таблицы указывают, успешно ли выполнена привязка ("Успешно") или возвращена ошибка ("INUSE" для ошибки WSAEADDRINUSE; "ACCESS" для ошибки WSAEACCES).

Кроме того, обратите внимание, что в этой конкретной таблице оба вызова связки выполняются под одной учетной записью пользователя.

Первый вызов привязки Второй вызов привязки
По умолчанию SO_REUSEADDR Режим_эксклюзивного_использования_адресаSO
Подстановочный знак Специфический Подстановочный знак Специфический Подстановочный знак Специфический
По умолчанию Подстановочный знак Используется Успех ДОСТУП Успех ЗАНЯТО Успех
Специфический Успех В ИСПОЛЬЗОВАНИИ Успех ДОСТУП В ИСПОЛЬЗОВАНИИ В ИСПОЛЬЗОВАНИИ
SO_REUSEADDR Подстановочный знак В ИСПОЛЬЗОВАНИИ Успех Успех Успех В ИСПОЛЬЗОВАНИИ Успех
Специфический Успех В ИСПОЛЬЗОВАНИИ Успех Успех В ИСПОЛЬЗОВАНИИ ИСПОЛЬЗУЕТСЯ
SO_EXCLUSIVEADDRUSE Подстановочный знак Используется ДОСТУП ДОСТУП ДОСТУП используется ДОСТУП
Специфический Успех ЗАНЯТО Успех ДОСТУП В использовании Используется

Несколько записей в таблице выше заслуживают пояснения.

Например, если первый вызывающий задает SO_EXCLUSIVEADDRUSE на определенном адресе, а второй вызывающий пытается вызвать привязать с универсальным адресом на том же порту, второй привязать вызов пройдет успешно. В этом случае второй вызывающий объект привязан ко всем интерфейсам, кроме определенного адреса, к которому привязан первый вызывающий объект. Обратите внимание, что обратное утверждение неверно: если первый вызывающий задает SO_EXCLUSIVEADDRUSE и вызывает функцию bind с флагом подстановочного знака, второй вызывающий не может вызвать функцию bind с тем же портом.

Поведение привязки сокета изменяется, когда вызовы привязки сокетов делаются от разных учетных записей пользователей. В таблице ниже указывается поведение, которое происходит в операционных системах Windows Server 2003 и более поздних версий, когда второй сокет пытается привязаться к адресу, ранее привязанному к первому сокету, с использованием определенных параметров сокета и другой учетной записи пользователя.

Первый вызов привязки , Второй вызов привязки
По умолчанию SO_REUSEADDR SO_EXCLUSIVEADDRUSE
Подстановочный знак Специфический Подстановочный знак Специфический Подстановочный знак Специфический
По умолчанию Подстановочный знак ЗАНЯТО ДОСТУП ДОСТУП ДОСТУП В ИСПОЛЬЗОВАНИИ ДОСТУП
Специфический Успех Используется Успех ДОСТУП В ИСПОЛЬЗОВАНИИ ЗАНЯТО
SO_REUSEADDR Подстановочный знак В ИСПОЛЬЗОВАНИИ ДОСТУП Успех Успех ИСПОЛЬЗУЕТСЯ ДОСТУП
Специфический Успех В ИСПОЛЬЗОВАНИИ Успех Успех ЗАНЯТО ЗАНЯТО
SO_EXCLUSIVEADDRUSE Подстановочный знак В ИСПОЛЬЗОВАНИИ ДОСТУП ДОСТУП ДОСТУП В ИСПОЛЬЗОВАНИИ ДОСТУП
Специфический Успех В ИСПОЛЬЗОВАНИИ Успех ДОСТУП INUSE используется

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

В Windows Vista и более поздних версиях можно создать двойной сокет стека, который работает как с IPv6, так и с IPv4. Если двойной стековый сокет привязан к адресу-джокеру, заданный порт зарезервирован в сетевых стеках IPv4 и IPv6, и выполняются проверки, связанные с SO_REUSEADDR и SO_EXCLUSIVEADDRUSE (если установлено). Эти проверки должны завершаться успешно на обоих сетевых стеках. Например, если tcp-сокет с двойным стеком устанавливает SO_EXCLUSIVEADDRUSE и затем пытается привязаться к порту 5000, то никакой другой tcp-сокет не может быть ранее привязан к порту 5000 (ни с использованием подстановочного знака, ни конкретно). В этом случае, если TCP-сокет IPv4 был ранее привязан к loopback-адресу на порту 5000, вызов привязки для двойного сокета будет заканчиваться неудачей с WSAEACCES.

Стратегии приложений

При разработке сетевого приложения, работающего на уровне сокета, важно учитывать тип безопасности сокета. Клиентские приложения — приложения, которые подключают или отправляют данные в службу, редко требуют дополнительных шагов, так как они привязываются к случайному локальному порту (эфемерного) порта. Если клиенту требуется определенная локальная привязка портов для правильной работы, необходимо учитывать безопасность сокета.

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

Все серверные приложения должны задавать SO_EXCLUSIVEADDRUSE для строгого уровня безопасности сокета. Это не только предотвращает перехват вредоносного программного обеспечения порта, но и указывает, привязано ли другое приложение к запрошенному порту. Например, вызов функции для привязки на подстановочный адрес процессом, у которого установлен параметр сокета SO_EXCLUSIVEADDRUSE, завершится неудачей, если другой процесс уже привязан к тому же порту на определенном интерфейсе.

Наконец, несмотря на то, что безопасность сокетов была улучшена в Windows Server 2003, приложение всегда должно задать параметр сокета SO_EXCLUSIVEADDRUSE, чтобы убедиться, что он привязывается ко всем определенным интерфейсам, запрошенным процессом. Безопасность сокета в Windows Server 2003 добавляет повышенный уровень безопасности для устаревших приложений, но разработчики приложений по-прежнему должны разрабатывать свои продукты с учетом всех аспектов безопасности.