Имею парочку VDSок для различных нужд (почта, веб-сервер, хранилка и т.п.) так вот, возникла необходимость скрывать порты (22, 443 и т.п.) от посторонних глаз. Немного подумав (идея уже не новая), решил написать простенький, так сказать, ICMP knocker, то есть открытие портов по пингу. Но пингу не простому, а с определенным размером сообщения/пакета. Пример для линукс:

ping -s 999 -c1 mysrv.com

Где -s - размер отправляемого сообщения, -с количество. Если совпадает размер сообщения, то для IP-адреса отправителя открываются указанные порты на некоторое время и отравляется сообщение об открытие портов в Телеграмм. Так для реализации данной поделки понадобятся: ufw, curl, tcpdump:

apt install ufw curl tcpdump

и сам скрипт:

nano /opt/port-knocker.sh

#!/bin/bash
PING_SIZE=999
PORTS='tcp/22 tcp/443'
TIMEOUT=3600

TOKEN="121212XXXX:XXXXZZZZAAAAEEEERRRRQQQQGGGGTTTTHHHH"
CHATID="55555XXXX"

PACKAGE_SIZE=$(( $PING_SIZE + 28 ))

function allow_access {
    RESULT=`ufw allow proto $PROTO from $SRC to $DST port $PORT`
    echo "`date` _ PORT-knocker _ $SRC -> $DST:$PORT :  $RESULT"
    MESS="<b>`uname -n`</b> %0A`date` %0Aufw allow proto tcp from $SRC to $DST PORT $PORT %0A$RESULT"
    curl -s "https://api.telegram.org/bot${TOKEN}/sendMessage?chat_id=${CHATID}&text=$MESS&parse_mode=HTML" >/dev/null &
}

function deny_access {
    sleep ${TIMEOUT} && RES=$(`ufw show added | grep "$DST" | grep "$SRC" | grep "port $PORT" | grep "proto $PROTO" | sed 's|ufw |ufw delete |g'`) && echo "`date` _ PORT-knocker _ $SRC -> $DST:$PORT : $RES" &
}

while true
do
        LINE=`timeout 3600 tcpdump -n -c1 -i any icmp[icmptype] == icmp-echo and ip[2:2] == $PACKAGE_SIZE 2>/dev/null`

        if [ -n "$LINE" ]
        then
            IP=`echo $LINE | sed 's|^.*IP ||' | tr ':' ' ' | awk '{print $1" "$3 }'`
            SRC=`echo $IP | awk {'print $1'}`
            DST=`echo $IP | awk {'print $2'}`
            if [ -n "$SRC" ] && [ -n "$DST" ]
            then
                for PPORT in ${PORTS}
                do
                    PROTO=`echo $PPORT | sed 's|/| |g' | awk '{print $1}'`
                    PORT=`echo $PPORT | sed 's|/| |g' | awk '{print $2}'`
                    if ufw show added | grep "$DST" | grep "$SRC" | grep " $PORT"
                    then
                        deny_access
                    else
                        allow_access
                        deny_access
                    fi
                done
            fi
        else
            echo "`date` - Timeout, reload sniffer"
        fi

done

Где,

PING_SIZE - размер сообщения
PORTS='tcp/22 tcp/443' - протокол и порт
TIMEOUT=3600 - таймаут открытия порта в секундах

TOKEN - Телеграмме BotFather подскажет по запросу "/mybots" -> "API Token"
CHATID - Телеграмме IDBot подскажет по запросу "/getid"

Делаем скрипт исполняемым:

chmod +x /opt/port-knocker.sh

Чтобы скрипт работал как сервис:

nano /etc/systemd/system/pk.service

[Unit]
Description=Start port-knocker

[Service]
StandardOutput=syslog
StandardError=syslog
WorkingDirectory=/opt/
Type=simple
ExecStart=/bin/bash /opt/port-knocker.sh
KillMode=process
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

Запускаем скрипт:

systemctl start pk

systemctl enable pk

Проверяем:

ping -s 999 -c1 mysrv.com

Должно пройти сообщение в телегу, о том порты открыты для такого-то IP

Если все ОК, то можно активировать UFW:

ufw enable

ufw show added

Повторить:

ping -s 999 -c1 mysrv.com

и глянуть опять:

ufw show added

ufw allow from XXX.XXX.XXX.XXX to YYY.YYY.YYY.YYY port 22 proto tcp 
ufw allow from XXX.XXX.XXX.XXX to YYY.YYY.YYY.YYY port 443 proto tcp

Значит всё ОК, у меня всё!)))

P.S. Уже есть заготовка на Python3, если будет востребована, то перепишу)

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


  1. dmitryrf
    28.06.2022 11:29
    +4

    Идея, действительно, не новая. Если интересно посмотреть на существующее решение, то вот: linux.die.net/man/1/knockd


  1. iig
    28.06.2022 11:29
    +2

    Есть готовый knockd, кстати.


  1. OOOTehnologiiBezopasnosti
    28.06.2022 11:35
    +3

    Интересный скрипт =))) Добавлю, что сам метод стар как мир и имеет название не ICMP открывашка, а "Port Knocking" =))) По крайней мере, у микротофилов довольно распространенная штука)) Можно, кстати, делать списки и организовывать многоуровневый порт кнокинг, чтобы первый удачный пакет лишь помещал IP адрес в белый список, который позволяет пробить пинг с новым размером пакета, и так далее. Плюс списки можно разделять, чтобы открывать доступ к определенным сервисам. А тех, кто не угадал (и вообще, перебирать начинает) - в бан на несколько месяцев. Но стандартный пинг со стандартным размером пакета надо разрешить, ибо без него может быть проблема с маршрутизацией - некоторые маршрутизаторы в интернете пингуют клиентский внешний адрес, который к нему уже обращался - что-то вроде keepalive для соединения.


    1. Winseven Автор
      28.06.2022 11:59
      +1

      Спасибо за идею, следующий этап: от простого к сложному)))


    1. Akina
      28.06.2022 13:16
      +2

      Но стандартный пинг со стандартным размером пакета надо разрешить

      А пинг вообще трогать не надо. Он должен фунциклировать так, словно кнокера вообще нет. То есть контроль размера входящего пакета и добавление разрешающего правила идут не вместо стандартной реакции на ICMP, а в дополнение/параллель к нему.

      Заодно хрен определить, что пинг с пакетом в сколько-то байтов выполнил какую-то специфическую функцию. Особенно если между его приходом и открытием доступа положить полуслучайный тайм-аут.

      A ещё можно надставить скрипт так, чтобы между пингом и открытием доступа был тайм-аут секунд в 5-10, а при поступлении в течение этого времени неправильного пинга запланированное открытие доступа для адреса-источника отменялось. Тогда брутфорс-скрипты совсем бессмысленны.


      1. Winseven Автор
        28.06.2022 20:26
        +1

        К слову, я ICMP вообще не трогаю, только нюхаю ICMP пакет с определенным размером
        tcpdump -n -c1 -i any icmp[icmptype] == icmp-echo and ip[2:2] == $PACKAGE_SIZE


  1. Maksmsk
    28.06.2022 11:41
    +1

    Не проблема написать скрипт который откроет ваши порты меньше чем за минуту. Учитывая что стандартный mtu 1500, то надо всего 1500 пакетов отправить с размером от 0 до 1500 это меньше минуты. Такая себе защита. Вы на сервер вешаете публичный адрес или все icmp пакеты приходящие на маршрутизатор/firewall натите в сторону сервера?


    1. Winseven Автор
      28.06.2022 11:57

      Вы знаете IP-адреса моих серверов, чтобы открыть их? Скрыть порты, а не защитить! Не вижу принципиальной разницы: натить трафик или вешать публичный IP на хост, например: мой домашний провайдер натит (1:1) трафик со своего внешнего адрес на мой серый IP.


    1. dabystru
      28.06.2022 18:38

      По идее, защита от этого — открывать порты не по одному пингу, а после прихода 256 пакетов, каждый из которых имеет заданный размер из 0...1500.


      1. iig
        28.06.2022 20:16
        +2

        Каждый входящий ICMP пакет запускает несколько экземпляров bash, внутри которых крутятся sed и awk. Для хорошей, качественной DOS атаки не нужен ботнет, достаточно обычного ping. Так себе защита.


  1. rstepanov
    28.06.2022 11:57

    А не лучше спрятать 443/tcp за Nginx, который будет перенаправлять все запросы с определенным URL на ваш секретный сервис, а с другими URL - показывать сайт про кошечек/собачек?


    1. Winseven Автор
      28.06.2022 12:01

      Конечно лучше, 443 порт как пример указал


  1. net_racoon
    28.06.2022 16:20

    Никогда не понимал нокинг, костыли какие-то ИМХО. Повешайте сервис на неродной порт и не будут боты его там искать. Все равно, если будут целенаправленно долбить ваш хост, нокинг вас не спасет (а может даже и хуже сделает).

    А если хочется прям безопасную безопасность, то делайте полноценный VPN во внутреннюю сеть.


    1. Winseven Автор
      28.06.2022 20:20
      +1

      Я ж не утверждаю, что это прям продакшн решение, просто поделился идеей.
      Вот есть у меня VDSка за 300 руб/мес и цепляюсь я к ней раз в неделю, предлагаете нестандартный порт юзать - не люблю я эти извращения, потом попробуй разберись на какой тачке какой порт SSH и вешать на каждый узел по VPN-серверу


      1. salaev
        29.06.2022 08:48

        Есть же ~/.ssh/config


      1. edo1h
        30.06.2022 16:47

        согласен с вами обоими. ни нестандартный порт, ни порт нокинг для ssh не нужны )))


    1. Chupaka
      29.06.2022 22:00

      Вроде как Shodan утверждает обратное: на каком бы порту вы ни висели, вас найдут :)


  1. edo1h
    28.06.2022 16:51
    +2

    возникла необходимость скрывать порты (22, 443 и т.п.) от посторонних глаз

    необходимость? не верю.


    1. Winseven Автор
      28.06.2022 20:22
      -1

      А я и не заставляю


  1. MaxStirlits
    28.06.2022 18:10
    +2

    Не проще всего лишнее убрать в wg?


    1. ducemollari
      29.06.2022 23:22

      Именно. Всё в дроп, кроме идущего из wg. Как сломал, так в консоль в админке.


  1. kt97679
    28.06.2022 22:40

    Если у вас на той же машине работает веб сервер, то можно использовать sslh или решение на базе websockets: habr.com/ru/post/531590


  1. t1gger
    29.06.2022 10:00

    как вариант, можно поставить OTP генератор и раз в минуту менять требуемый размер пакета


  1. miga
    29.06.2022 16:22
    +2

    Security through obscurity во всей красе (это, впрочем, ко всему порт-нокингу относится)

    Надеюсь, у вас есть какой-нибудь бэкдор, чтобы внезапно не остаться без доступа к нужному серверу, случись вам оказаться в странном сетевом окружении с забавным MTU


  1. Chupaka
    29.06.2022 22:10

    Всегда интересовало, почему в подобных мануалах не пытаются использовать ICMP Payload. Там ведь можно практически полноценный (хоть и не защищённый от перехвата) пароль передать: ping -p 4869204869


  1. pprometey
    30.06.2022 05:29

    Кстати, и эти штуки боты научились обходить.


  1. YEN
    30.06.2022 09:29
    +1

    Как это упростить для простого обывателя с Андроид-телефоном до уровня "нажать туда, нажать сюда"?