Прочитав по диагонали статью гражданина @Winseven«ICMP открывашка портов для сервера», я сдержался. Все-таки велосипединг — это весело. Но вчитавшись, я опешил. Зачем запускать отдельное приложение для отслеживания нужных пакетов? Правильно ли, что достаточно один раз попасть пальцем в небо, чтобы порт был открыт? По мне, как-то не по фэншую.

Душа все это не вынесла, и я решился на статью.

Какие инструменты будут использоваться?

  1. Мне привычнее иметь дело с iptables. Он нужен для всего: запрета пакетов, добавления адресов в списки и прочих файрвольных штучек.

  2. Для составления списков используем ipset.

Принцип работы:

  • Пользователь посылает серию специальных пакетов нужному серверу.

  • Сервер, получив первый правильный пакет, заносит кандидата в первый список.

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

  • Несколько итераций с переносом кандидата по спискам.

  • Сервер, получив последний правильный пакет, переносит кандидата в список разрешенных подключений, при условии нахождения в предпоследнем списке.

Для примера рассмотрю комбинацию из трех пакетов ICMP (Ping) разного размера (999, 1028 и 500 байтов).

Для начала создам все необходимые списки:

sudo ipset create knock_allow hash:net,iface timeout 60
sudo ipset create knock_step_1 hash:ip timeout 2
sudo ipset create knock_step_2 hash:ip timeout 2

Если долго смотреть на команды выше, можно заметить параметр timeout. Согласно документации, этот параметр отвечает за то, сколько времени в секундах запись будет присутствовать в списке. То есть для данного решения у пользователя будет минута на подключение по ssh.

Теперь пишу правила. Файрвол должен быть чистым:

sudo iptables -N INPUT_NEW
sudo iptables -N PORTKNOCKING
sudo iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -m conntrack --ctstate NEW -j INPUT_NEW
sudo iptables -A INPUT_NEW -j PORTKNOCKING
sudo iptables -A INPUT_NEW -p tcp -m tcp --dport 22 -m set --match-set knock_allow src,src -j ACCEPT
sudo iptables -A PORTKNOCKING -p icmp --icmp-type 8 -m connbytes --connbytes 500:500 --connbytes-mode bytes --connbytes-dir original -m set --match-set knock_step_2 src -j SET --add-set knock_allow src,src --exist
sudo iptables -A PORTKNOCKING -p icmp --icmp-type 8 -m connbytes --connbytes 1028:1028 --connbytes-mode bytes --connbytes-dir original -m set --match-set knock_step_1 src -j SET --add-set knock_step_2 src
sudo iptables -A PORTKNOCKING -p icmp --icmp-type 8 -m connbytes --connbytes 999:999 --connbytes-mode bytes --connbytes-dir original -j SET --add-set knock_step_1 src

Что я накостылял?

  1. Запретил все пакеты, у которых состояние INVALID.

  2. Разрешил все установленные соединения (ESTABLISHED).

  3. RELATED добавил для тех парней, которые не сильно знают, что это, а по попе получать не хотят.

  4. Все новые соединения я обрабатываю особо: сначала пропускаю через Port knocking, а затем все разрешенные пакеты разрешаю.

Почему я рассматриваю списки с начала в конец? Потому что при прохождении правил, таргет SET не прекращает прохождение по списку правил файрвола. Это значит, что послав один пакет, можно (гипотетически) сразу попасть во все списки. В данном примере, конечно, не получится, но все равно лучше смотреть с конца в начало.

Собственно, вся магия!

Как проверить?

Для начала установим политику по умолчанию в DROP (помните, что это может сломать ваааааще все!) Так что делайте с осторожностью и имейте возможность физического доступа к серверу:

sudo iptables -P INPUT DROP

Затем на виндах делаем следущее:

ping -l 971 -w 100 -n 1 mysrv.com; ping -l 1000 -w 100 -n 1 mysrv.com; ping -l 472 -w 100 -n 1 mysrv.com; ssh mysrv.com

На удивление, все работает, даже если порты закрыты =) Но почему размер пакета указан не такой, какой указан в настройке файрвола? Потому что добавляется 28 бит заголовков пакета.

Сочинение выше — базис. На его основе можно делать:

  • Кнокеры не только по ICMP, но и по TCP, UDP и прочие фантазии.

  • Кнокеры из сложных рукопожатий, включая промежутки тишины.

  • Примитивный Fail2Ban. Я рисовал отдельный чайник для тех, кто в списке банов, где в 75% ты попадёшь на TARPIT, в 20% — на DROP и в 5% — на REJECT.

Помимо этого, правила можно улучшить:

  • Проверять, что перестук не начался сначала, дойдя до середины.

  • Игнорировать перестуки, если пользователь и так в нужном списке.

Но мне чуть-чуть лень =)

P.S.

Велосипеды, конечно, хорошо, но давайте читать документацию:

Комментарии (23)


  1. savostin
    29.06.2022 14:09
    +9

    Неделя port knocking на Хабре! Ждем следующую статью "Я тут почитал предыдущие, ну нельзя же так!" Вот за это и любим наш Хабр ;)


    1. Yaris
      29.06.2022 14:21
      +1

      Может, это классическая схема с "... захожу под другим именем и пишу заведомо неправильный ответ" :-)


      1. Winseven
        29.06.2022 14:23

        Да, так и есть)))


      1. ZakharovAV Автор
        29.06.2022 15:14
        +1

        Тогда если завтра выйдет пост про реализацию PortKnocking через NFQueue, то это буду либо я, либо наши сеньоры =))
        Шутка.


        1. snp
          29.06.2022 15:38
          +3

          Думаю, простого комментария по этой теме хватит :)


          #!/usr/sbin/nft -f
          
          flush ruleset
          
          define SSH_KNOCK_PING_LEN1 = 128
          define SSH_KNOCK_PING_LEN2 = 138
          define SSH_KNOCK_PING_LEN3 = 148
          define SSH_KNOCK_PING_LEN4 = 158
          
          table inet filter {
            set s_ip_ssh_knock_step1 { type ipv4_addr; timeout 15s; gc-interval 5s; }
            set s_ip_ssh_knock_step2 { type ipv4_addr; timeout 15s; gc-interval 5s; }
            set s_ip_ssh_knock_step3 { type ipv4_addr; timeout 15s; gc-interval 5s; }
            set s_ip_ssh_knock_step4 { type ipv4_addr; timeout 15s; gc-interval 5s; }
          
            chain input {
              type filter hook input priority filter;
          
              ct state invalid counter drop
              ct state related,established counter accept
          
              ct state new tcp dport ssh ip saddr @s_ip_ssh_knock_step4 counter accept
              icmp type echo-request jump input_knock
              ct state new tcp dport ssh counter drop
            }
            chain input_knock {
              ip saddr @s_ip_ssh_knock_step3 meta length $SSH_KNOCK_PING_LEN4 add @s_ip_ssh_knock_step4 { ip saddr } counter
              ip saddr @s_ip_ssh_knock_step2 meta length $SSH_KNOCK_PING_LEN3 add @s_ip_ssh_knock_step3 { ip saddr } counter
              ip saddr @s_ip_ssh_knock_step1 meta length $SSH_KNOCK_PING_LEN2 add @s_ip_ssh_knock_step2 { ip saddr } counter
                                             meta length $SSH_KNOCK_PING_LEN1 add @s_ip_ssh_knock_step1 { ip saddr } counter
            }
          }


    1. Winseven
      29.06.2022 14:25

      Вы что?! Нельзя использовать PortKnocker, используйте VPN, Nginx... и вообще желательно отключить фаервол)))


  1. Winseven
    29.06.2022 14:21
    +2

    Извините, если мой велосипед Вас задел!)))


    1. ZakharovAV Автор
      29.06.2022 14:46
      +1

      Спасибо, что держите в тонусе. =)


  1. up40k
    29.06.2022 14:39

    Это значит, что послав один пакет, можно (гипотетически) сразу попасть во все списки.

    Вы сравниваете размер и нахождение в предыдущем сете. Размер реального пакета удовлетворяет только одной из записей в таблице. Сколько бы из них и в каком порядке вы ни тестировали. Какая разница, в каком порядке располагать записи? Каждый пакет будет прописывать адресанта в свой сет.


    1. ZakharovAV Автор
      29.06.2022 14:42

      только если адресант есть в предыдущем. то есть послав пакет размером 1028 раньше 999 в 3 список попасть не получится. Как я и говорил, если бы было 3 записи размером 999, то первый же пакет записал адресанта во все списки. А это - не зер гут


      1. up40k
        29.06.2022 14:52

        только если адресант есть в предыдущем. то есть послав пакет размером 1028 раньше 999 в 3 список попасть не получится.

        Ага, так и задумано ведь.

        Как я и говорил, если бы было 3 записи размером 999, то первый же пакет записал адресанта во все списки.

        Не говорили :) при одинаковом размере да, только обратная проверка сетов работает (как единственное условие сравнения при всегда прямом прохождении ip-таблиц).


        1. ZakharovAV Автор
          29.06.2022 14:53

          А вот и говорил =)))
          "Почему я рассматриваю списки с начала в конец? Потому что при прохождении правил, таргет SET не прекращает прохождение по списку правил файрвола. Это значит, что послав один пакет, можно (гипотетически) сразу попасть во все списки. В данном примере, конечно, не получится, но все равно лучше смотреть с конца в начало."


          1. up40k
            29.06.2022 15:03
            +1

            Простите за занудство, но вы привели идеальный пример и не указали крайние случаи :) Гипотетическая область значений подразумевает явное их указание, имхо. Хотя бы для исключения таких вопросов, как мой.


            1. ZakharovAV Автор
              29.06.2022 15:05
              +1

              Согласен. Впредь буду постараться яснее проецировать мысли на бумагу


    1. Tarakanator
      29.06.2022 14:46
      +1

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


      1. ZakharovAV Автор
        29.06.2022 14:48

        Именно так


  1. titbit
    29.06.2022 15:41
    +2

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


    1. ZakharovAV Автор
      29.06.2022 15:49

      С нетерпением жду ваш вариант. Мне он уже по нраву =)))


      1. avelor
        29.06.2022 17:53
        +1

        немного наркомании - брать OTP, и генерить скриптом правила айпитаблей, отсекая например 1 и 5 число в 6-символьном OTP. оно и будет портом для пастука (например) %)



    1. GennPen
      29.06.2022 17:31

      Тут надо хитрее, список портов должен зависеть от чего-то глобального, например от времени. Схема будет сложнее, зато безопаснее.

      Можно пойти дальше, если есть IPv6 /64 подсеть, то еще и IPv6 адрес генерировать в зависимости от времени, например раз в минуту менять на новый.


    1. savostin
      30.06.2022 00:03

      Прикрутить OTP в качестве набора пакетов.


  1. 13werwolf13
    30.06.2022 14:44

    следующая статья будет про knockd