Имею парочку 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)
OOOTehnologiiBezopasnosti
28.06.2022 11:35+3Интересный скрипт =))) Добавлю, что сам метод стар как мир и имеет название не ICMP открывашка, а "Port Knocking" =))) По крайней мере, у микротофилов довольно распространенная штука)) Можно, кстати, делать списки и организовывать многоуровневый порт кнокинг, чтобы первый удачный пакет лишь помещал IP адрес в белый список, который позволяет пробить пинг с новым размером пакета, и так далее. Плюс списки можно разделять, чтобы открывать доступ к определенным сервисам. А тех, кто не угадал (и вообще, перебирать начинает) - в бан на несколько месяцев. Но стандартный пинг со стандартным размером пакета надо разрешить, ибо без него может быть проблема с маршрутизацией - некоторые маршрутизаторы в интернете пингуют клиентский внешний адрес, который к нему уже обращался - что-то вроде keepalive для соединения.
Akina
28.06.2022 13:16+2Но стандартный пинг со стандартным размером пакета надо разрешить
А пинг вообще трогать не надо. Он должен фунциклировать так, словно кнокера вообще нет. То есть контроль размера входящего пакета и добавление разрешающего правила идут не вместо стандартной реакции на ICMP, а в дополнение/параллель к нему.
Заодно хрен определить, что пинг с пакетом в сколько-то байтов выполнил какую-то специфическую функцию. Особенно если между его приходом и открытием доступа положить полуслучайный тайм-аут.
A ещё можно надставить скрипт так, чтобы между пингом и открытием доступа был тайм-аут секунд в 5-10, а при поступлении в течение этого времени неправильного пинга запланированное открытие доступа для адреса-источника отменялось. Тогда брутфорс-скрипты совсем бессмысленны.
Winseven Автор
28.06.2022 20:26+1К слову, я ICMP вообще не трогаю, только нюхаю ICMP пакет с определенным размером
tcpdump -n -c1 -i any icmp[icmptype] == icmp-echo and ip[2:2] == $PACKAGE_SIZE
Maksmsk
28.06.2022 11:41+1Не проблема написать скрипт который откроет ваши порты меньше чем за минуту. Учитывая что стандартный mtu 1500, то надо всего 1500 пакетов отправить с размером от 0 до 1500 это меньше минуты. Такая себе защита. Вы на сервер вешаете публичный адрес или все icmp пакеты приходящие на маршрутизатор/firewall натите в сторону сервера?
Winseven Автор
28.06.2022 11:57Вы знаете IP-адреса моих серверов, чтобы открыть их? Скрыть порты, а не защитить! Не вижу принципиальной разницы: натить трафик или вешать публичный IP на хост, например: мой домашний провайдер натит (1:1) трафик со своего внешнего адрес на мой серый IP.
dabystru
28.06.2022 18:38По идее, защита от этого — открывать порты не по одному пингу, а после прихода 256 пакетов, каждый из которых имеет заданный размер из 0...1500.
iig
28.06.2022 20:16+2Каждый входящий ICMP пакет запускает несколько экземпляров bash, внутри которых крутятся sed и awk. Для хорошей, качественной DOS атаки не нужен ботнет, достаточно обычного ping. Так себе защита.
net_racoon
28.06.2022 16:20Никогда не понимал нокинг, костыли какие-то ИМХО. Повешайте сервис на неродной порт и не будут боты его там искать. Все равно, если будут целенаправленно долбить ваш хост, нокинг вас не спасет (а может даже и хуже сделает).
А если хочется прям безопасную безопасность, то делайте полноценный VPN во внутреннюю сеть.
Winseven Автор
28.06.2022 20:20+1Я ж не утверждаю, что это прям продакшн решение, просто поделился идеей.
Вот есть у меня VDSка за 300 руб/мес и цепляюсь я к ней раз в неделю, предлагаете нестандартный порт юзать - не люблю я эти извращения, потом попробуй разберись на какой тачке какой порт SSH и вешать на каждый узел по VPN-серверуedo1h
30.06.2022 16:47согласен с вами обоими. ни нестандартный порт, ни порт нокинг для ssh не нужны )))
Chupaka
29.06.2022 22:00Вроде как Shodan утверждает обратное: на каком бы порту вы ни висели, вас найдут :)
MaxStirlits
28.06.2022 18:10+2Не проще всего лишнее убрать в wg?
ducemollari
29.06.2022 23:22Именно. Всё в дроп, кроме идущего из wg. Как сломал, так в консоль в админке.
kt97679
28.06.2022 22:40Если у вас на той же машине работает веб сервер, то можно использовать sslh или решение на базе websockets: habr.com/ru/post/531590
t1gger
29.06.2022 10:00как вариант, можно поставить OTP генератор и раз в минуту менять требуемый размер пакета
miga
29.06.2022 16:22+2Security through obscurity во всей красе (это, впрочем, ко всему порт-нокингу относится)
Надеюсь, у вас есть какой-нибудь бэкдор, чтобы внезапно не остаться без доступа к нужному серверу, случись вам оказаться в странном сетевом окружении с забавным MTU
Chupaka
29.06.2022 22:10Всегда интересовало, почему в подобных мануалах не пытаются использовать ICMP Payload. Там ведь можно практически полноценный (хоть и не защищённый от перехвата) пароль передать: ping -p 4869204869
YEN
30.06.2022 09:29+1Как это упростить для простого обывателя с Андроид-телефоном до уровня "нажать туда, нажать сюда"?
dmitryrf
Идея, действительно, не новая. Если интересно посмотреть на существующее решение, то вот: linux.die.net/man/1/knockd