На внутреннем пентесте промышленного объекта единственным исходящим каналом из скомпрометированной 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_config | PermitTunnel yes или point-to-point |
| Ядро Linux | Модуль tun загружен (modprobe tun) |
| IP forwarding | net.ipv4.ip_forward=1 на сервере |
| NAT | iptables 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 -w | sshuttle | Chisel | ligolo-ng |
|---|---|---|---|---|
| Уровень туннеля | L3 (tun) | L3 (эмуляция через tproxy) | SOCKS/TCP | L3 (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.