Pull to refresh

Еще один Port knocking

Reading time9 min
Views8.9K

Приветствую коллеги! В данной статье  хочется описать свою доработку популярной технологии защиты сетевых рубежей под названием Port Knocking, реализованную на оборудовании MIKROTIK. Технология древняя, неоднократно описана и разжевана. Используется повсеместно и довольно эффективна. По этому в статье я не стану подробно объяснять что это и для чего нужно. Предполагается что читатель в теме. Если не в теме но есть интерес, рекомендую вначале познакомиться с технологией в других публикациях, коих огромное количество.


Для чего статья:

Hidden text

Надеюсь что данная реализация кому то да будет полезной, поскольку у меня она зарекомендовала себя хорошо, а у других я чего то подобного не встречал. Идея родилась в самый разгар Корона дампа, когда на “удаленку” отправили 90% (а где то и больше) персонала. Конечно технологией удаленного доступа к локальным ресурсам компании у нас пользовались и до этого. И Port Knocking, был настроен давно. И работал по классике. Была настроена цепочка правил где микротик принимал пакеты определенного размера по протоколу ICMP (обычный пинг), если пакеты идут в правильной последовательности и правильного размера, для IP источника открывается удаленный доступ к нужному ресурсу. Для этого у меня (для WINDOWS клиентов) было написано несколько БАТ файлов. От самых простых с зацикленным пингом нужного ресурса, до довольно изощренных. В основном для VPN клиентов. Где единожды закинув клиенту на флэшку некий дистрибутив из БАТ файлов и кое каких других, процесс установки VPN соединения полностью автоматизировался. Даже если клиент до этого не пользовался ВПНом совсем. Помимо этого в зависимости от задачи через некое подобие меню можно подключать сетевые диски, устанавливать RDP соединения и выполнять ряд других действий. Этот способ является основным и по сей день. Но только для WINDOWS клиентов, поскольку в других OS я к сожалению не силен. По окончании работы батника все следы его работы и установленных соединений удаляются почти бесследно. Способ для меня крайне хорош и удобен. Но постепенно стало выяснятся что не все сидят под ВИНДОЙ (самая частая альтернатива, оказалась MAC OS) . А у кого-то и флэшку вставить некуда, или политиками разрешен запуск только определенного софта. Короче нюансов хватало.

            В результате этой вакханалии родилась идея некого кроссплатформенного решения. Простого и не замысловатого. Работающего как с флэшки так и с общедоступного ресурса. И первое что пришло на ум, старый добрый HTML + JavaScript . JS был выбран как раз по тому что способен исполнятся на стороне клиента (наверно любого). Оговорюсь, скрипт писал фрилансер, по-моему ТЗ. Возможно, что код не идеален. Поэтому прошу воздержаться от критики. Если вы специалист и можете помочь допилить код, буду рад. Если, по-вашему, прям чего-то очень не хватает, то рынок фриланса к вашим услугам. Я вам вряд ли помогу.

     Что нам нужно?

  1. На МИКРОТИКЕ настраиваем
    Port Knocking. Но не классический ICMP (пинг с заданным размером пакета), а по
    обращению к портам в определенной последовательности по протоколу TCP. В примере мы
    задействуем 4 порта. Порты лучше брать выше 1024 и до 65535 (следите что бы
    порты были свободны на вашем микротике и не использавались где либо еще.
    (Подробнее о портах). В случае если порядок соблюден верно, для адреса источника становятся доступен пинг хоста назначения (у себя вы можете изменить конечный результат по своему усмотрению). В качестве примера, ниже я размещу ссылку на работающую форму и укажу адрес хоста и код для проверки работоспособности. Подтверждением работы будет возможность пинга удаленного хоста который до выполнения скрипта будет невозможным. Может быть, когда вы будете читать статью форма уже будет недоступна. Но исходники кода я приложу, так что возможность проверки сохранится в любом случае, но уже на ваших ресурсах.

  2. Код правил “firewall filter” для микротика:

Hidden text
/ip firewall filter
add action=accept chain=input comment=ICMP in-interface=[имя внешнего интерфейса. Обычно ether1] protocol=icmp src-address-list=ICMP
add action=jump chain=input connection-state=new dst-address=[ip адрес внешнего интерфейса (по желанию)] dst-port=1025,1088,1051,1549 in-interface=[имя внешнего интерфейса. Обычно ether1] jump-target=Kn-Tcp protocol=tcp
add action=add-src-to-address-list address-list=Gate001 address-list-timeout=2s chain=Kn-Tcp connection-state=new dst-port=1025 protocol=tcp
add action=add-src-to-address-list address-list=Gate002 address-list-timeout=2s chain=Kn-Tcp connection-state=new dst-port=1088 protocol=tcp src-address-list=Gate001
add action=add-src-to-address-list address-list=Gate003 address-list-timeout= 2s chain=Kn-Tcp connection-state=new dst-port=1051 protocol=tcp src-address-list=Gate002
add action=add-src-to-address-list address-list=ICMP address-list-timeout=1h chain=Kn-Tcp connection-state=new dst-port=1549 protocol=tcp src-address-list=Gate003
add action=return chain=Kn-Tcp connection-state=new
/

В данном примере:

Hidden text

мы использовали порты: 1025, 1088, 1051 и 1549. При поступлении пакета на каждый из этих портов он попадет в созданную цепочку “Kn-Tcp” (в принципе конструкцию с правилом “Jump” и “Return” можно не использовать, работать будет и без нее. В примере эти правила как дань классике. Если откажитесь от  них то в других правилах желательно указать IN interface и выбрать цепочку input) при верной последовательности поступления пакетов, создаются временные адрес листы, последний из которых сроком на 1 час. И именем ICMP. Из этого адрес листа получает IP адреса источников правило с комментарием “ICMP”. Если IP есть в листе, то для него разрешается пинг. Перед вставкой кода в консоль микротика поправьте его под ваше оборудование. В частности укажите IP и имя внешнего интерфейса. Править код рекомендую в нормальном редакторе с подсветкой синтаксиса, к примеру notepad++, который наконец начал понимать синтаксис ROS. Если будете использовать штатный notepad windows возможны ошибки при COPY PASTE.

3. Размещаем где либо HTML страницу с JavaScript. Это может быть как общедоступный WEB сервер. Так и локальная директория на компьютере или usb флэшке.

На январь 2023 года демо страница общедоступна по адресу https://kn.invstdev.ru (напомню, что не гарантирую ее вечную работу по этому адресу. Ищите исходники в конце статьи)

4. Открываем страницу, прописываем хост и код (перечень портов разделенных “-”), жмем капу “отправить”. Если не поставили галку “повторять”, получаем доступ к ресурсу на 1 час с соответствующем уведомлением (страницу с уведомлением можно переопределить в файле “index.html” на любую другую). Если галку “повторять” поставили, скрипт будет регулярно повторяться в течении 8 часов.

Работу скрипта вы можете проверить на демо ресурсе:

Hidden text

(вечную работу которого так же не гарантирую), Хост: kn.papiruss.ru Код: 1025-1088-1051-1549

После нажатия кнопки отправить вы сможете пропинговать вышеуказанный хост. Пинг которого до этого был невозможен.

            Для особо параноидально настроенных товарищей, во избежании попыток  подбора нужной комбинации портов, могу предложить до оснастить конструкцию "сладким горшочком" (Honeypot). Гугл вам в помощь. Но я не думаю что это хоть сколько то целесообразно в вашем случае. Смаил


Коды HTML и JavaScript.

Создаем 2 пустых файла с именами index.html и theend.html, а так же поддиректорию js с файлом main.js

Файл main.js (нужно положить в
поддиректорию js):

Hidden text
'use strict';

const TIMEOUT = 1000 * 60 * 60 * 8;

function main() {
    const submitButton = document.getElementById('submit');
    const hostElement = document.getElementById('host');
    const codeElement = document.getElementById('code');
    const needIterateElement = document.getElementById('needIterate');

    submitButton.addEventListener('click', (e) => {
        e.preventDefault();
        const host = hostElement.value;
        const code = codeElement.value;
        const ports = code.split("-");

        let value = needIterateElement.checked;
        if (value) {
            makeRequests(host, ports);
            let timerId = setInterval(() => {
                makeRequests(host, ports);
            }, 10 * 60 * 1000);

            setTimeout(() => {
                clearInterval(timerId);
                window.location.replace("./theend.html");
            }, TIMEOUT);
        } else {
            makeRequests(host, ports);
            setTimeout(() => {
                window.location.replace("./theend.html");
            }, 1000);
        }
    });
}
function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}
function makeRequests(host, ports) {
    console.log(ports);
    for (let i = 0; i < ports.length; i++) {
        try {
            console.log(ports[i]);
            const newLocal = 'https://' + host + ":" + ports[i];
            console.log(newLocal);
            // fetch(newLocal).then().catch();
            var oReq = new XMLHttpRequest();

            oReq.onload = function (e) {
                var arraybuffer = oReq.response; // not responseText
                console.log(arraybuffer);
            }
            oReq.open("GET", newLocal);
            oReq.responseType = "arraybuffer";
            oReq.send();
        } catch (e) {
            console.log(e);
        }
    }
}

main();

Cтраница index.html:

Hidden text
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>KNOCK</title>
</head>
<body>
<form id="form" autocomplete="off">
    <label for="host">Хост<input autocomplete="off" id="host" type="text" required></label>
    <label for="code">Код<input autocomplete="off" id="code" type="password" required></label>
    <label for="needIterate">Повторять<input autocomplete="off" type="checkbox" id="needIterate" name="needIterate"></label>
    <button id="submit">Отправить</button>
</form>
<script src="js/main.js"></script>
</body>
</html>

Страница theend.html:

Hidden text
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>KNOCK THE END</title>
</head>
<body>
Работа скрипта окончена. Ваш сеанс будет активен еще 1 час.
</body>
</html>

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

            Всего двумя правилами в таблице FIREWALL/NAT вы можете перенаправлять запросы к любому общедоступному ресурсу от имени вашего микротика. Если еще не понимаете зачем это надо, вот пример. На VPS хостинге вы подняли свою виртуалку, скажем с RDP. И уже где то минуту спустя вы сталкиваетесь с тем что вас начинают нещадно брутить. И конечно есть уйма способов как защитить себя и свою VPS от этой напасти. Но я предложу один. У вас есть микротик на котором мы только что настроили Port Knocking. Пишем в NAT два правила (редиректа и маскарада), а в FIREWALL своей VPS разрешаем подключения к RDP только с определенных IP, среди которых указываем IP своего микртотика.  Клиенты прошедшие авторизацию через Port Knocking, в качестве адреса назначения указывают адрес и порт вашего микротика, а тот перенаправляет подключения на ваш RDP. Вуаля, без особых хлопот вы получили Port Knocking на своем RDP сервере периметр которого не защищен микротиком явно. Они вообще могут быть на разных континентах.

КОД для firewall NAT:

Hidden text
/ip firewall nat
add action=dst-nat chain=dstnat comment=REDIREKT_KUDA_TO dst-port=3389 protocol=tcp src-address-list=ICMP to-addresses=1.1.1.1
add action=masquerade chain=srcnat dst-address=1.1.1.1 dst-port=3389 protocol=tcp
/

В этом примере:

Hidden text

микротик принимает TCP пакеты на порт 3389 от источников из адрес листа ICMP и пересылат их хосту 1.1.1.1 на тот же порт, с маскарадом (таким образом хост получателя видит не ваш реальный IP, а IP микротика). Если правило маскарада исключить то пакеты так же будут пересылаться, но ответа вы не получите. Поскольку хост назначения будет отвечать вам на прямую, иначе говоря ответы на ваши запросы будут приходить от хоста 1.1.1.1, в то время когда ваша система будет ожидать их от хоста микротика. При желании это можно решить правкой маршрутов на сервере "получателе" или его аппаратном шлюзе (если таковой есть). Но проще просто использовать второе правило. При чем с точки зрения лучшей производительности и при наличии статического IP адреса на микротике посреднике, макард в экшене лучше заменить на “src-nat” с указанием ip адреса внешнего интерфейса микротика. Это правило всегда более предпочтительно вместо маскарада, но при условии что на микротике имеется белая статика. Еще одним бонусом такого способа подключения будет то что ваши клиенты (если они не через чур любознательны и технически грамотны) не будут знать реального адреса сервера RDP. Получается как в офисной АТС, связь через доп номера. На RDP сервере фаервол вообще можно не трогать. Полностью оградив его от атак из внешней среды (атаки из локальной подсети при этом остаются возможными), тем что бы не указывать в настройках сети, адрес шлюза. Добавив в таблицу маршрутизации один только статистический маршрут к вашему микротику. При этом на сервере перестанет работать интернет, но и это можно решить различными способами, о которых я возможно расскажу в других статьях.

На этом считаю что статью можно закончить. Глубоко признателен всем кто дочитал до конца!

            С уважением, всем  Хабра )))

Tags:
Hubs:
Total votes 7: ↑6 and ↓1+5
Comments14

Articles