В этой небольшой заметке я расскажу о принципах работы брандмауэра Iptables на примере фильтрации пакетов с помощью TCP-флагов и с помощью модуля определения состояний conntrack. Она рассчитана на тех, кто уже знаком с Iptables и желает глубже понять логику его функционирования.
Разрешение WEB-трафика с помощью TCP-флагов
Допустим, что на нашем сервере находится корпоративный сайт, к которому извне должны подключаться сотрудники. Исходя из best practices по составлению правил для брандмауэра Iptables, политика цепочки OUTPUT по умолчанию ACCEPT, а политика INPUT – DROP:
*filter
:INPUT DROP [1691:75254] :OUTPUT ACCEPT [4838:560591]
Если соединение инициируется извне, то согласно классической схеме установления TCP-соединения (TCP-handshake) мы получаем первый пакет с битом SYN, в ответ на который посылаем подтверждающий пакет с битами SYN и ACK. Все последующие входящие пакеты, связанные с первоначальным, будут приходить только с битом ACK. Следовательно, чтобы разрешить входящий трафик на порт назначения 80 необходимо прописать 2 разрешения — одно с флагом SYN (первый входящий пакет), второе с флагом ACK (третий и последующие):
iptables -A INPUT -p tcp --tcp-flags FIN,SYN,RST,ACK SYN --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --tcp-flags SYN,ACK ACK --dport 80 -j ACCEPT
Флаги FIN (завершение соединения) и RST (сброс соединения) исключены из второго правила по той причине, что они понадобятся для завершения сессии клиент-сервер, иначе она какое-то время будет занимать ресурсы на обеих сторонах уже после завершения обмена данными.
После этого пользователи смогут беспрепятственно обращаться к нашему веб-серверу.
Если же мы попытаемся обновить ПО сервера, то теперь сам сервер будет выступать в качестве инициатора соединения и отправлять первый пакет TCP-рукопожатия с битом SYN. В ответ от внешнего сервера с его 80-го порта должен прийти 2-й пакет с битами SYN и ACK, который нужно пропустить. Если этого не сделать, то обращение по telnetу на 80 порт, например ya.ru, будет оставлено без ответа:
root@server:~# telnet ya.ru 80
Trying 213.180.193.3...
Поэтому добавим разрешающее правило:
iptables -A INPUT -p tcp --tcp-flags FIN,SYN,RST,ACK SYN,ACK --sport 80 -j ACCEPT
После повторного обращения к ya.ru мы увидим, что сессия вроде бы установлена:
root@server:~# telnet ya.ru 80
Trying 213.180.193.3...
Connected to ya.ru.
Escape character is '^]'.
но по завершении тройного TCP-рукопожатия мы уже не сможем принять ни одного пакета данных, так как все они будут приходить с 80-го порта удалённого сервера только с битом ACK, для которого правило не прописано. Добавим его
iptables -A INPUT -p tcp --tcp-flags SYN,ACK ACK --sport 80 -j ACCEPT
и получим полный доступ к интернету с сервера.
В итоге, правила будут выглядеть следующим образом:
iptables -A INPUT -p tcp --tcp-flags FIN,SYN,RST,ACK SYN --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --tcp-flags SYN,ACK ACK --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --tcp-flags FIN,SYN,RST,ACK SYN,ACK --sport 80 -j ACCEPT
iptables -A INPUT -p tcp --tcp-flags SYN,ACK ACK --sport 80 -j ACCEPT
Модуль отслеживания состояния пакетов сonntrack
На практике нет особой необходимости в написании правил фильтрации с TCP-флагами, как мы поступали в предыдущем параграфе. Для того, чтобы отличать «правильные» пакеты от «неправильных», в брандмауэре iptables предусмотрен модуль отслеживания состояний conntrack.
Модуль помечает каждый пакет специальными метками, основные из которых представлены в таблице:
Следует понимать, что ESTABLISHED и RELATED пакеты не могут появиться сами по себе – они появляются в результате инициирования TCP-соединения с помощью самого первого NEW-пакета и логически привязаны к нему. Это позволяет все пакеты, которые адресованы нашей системе, но не подпадают под описанные критерии, считать INVALID и отбрасывать как подозрительные.
Поэтому, самым распространённым правилом, разрешающим прохождение лишь «правильного» трафика, является глобальное разрешение только входящих ESTABLISHED,RELATED пакетов на любые порты внешнего интерфейса, например eth0 (если интерфейс один, то его можно явно не указывать):
iptables -A INPUT -i eth0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
А появиться они могут только после инициирования TCP-соединений извне по конкретным портам, которые и прописываются отдельно, например:
iptables -A INPUT -i eth0 -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT
TCP-соединения, инициированные с сервера, т.е. пакеты с меткой NEW, будут разрешены политикой цепочки OUTPUT, все же остальные пакеты будут принадлежать уже к категории ESTABLISHED и будут разрешены цепочкой INPUT автоматически.
Обычно именно в таком виде правила брандмауэра и рекомендуется задавать в большинстве случаев, поскольку это избавляет от необходимости анализировать TCP-флаги SYN, ACK и т.п.
В итоге, 4 правила из предыдущего параграфа можно заменить двумя новыми (одним общим и одним частным):
iptables -A INPUT -i eth0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i eth0 -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT
В действительности, для полного контроля над ситуацией в начале 2000-х желательно было сопоставлять метки, назначенные пакету модулем conntrack, с актуальным состоянием битов в них с помощью опции ––tcp–flags для параметра –p tcp. В те времена была распространена атака типа спуфинга (подделки адресов отправителя), в ходе которой на атакуемый хост посылался пакет с битом SYN для инициализации TCP-соединения, но в адресе отправителя указывался, например, наш хост. В ответ атакуемый присылал нам пакет с установленными битами SYN и ACK, но для нашей машины этот пакет оказывался новым (NEW), поэтому брандмауэр должен был бы отбросить этот пакет ввиду его инвалидности и закрыть соединение. Если же этого по каким-то причинам не происходило, то TCP-сессия оставалась незакрытой, давая злоумышленнику возможность использовать её для дальнейшего взлома, при этом все следы вели бы на наш сервер. Либо можно было бы засыпать атакуемый сервер такими SYN-запросами (т.н. SYN-flood), открывая всё новые и новые сессии и, таким образом, подставить нас. Именно поэтому рекомендовалось явным образом пресекать подобные попытки, отсылая ответ с флагом RST для гарантированного уничтожения соединения:
iptables -A INPUT -m conntrack --ctstate NEW,INVALID -p tcp --tcp-flags SYN,ACK SYN,ACK -j REJECT --reject-with tcp-reset
Сейчас подобные атаки уже неактуальны, но если включить логирование таких пакетов, то эпизодически они всё же будут обнаруживаться в логах сервера.