Компактный защищённый мини-компьютер на тёмном антистатическом коврике, на его OLED-дисплее светится голубым моноширинным шрифтом статус туннеля tun0 UP и маршрут 10.10.0.0/16. Резкий боковой свет...


На внутреннем пентесте промышленного объекта единственным исходящим каналом из скомпрометированной DMZ оказался SSH на 22-м порту. OpenVPN блокировался DPI на уровне handshake, WireGuard по UDP не проходил - файрвол резал всё кроме TCP 22 и 443. Нужен был полноценный L3-доступ к сегменту 10.10.0.0/16 для запуска nmap, Responder и CrackMapExec. Не SOCKS-прокси для браузера, а маршрутизируемый IP-туннель на уровне ядра. Штатный ssh -w решил задачу за минуту - без заливки бинарников на целевой хост, через шифрованный канал поверх обычного SSH-соединения.

Зачем пентестеру L3 туннелирование через SSH​

Стандартные SSH-пробросы закрывают задачи конкретных портов или SOCKS-прокси. На первый взгляд хватает: пробросил 445-й через ssh -L, запустил smbclient - работает. Но стоит попытаться обойти блокировки SSH и получить доступ к целой подсети - всё разваливается.

-L и -R требуют заранее знать порт и хост назначения. Сканируешь внутреннюю подсеть с сотнями хостов - и это превращается в ручное перечисление десятков пробросов. Каждый новый сервис - новая строка ssh -L. Динамическое обнаружение сервисов? Забудь.

-D поднимает SOCKS5-прокси. Браузер и proxychains через него работают, но Responder, ARP-спуфинг, ICMP-сканирование - нет. DNS-запросы по умолчанию утекают мимо SOCKS, что ломает резолвинг внутренних имён и одновременно светит запросы атакующего на периметре.

ssh -w создаёт пару виртуальных tun-интерфейсов: один на клиенте, другой на сервере. Назначаешь им IP-адреса - получаешь point-to-point SSH туннель. Дальше ip route add 10.10.0.0/16 dev tun0 - весь трафик к внутренней подсети идёт через SSH. Приложениям плевать на прокси: они видят обычный виртуальный сетевой интерфейс Linux с маршрутизацией через SSH туннель на уровне ядра.

Для пентестера это значит: один SSH туннель как VPN замена без загрузки бинарников на скомпрометированный хост. Нужен только работающий sshd и root.

Предусловия для настройки SSH VPN​

Прежде чем поднимать OpenSSH tunnel device - чётко пойми границы применимости. Я видел, как люди тратили полчаса на отладку, не проверив элементарных вещей.

ПараметрТребование
OpenSSH>= 4.3 (поддержка -w)
Права на клиентеroot или CAP_NET_ADMIN
Права на сервереroot или доступ к /dev/net/tun
sshd_configPermitTunnel yes или point-to-point
Ядро LinuxМодуль tun загружен (modprobe tun)
IP forwardingnet.ipv4.ip_forward=1 на сервере
NATiptables MASQUERADE для подсети туннеля
Windows-клиентНЕ поддерживает ssh -w

Два момента, которые русскоязычные руководства обычно опускают.

Первый: на обеих сторонах нужен root. Если на скомпрометированном хосте только непривилегированный shell - ssh -w не заработает. По Arch Wiki, для non-root нужен capability CAP_NET_ADMIN и доступ к tun-устройству - на практике такое встречается крайне редко. В такой ситуации смотри в сторону Chisel или ligolo-ng - они работают в userspace без привилегий.

Второй: SSH -w tun/tun - это TCP поверх TCP. При потерях пакетов на канале начинается retransmission storm: внешний TCP пересылает потерянный сегмент, внутренний TCP тоже запускает свой таймер - задержки растут нелинейно. Для типичного пентестерского трафика (сканирование портов, relay-атаки, exfiltration) терпимо. Для потокового видео или масштабного туннелирования - нет.

Настройка tun интерфейса SSH на сервере​

Серверная сторона - хост, через который пойдёт трафик во внутреннюю сеть. На пентесте это jump host или скомпрометированный bastion с выходом в целевой сегмент.

Три шага: разрешить туннелирование в sshd, включить форвардинг, настроить NAT.

В /etc/ssh/sshd_config добавляешь [URL='https://man.openbsd.org/sshd_config#PermitTunnel']PermitTunnel[/URL] yes, перезапускаешь sshd: systemctl restart sshd. IP-forwarding: sysctl -w net.ipv4.ip_forward=1 (чтобы пережило ребут - пишешь net.ipv4.ip_forward=1 в /etc/sysctl.d/99-tunnel.conf). NAT: iptables -t nat -A POSTROUTING -s 10.99.99.0/30 -o eth0 -j MASQUERADE, где eth0 - интерфейс сервера, смотрящий во внутреннюю сеть.

Для ограничения конкретного пользователя в sshd_config - блок Match:
Код:
Match User pentester
  PermitTunnel point-to-point
  AllowTcpForwarding no
  X11Forwarding no
Значение point-to-point вместо yes явно запрещает TAP (layer 2) и оставляет только TUN (layer 3) - меньше поверхность атаки. Для дополнительного hardening можно комбинировать с PermitRootLogin forced-commands-only и указать tunnel="0" в authorized_keys - root по ключу только создаёт tun0, интерактивный shell не получает. (Подробнее - rot13.org, раздел SSH tunnel hardening.)

Создание SSH L3-туннеля и маршрутизация​

📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме

с клиента должен проходить. Проходит - маршрут работает, nmap, curl и smbclient увидят внутренние хосты напрямую. Не проходит - типичные причины: забыл ip link set tun0 up (самая частая ошибка, на неё тратят больше всего времени), не загружен модуль tun (modprobe tun), или PermitTunnel не включён в sshd_config.

Split tunnel и full tunnel​

Для пентеста обычно хватает split tunnel - только целевые подсети через SSH, остальное напрямую. Добавляешь маршруты к нужным сегментам: ip route add 10.10.0.0/16 via 10.99.99.1 dev tun0. Несколько целевых подсетей - прописываешь маршрут для каждой.

Full tunnel (весь трафик через SSH) нужен реже, но требует замены default route. Критически важно сохранить прямой маршрут к самому SSH-серверу через оригинальный gateway - иначе туннель закольцуется и соединение оборвётся. Последовательность: сохраняешь текущий gateway ORIG_GW=$(ip route show default | awk '{print $3}'), прописываешь маршрут к серверу через оригинальный gateway ip route add 203.0.113.10/32 via $ORIG_GW, затем меняешь default ip route replace default via 10.99.99.1 dev tun0. При отключении туннеля маршруты нужно откатить - иначе хост потеряет сеть.

DNS-утечки и проблема MTU при SSH туннелировании​

Два бага, которые стабильно ломают SSH VPN на практике. Ни один из русскоязычных гайдов не раскрывает их нормально.

DNS-утечки​

Маршруты прописаны, IP-трафик идёт через туннель. А DNS-запросы по-прежнему уходят через локальный резолвер мимо шифрованного канала. Приложение резолвит internal.corp.local через публичный DNS, получает NXDOMAIN. Хуже того: DNS-запросы видны на периметре и выдают, какие внутренние имена разведывает атакующий. Классическая утечка, на которую наступают даже опытные ребята.

Решение: на время работы туннеля переписать /etc/resolv.conf на внутренний DNS (nameserver 10.10.0.1). Если используется systemd-resolved: resolvectl dns tun0 10.10.0.1 && resolvectl domain tun0 ~corp.local - тогда только запросы к зоне corp.local пойдут через туннельный DNS, остальные останутся на системном. На одноразовом пентесте проще тупо отредактировать resolv.conf и вернуть при отключении.

MTU tun-интерфейса​

Стандартный MTU Ethernet - 1500 байт. SSH-туннель добавляет overhead (заголовки SSH + внешний TCP), и пакеты размером 1500 начинают фрагментироваться. Если PMTUD (Path MTU Discovery) работает - проблема разруливается автоматически. Но на хостах, где заблокирован ICMP type 3 code 4 (Fragmentation Needed), фрагментация молча убивает соединения: TCP-сессии зависают, крупные файлы не передаются. Выглядит как «туннель работает, но ничего не качается» - и ты сидишь, пялишься в tcpdump, пока не вспомнишь про MTU.

На tun-интерфейсах ставь ip link set tun0 mtu 1400. Это значение стабильно работает на типичных каналах. Если SSH-сессия проходит через дополнительные обёртки (HTTP CONNECT прокси, ещё один уровень SSH) - снижай до 1280.

Автоматизация SSH корпоративного туннеля​

На одноразовом пентесте автоматизация не нужна - поднял, отработал, закрыл. Но когда SSH VPN используется как аварийный канал к инфраструктуре (или как persistent access на длительном engagement), туннель должен переживать разрывы.

Два подхода. autossh - обёртка, которая мониторит SSH и перезапускает при разрыве: autossh -M 0 -f -N -w 0:0 -o "ServerAliveInterval=30" -o "ServerAliveCountMax=3" root@server. Параметр -M 0 отключает собственный мониторинг autossh в пользу встроенного ServerAliveInterval - надёжнее и не требует дополнительного порта.

systemd unit - если autossh нет под рукой. Сервис с Restart=always и RestartSec=15s, который запускает ssh -N server. При обрыве systemd перезапускает процесс автоматически. Для создания tun-интерфейса - отдельный oneshot-сервис с зависимостью After=, который назначает IP и поднимает интерфейс до запуска SSH.

В обоих случаях ServerAliveInterval 30 и ServerAliveCountMax 5 обязательны. Без них SSH не обнаружит мёртвое соединение и процесс повиснет с открытым, но нерабочим туннелем. Проверено на собственном опыте - повисший туннель без keepalive может молча лежать часами.

SSH -w против sshuttle, Chisel и ligolo-ng​

SSH как замена VPN - не единственный способ обхода firewall через SSH. Вот как ssh -w соотносится с альтернативами:

Параметрssh -wsshuttleChiselligolo-ng
Уровень туннеляL3 (tun)L3 (эмуляция через tproxy)SOCKS/TCPL3 (tun)
Root на клиентеДаДаНетНет
Root на сервереДаНет (нужен Python)НетНет
Бинарь на сервереНет (штатный sshd)Нет (Python)Да (chisel binary)Да (agent binary)
TCP-over-TCPДа (проблема)НетДа (проблема)Нет
UDP-трафикЧерез tunЧастично (DNS)НетДа
Скорость развёртыванияСредняяВысокаяВысокаяСредняя

sshuttle - самый прагматичный вариант, когда на сервере есть Python. Не создаёт настоящий tun, а перехватывает трафик через iptables/pf на клиенте и проксирует через SSH. Не требует root на сервере и не страдает от TCP-over-TCP. Одна команда: sshuttle -r user@server 10.10.0.0/16. На практике - первое, что пробую, если Python на месте.

Chisel работает полностью в userspace - root не нужен ни на одной стороне. На скомпрометированном хосте привилегии есть не всегда, и тут Chisel выигрывает. Но для полноценного L3-доступа потребуется дополнительная обвязка через SOCKS.

ligolo-ng - создаёт полноценный tun на стороне атакующего через легковесный agent. Из всех вариантов ближе всего к настоящему VPN без TCP-over-TCP. Если можно залить agent - это мой выбор.

ssh -w оправдан в конкретном сценарии: на целевой машине нельзя запускать сторонние бинарники (EDR с поведенческим анализом, read-only FS, жёсткий AppLocker), а sshd уже работает. Нет зависимостей, нет загрузки файлов - только штатный sshd и root.

Обнаружение SSH L3-туннеля​

С точки зрения blue team обход ограничений через SSH tunneling создаёт характерные артефакты. Не питай иллюзий - это не stealth-техника.

Сетевые индикаторы. Длительное SSH-соединение с аномальным объёмом трафика в обе стороны. Соотношение входящего и исходящего, близкое к 1:1, нетипично для обычного администрирования (там преобладает одно направление). DPI-системы профилируют SSH-сессии по этому паттерну.

Хостовые индикаторы. ip link show type tun покажет все tun-устройства на хосте. Новые маршруты через tun0 видны в ip route. Процесс ssh с флагом -w виден в ps aux. Открытый файловый дескриптор /dev/net/tun у процесса ssh - ещё один артефакт.

Логи sshd. При LogLevel VERBOSE или выше sshd пишет создание tunnel device. Строка Tunnel forwarding в auth.log - прямой индикатор. Сама директива PermitTunnel в конфиге - артефакт настройки, который ищется при forensics.

Если SOC мониторит SSH-логи или на хосте работает EDR - туннель будет обнаружен. В средах с активным мониторингом стоит рассматривать альтернативные транспорты или обфускацию SSH-протокола.



Больше половины проблем с SSH-туннелями на пентестах - не DPI и не файрволы, а забытые мелочи: маршрут к серверу через оригинальный gateway, DNS-утечка мимо шифрованного канала, MTU 1500 на tun-интерфейсе. Я видел, как пентестеры с пятилетним стажем тратили час на отладку «нерабочего туннеля», потому что забыли ip link set tun0 up после назначения адреса. ssh -w - инструмент с нулевыми внешними зависимостями и высокими требованиями к пониманию сетевого стека.

Что до выбора транспорта: ssh -w побеждает ровно в одном сценарии - на целевой машине нельзя разместить ничего, кроме штатного sshd. Root есть - туннель поднимается за 30 секунд без загрузки бинарников. Во всех остальных случаях sshuttle проще, а ligolo-ng мощнее и не страдает от TCP-over-TCP.

И последнее. Сообщество переоценивает «невидимость» SSH-туннелей. Длительная сессия с аномальным трафиком - один из первых триггеров в нормально настроенном SIEM. Рассчитывать на то, что «SSH везде разрешён, значит не заметят» - рассуждение, которое не переживает первую встречу с blue team, у которой настроены пороги по объёму и длительности SSH-сессий. Если хочешь отработать эту связку в безопасной среде - на HackerLab есть лабы с сегментированными сетями, где можно погонять и ssh -w, и ligolo-ng, и sshuttle без риска спалиться перед настоящим SOC.
 
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

Похожие темы

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🧭 Навигатор · ИБ 2026
Не знаешь, какой трек твой?
5 направлений ИБ, реальные зарплаты и точка входа для каждого — в одном треде.
JuniorSenior+
100K → 600K+ ₽ /мес
Открыть навигатор →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →