О, вам нравится SSH? А перечислите-ка все флаги!

Приветствую

Все мы видели эти красивые схемы, демонстрирующие, как в SSH устроен проброс портов. Но, если мы с вами мыслим хотя бы немного схоже, то эти схемы оставляют у вас больше вопросов, чем дают ответов. Если вы за «красных» в области компьютерной безопасности, то, чтобы обрести в сети суперсилу и в дальнейшем бесчинствовать, вы должны понимать сеть лучше, чем те, кто её проектировал. Один из инструментов, наделяющих вас такой суперсилой — SSH. Но иногда нам мешают добиться поставленных целей сам синтаксис инструмента и другие концепции, на основе которых этот инструмент работает. Чтобы вы могли бесчинствовать эффективнее, не срывая сроков, я собрал для вас длинный список присущих SSH штук, которые я нахожу полезными. Хорошо, если вы его тоже почитаете, но составлял я его в основном для себя. Заметил, что сам я качественно усваиваю те или иные концепции, только если, изучая информацию, повторяю упражнения на клавиатуре. В этом посте я, в сущности, рассказываю, чему научился таким образом. Должен отметить, что во всех этих примерах я демонстрирую проброс портов при помощи веб-сервера, но таких же результатов можно добиться и при помощи почти любого сервиса, в частности, RDP, SQL, т.д.

Проброс локального порта (-L)

Как понятно из названия, при пробросе локального порта можно создать локальный порт, информация с которого переадресовывается на удалённый порт. Предположим, что на сервере internal-web.int располагается веб-страница, доступная только через петлевой интерфейс. Таким образом, чтобы обратиться к этой веб-странице, нужно обратиться к internal-web.int. Обойти это препятствие можно, например, воспользовавшись пробросом локального порта по SSH. Допустим, мы обращаемся по SSH к странице internal-web.int, расположенной на хост-машине campfire.int. В таком случае мы пробрасываем локальный порт, что в дальнейшем позволит нам обратиться к удалённому веб-серверу через локальный порт.

Вот как выглядит команда для этого: ssh -N -f -L 1337:127.0.0.1:80 root@internal-web.int. Эта команда выполняется на campfire.int. Это сложная команда, поэтому давайте, как обычно, разберём каждый её флаг отдельно, чтобы сориентироваться, что именно здесь происходит.

  • ‑N: так мы сообщаем SSH, что за пределы сервера мы никакие команды отправлять не будем. Без этого мы бы получили оболочку на root@internal-web.int.

  • ‑f: так мы переводим работу SSH в фоновый режим. Если бы мы так не сделали, то окно терминала зависло бы, и пользоваться терминалом мы бы не смогли.

  • ‑L так мы приказываем SSH пробросить локальный порт.

  • 1337:127.0.0.1:80: так мы приказываем SSH связать локальный порт 1337 с удалённым портом 80 (тем самым, на котором включён наш веб‑сервер).

Примечание: насколько мне известно, лучше всего это делать именно с помощью флага -L, означающего локальность слева от адреса или -R, означающего локальность порта справа от адреса.

  • root@internal-web.int так мы сообщаем SSH, что хотим зайти под именем и паролем на удалённый сервер с правами администратора, что позволит нам открыть SSH‑туннель. Напомню, что порт 1337 на нашей локальной машине будет связан с портом 80 на удалённом сервере.

Теперь, обустроив проброс локального порта, можно взаимодействовать с портом  80 на internal-web.int, отправляя запросы на порт 1337 нашей локальной машины ( campfire.int).

Взаимодействие с портом 80 на internal-web.int.
Взаимодействие с портом 80 на internal-web.int.

Проброс удалённого порта (-R)

Проброс удалённого порта — это операция, противоположная пробросу локального. Допустим, у нас есть доступ к internal-web.int, и на нём располагается веб-страница, доступная только через петлевой интерфейс. Также предположим, что campfire.int не может напрямую обращаться к internal-web.int. В таком сценарии хотелось бы обратиться к internal-web.int от campfire.int. Проблема в том, что из-за брандмауэра невозможно наладить прямой обмен данными между campfire.int и internal-web.int. Пытаясь с этим справиться, обнаруживаем, что vuln-server.int доступен как с campfire.int, так и с internal-web.int. В данном случае нужно воспользоваться пробросом удалённого порта через SSH, чтобы переадресовывать данные порта 80 с internal-web.int на произвольный порт vuln-server.int. Организовав проброс удалённого порта, мы сможем открывать внутреннюю веб-страницу с internal-web.int, работающую на порте 80. Для этого направим команду curl на vuln-server.int.

Вот команда для этой цели: ssh -N -f -R 3000:127.0.0.1:80 root@internal-web.int.

  • N: так мы сообщаем SSH, что за пределы сервера мы никакие команды отправлять не будем. Без этого мы бы получили оболочку на root@internal-web.int.

  • f: так мы переводим работу SSH в фоновый режим. Если бы мы так не сделали, то окно терминала зависло бы, и пользоваться терминалом мы бы не смогли.

  • R так мы приказываем SSH пробросить удалённый порт

  • 3000:127.0.0.1:80: В результате SSH должна связать удалённый порт 3000 с портом 80 локального компьютера.

Замечание
Что касается запоминания, я обнаружил, что удобнее всего понимать -L как «локальный слева от адреса». При пробросе удалённого порта с флагом -R локальный порт указывается справа от адреса.

  • root@internal-web.int: так мы сообщаем SSH, что хотим зайти под именем и паролем на удалённый сервер с правами администратора, что позволит нам открыть SSH-туннель. Напомню, что порт 3000 (на vuln-server.int) будет связан с портом 80 на данном сервере.

Теперь, обустроив проброс удалённого порта, можно взаимодействовать с портом 80 на internal‑web.int, отправляя запрос curl на vuln‑server.int:3000

Динамический проброс порта (-D)

Динамический проброс порта при помощи опции -D — интересный вариант опосредованной передачи трафика через прокси SOCKS. Допустим, что на internal-web.int  размещено веб-приложение, доступное только из внутрикорпоративной сети. Также предположим, что по SSH можно получить доступ к vuln-server.int, он находится в той же самой внутренней сети, и так нам открывается путь к internal-web.int. Вот чего хотелось бы добиться в данном случае: достучаться до веб-сервера, работающего по адресу internal-web.int, пустив через прокси весь наш трафик от campfire.int через vuln-server.int. При этом задействуются обе цепочки прокси и браузер с нашей локальной машины. Сначала давайте убедимся в том, что конфигурация в файле /etc/proxychains.conf записана правильно.

  • Socks5: сообщает цепочкам прокси, что нужно использовать socks5 (а не socks4)

  • 127.0.0.1: приказывает цепочкам прокси использовать наш localhost

  • 8080: именно этот порт мы будем динамически пробрасывать. Указанный здесь порт должен совпадать с тем, который вы задали с опцией ‑D в рамках вашей команды SSH.

Вот команда для этой цели: ssh -N -f -D 8080 root@vuln-server.int.

  • ‑N: так мы сообщаем SSH, что за пределы сервера мы никакие команды отправлять не будем. Без этого мы бы получили оболочку на root@internal-web.int.

  • ‑f: так мы переводим работу SSH в фоновый режим. Если бы мы так не сделали, то окно терминала зависло бы, и пользоваться терминалом мы бы не смогли.

  • ‑D 8080 приказывает SSH создать на локальной машине динамический порт, через который мы будем посылать наш трафик.

  • root@vuln-server.int: так мы сообщаем SSH, что хотим зайти под именем и паролем на удалённый сервер с правами администратора, что позволит нам открыть SSH‑туннель. Через него мы сможем проксировать наш трафик.

Организовав динамический проброс порта через 8080 и установив socks5 127.0.0.1 8080 в /etc/proxychains.conf, можно выполнить proxychains curl 192.168.1.185 и увидеть нашу веб-страницу, размещённую по адресу 192.168.1.185. Кроме того, DNS по SOCKS кажется мне ненадёжным решением, почему я и указал конкретный IP-адрес в приведённой ниже команде curl.

Теперь сконфигурируем Firefox так, чтобы можно было просматривать наш внутренний сервер через прокси SOCKS. Для этого понадобится поставить правильные настройки прокси внутри самого firefox. Для этого откроем Firefox и перейдём в: Settings -> Privacy & Security -> Network Settings. Там выберите конфигурирование прокси «вручную» и отметьте “Proxy DNS when using SOCKS V5”. Наконец, сообщите Firefox о прокси SOCKS, который мы только что настроили. Для этого задайте хосту SOCKS адрес 127.0.0.1 и порт 8080 (или другой порт, тот, который вы указали в вашей команде SSH).

Теперь, когда прокси у нас настроен, можно обратиться к веб-странице на internal-web.int, поскольку весь наш трафик перенаправляется с локальной машины через vuln-server.int. Круто, правда?

Инсталляционные серверы или jump-серверы (-J)

По сравнению с предыдущими командами перепрыгивание с хоста на хост через SSH не составляет труда. В данном сценарии мы проксируем наш трафик через два хоста, намереваясь добраться до искомого хоста, куда не достучаться с нашего актуального хоста campfire.int. Цепочка переходов будет выглядеть так: campfire.int -> vuln-server.int -> internal-web.int -> dns.int.

Это делается при помощи команды ssh -J root@vuln-server.int, root@internal-web.int, root@dns.int. Обратите внимание: переходы разделяются запятыми.

Агентская переадресация (-А)

Агентская переадресация по SSH — интересная концепция, о которой подробно рассказано в статье Zero Effort Private Key Compromise: Abusing SSH-Agent for Lateral Movement. Если вы планируете заниматься агентской переадресацией, то настоятельно рекомендую её прочитать. Вкратце: SSH-агент позволяет добавлять приватные ключи/идентификаторы к агенту, работающему на вашей локальной машине. Это делается командой ssh-add <private_key_file>. Список этих ключей выводится командой ssh-add -l. Добавив ключ в утилиту ssh-agent, можно по SSH попасть на сервер, воспользовавшись одним лишь ключом, не вводя пароль повторно. Это удобно при работе как с пользовательскими, так и с сервисными аккаунтами. При помощи опции -A можно переадресовать ваш агент для работы с ключами на ту машину, к которой вы подключаетесь. Так вы сможете пользоваться вашими приватными ключами с той машины, к которой подключаетесь.

В качестве демонстрации давайте предположим, что хотим перепрыгнуть через vuln-server.int на internal-web.int, в то же время переадресуя ключи в нашем ssh-агенте, так, чтобы можно было ими пользоваться, оказавшись на internal-web.int.

Это делается при помощи следующей команды: ssh -A -J root@vuln-server.int  root@internal-web.int.

  • ‑A приказывает SSH переадресовывать ключи в нашем SSH‑агенте на удалённую машину internal‑web.int

  • ‑J root@vuln-server.int приказывает SSH проксировать наш трафик через vuln‑server.int перед тем, как обращаться к internal‑web.int

  • root@internal-web.int: так мы сообщаем SSH, что хотим зайти под именем и паролем на удалённый сервер с правами администратора, что позволит нам открыть SSH‑туннель.

Как видите, выполнив ssh -A -J root@vuln-server.int root@internal-web.int, можно пользоваться ssh root@dns.int, и при этом не требуется указывать ни приватный ключ, ни какие-либо учётные данные. Дело в том, что на нашей локальной машине campfire.int есть ssh-ключ для dns.int, загруженный в ssh-агент. Чтобы убедиться в этом, запустим ssh-agent -l.

Выделение TTY-команд (-t)

Этот вариант проще некуда, однако он очень пригодится, если требуется быстро выполнять на удалённом сервере такие команды, которые предполагают те или иные взаимодействия — например, Vim или top. Мой любимый пример такого рода — случай, когда требуется быстро отредактировать файл на удалённом сервере. Для этого нужно всего лишь выполнить команду ssh ssh root@internal-web.int -t top —  вы получите приветствие TTY, в котором будет команда top.

Глобальный порт (-g)

Такой вариант встречается реже, но с его помощью можно определить локально переадресуемый порт как «глобальный» (это моя терминология, а не официальная). Тогда мы сможем проксировать и направлять на порт внешнего сервера весь трафик, поступающий на указанный порт нашей локальной машины. Этот вариант напоминает опцию -L, упоминавшуюся выше, но далее мы получаем доступ через оболочку к vuln-server.int и попробуем проксировать любые соединения, идущие на порт 2222, перенаправляя их на порт 22 машины internal-web.int.

Команда для этого выглядит так: ssh -N -f -g -L 2222:localhost:22 root@internal-web.int.

  • ‑N: так мы сообщаем SSH, что за пределы сервера мы никакие команды отправлять не будем. Без этого мы бы получили оболочку на root@internal-web.int.

  • ‑f: так мы переводим работу SSH в фоновый режим. Если бы мы так не сделали, то окно терминала зависло бы, и пользоваться терминалом мы бы не смогли.

  • ‑g так мы приказываем SSH разрешить удалённым хостам подключаться к локальным переадресуемым портам

  • ‑L приказывает SSH переадресовать локальный порт

Как видите, пусть даже наша исходная команда SSH была адресована на порт 2222 машины vuln-server.int,  теперь оболочка сообщает нам, что на самом деле мы находимся на internal-web.int, поскольку ssh -N -f -g -L 2222:localhost:22 root@internal-web.int.

SSH-консоль (~?)

SSH-консоль — это «скрытая» фича SSH, предоставляющая вам некоторый контроль над  SSH, причём, для этого вам не придётся взаимодействовать с удалённой системой. Это полезно, если вы пытаетесь управлять SSH как таковым, но при этом ваша оболочка неисправна. Чтобы вывести меню справки по консоли, нажмите ~?. Если вы умеете работать с vim, то эта комбинация может напомнить вам ведущий символ. Итак, таким образом открывается справочная консоль. Две из имеющихся в ней опций я нахожу очень полезными. Во-первых, это ~, убивающая ваш сеанс (очень кстати, если вы что-то поломали).

Вторая опция — это  «Консоль», активируется командой ~C. В ней предусмотрено несколько вариантов переадресации. Если вы зашли на сервер по SSH и решили, что эту сессию будете использовать для проброса портов (например, динамического проброса с применением опции -D, которую мы обсуждали выше), то переадресовать эту сессию можете при помощи опции -D 8080. Тогда данные этой сессии будут пробрасываться на лету. Так, я подключился к vuln-server.int через обычный ssh. Нажал ~C, набрал -D 8080, после чего дважды нажал «ввод» — в ответ мне открылось обычное приглашение командной строки. Но, если мы используем прокси-цепочки на хост-машине campfire.int (и удостоверились, что файл /etc/proxychains.conf настроен для работы с портом 8080), то сессию SSH можно задействовать так, как будто она была инициирована при помощи ssh -D. Ловко.

Конфигурационный файл SSH

Файл с конфигурацией SSH находится по адресу ~/.ssh/config и помогает экономить время, когда вы устанавливаете соединения по SSH. Используемый в нём синтаксис очень легко прослеживается. Кроме того, конфигурацию SSH можно сохранять, чтобы не приходилось заново вводить в командную строку все нужные опции при каждой новой сессии. При установлении соединения SSH выполняет синтаксический разбор этого файла. Если конфигурация того сервера, к которому вы подключаетесь, определена в файле ~/.ssh/config, то именно она и будет использоваться. Обратите внимание: параметры, указанные в командной строке, имеют приоритет над прописанными в конфигурационном файле. Таким образом, если у вас в файле ~/.ssh/config написано, что пользователь internal-web.int должен обладать правами администратора (root), но вы выполняете команду SSH ssh graham@internal-web.int, SSH попытается запустить вас в систему как graham, а не как root. Ниже в качестве примера приведён элементарный файл ~/.ssh/config.

# Комментарии, начинающиеся с `#`, можно ставить только в начале строки. 
host internal-web.int
    User root
    IdentityFile /home/smores/ssh_agent/internal-web-no-pw
    Port 2222

Вот как SSH будет разбирать этот файл, выполняя команду ssh internal-web.int:

  • Пытается сопоставить internal‑web.int, фигурирующий в вызове командной строки, с ключевым словом host, сравнивая это значение с internal‑web.int, указанным в ~/.ssh/config

  • Если удаётся найти internal‑web.int в ~/.ssh/config, то будут задействованы все опции, прописанные под хостом и при этом не указанные при вызове в командной строке

  • Если совпадений не найдено, то SSH будет использовать лишь те опции, что вы сами указали в командной строке при вызове SSH.

Ключевые слова для конфигурирования SSH

Есть множество ключевых слов, которые можно задействовать в конфигурационном файле SSH. Но некоторые из тех, которыми я часто пользуюсь, не самоочевидны (например, Port и User) .

IdentityFile /path/to/private_key: позволяет указать приватный ключ, который вы собираетесь использовать на хосте. Семантически равноценно ‑i.

ForwardAgent: то же самое, что выполнить ssh ‑A (опять же, прежде, чем этим пользоваться, почитайте статью Zero Effort Private Key Compromise: Abusing SSH‑Agent for Lateral Movement.)

ProxyJump root@internal-web.int: указываем сервер, через который будет проксироваться трафик. Семантически равноценно опции ‑J, которую мы упоминали выше. Обратите внимание, что в следующем примере требуется аутентифицироваться с адресом root@vuln-server.int. Это показывает, что наш трафик действительно маршрутизируется через vuln‑server.int, прежде, чем мы получим оболочку на internal‑web.int, как и просили в команде SSH.

Match: это немного сложнее. Ключевое слово Match предназначено для определения условий в конфигурации SSH. В следующем примере SSH выполняет команду export | grep PROXYME=TRUE. Если программа возвращается с кодом состояния 0 (в данном случае это означает, что grep нашла совпадение), то будет использовать те ключевые слова SSH, которые были определены в блоке Match (здесь — ProxyJump). В противном случае будет использоваться лишь обычный блок host internal‑web.int.

В следующем примере мы сначала выполняем команду ssh internal-web.int, при помощи которой успешно подключаемся к серверу, воспользовавшись при этом приватным ключом, который обозначается ключевым словом IdentityFile. Поскольку export | grep PROXYME=TRUE возвращается с кодом состояния 1 (это означает, что grep не нашла совпадений), мы не выполняем ключевого слова ProxyJump, находящегося в инструкции match.

Далее устанавливаем переменную окружения PROXYME в значение TRUE, воспользовавшись export PROXYME=TRUE, и повторно выполняем всё ту же ssh internal-web.int. На этот раз нам поступает требование аутентифицироваться на time vuln-server.int перед тем, как мы получим оболочку на internal-web.int. Дело в том, что SSH вычислила блок Match и выполнила export | grep PROXYME=TRUE, в ответ на что получила код состояния 0 (это означает, что grep нашла совпадение). Поскольку команда вернула true, она выполнила тот вариант кода, в котором фигурирует ключевое слово ProxyJump, определённое в блоке Match.

Также следует указать, что scp (и некоторые другие утилиты на основе SSH) могут пользоваться вашим конфигурационным файлом SSH! Как правило, это делается по умолчанию, но мне известны случаи, когда это не так. Если вы работаете в такой системе, где scp не использует ваш конфигурационный файл ~/.ssh/config автоматически, то можно явно определить это при помощи аргумента -F ~/.ssh/config.

ssh-copy-id

Утилита ssh-copy-id — это небольшой инструмент, при помощи которого удобно быстро загружать публичный ключ на сервер.

Вот команда для этого: ssh-copy-id -i internal-web root@internal-web.int.

  • ‑i internal‑web: указываем имя того приватного ключа, при помощи которого мы хотим аутентифицироваться на сервере.

  • root@internal-web.int указываем тот сервер, на который мы хотим загрузить публичный ключ.

ssh-keygen

При помощи этой утилиты генерируются пары, в каждую из которых входят публичный и приватный ключ. Обычно рекомендуется задавать ключ побольше при помощи опции -b. Хотя я и не эксперт по криптографии, обычно чем длиннее — тем лучше, и по умолчанию размер ключа равен 3072 (как минимум, на моей машине). По умолчанию ssh-keygen использует алгоритм RSA, но можно использовать и другие (более предпочтительные и сильные) алгоритмы, указав флаг -t. IE ssh-keygen -t ecdsa -b 521 Кроме того, можно проверять ключи, просматривать их фингерпринт и размер в байтах при помощи команды ssh-keygen -lf <file-name>.

Заключение

Вот и всё, вы познакомились с моей личной шпаргалкой по работе с SSH. Теперь беритесь за дело и заходите по SSH на новые машины, уверенно понимая, что делаете.

Ссылки

https://github.com/cwolff411/redteamvillage-sshtunnels

https://www.ssh.com/academy/ssh/tunneling-example

https://goteleport.com/blog/ssh-tunneling-explained/

https://linuxize.com/post/how-to-setup-ssh-tunneling/

https://iximiuz.com/en/posts/ssh-tunnels/

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


  1. Lev3250
    19.05.2026 21:36

    Дошёл до "петлевого интерфейса" и полез в оригинал

    only accessible on the loopback interface

    Дальше можно не читать. Лучше потратить немного больше времени, но читать сразу оригинал


    1. Lev3250
      19.05.2026 21:36

      Дополнение к прошлому комменту. Специально в виде отдельного коммента. Ллмка, короткая была использована для перевода распознала рутового пользователя, записанного в виде рут@домен как email и защитила его!!

      Команда для этого выглядит так: ssh -N -f -g -L 2222:localhost:22 [email protected]

      В оригинале

      ssh -N -f -g -L 2222:localhost:22 root@internal-web.int

      Я сам использую ллм каждый день для переводов, но смысл выкладывать это без вообще минимальной вычитки, или хотя бы примерного понимания предметной области?


      1. AndrewBond
        19.05.2026 21:36

        Есть вариант, что выложено было тоже автоматически


      1. Shaman_RSHU
        19.05.2026 21:36

        Не так страшен LLM, как его пользователь, не проверяющий результаты выдачи :)


      1. Kobagugi
        19.05.2026 21:36

        Зато мы узнали, что рут это почтовый ящик)) Идеальный уровень погружения в системное администрирование


    1. MountainGoat
      19.05.2026 21:36

      Так писали во всех книгах по Линуксу, которые я сдуру читал в 2005-ом.


      1. nuclight
        19.05.2026 21:36

        В среде линукса всегда было плохо с документацией, и книги выходили такие же - компиляция наборов хаутушек, в которых запросто могли ходить кривые переводы. Говорю как участник команды перевода man-страниц FreeBSD на русский в 2006-м.


        1. MountainGoat
          19.05.2026 21:36

          И сейчас так же. Вчера впервые пытался упаковать программу в AppImage. Сам-то он собрался без проблем. Но вот манифест, необходимый, чтобы его приняли в стор, противоречит требованиям утилиты для упаковки. Единственный способ ублажить софт - это иметь название программы, которое везде показывается пользователю и в меню, не "Application Name" а "org.MountainGoat.Application_Name", что конечно зашквар. Так за день и не нашёл, что делать.

          Когда лень пройдёт, буду писать авторам программ, недавно принятых в стор, как они так извернулись. Может кто ответит. Вот и вся документация.


          1. Daiichi
            19.05.2026 21:36

            Вчера впервые пытался упаковать программу в AppImage.

            Чем? appimagetool'ом? Если этой тулзой, то рекомендуется брать rolling release.

            Единственный способ ублажить софт - это иметь название программы, которое везде показывается пользователю и в меню, не "Application Name" а "org.MountainGoat.Application_Name", что конечно зашквар.

            Это очень странная хохма, потому что в файле org.MountainGoat.Application_Name.desktop, в поле Name= можно написать вообще любое название, хоть SilverShamrock. Если интересно, скачайте, например, тот же KeePassXC в виде .AppImage, распакуйте, да посмотрите, как там внутри всё устроено.


        1. higin
          19.05.2026 21:36

          Подписываюсь под каждым Вашим словом. Говорю как читатель этой самой документации с 2005 года. FreeBSD определенно имеет более качественную документацию.


  1. ku4in
    19.05.2026 21:36

    Перевод, конечно, кривой. Но про консоль ssh (~C), что можно пробросить порт (-L, -R) или включить прокси (-D) прям в текущей сессии, не знал. Спасибо, реально полезная штука!


    1. kray74
      19.05.2026 21:36

      Хотите ещё полезных штук?

      man ssh


      1. Wizard_of_light
        19.05.2026 21:36

        Начало инструкции для чайников: "Итак, вы где-то услышали про SSH"

        Начало инструкция для юзеров: "Итак, вы приступаете к использованию SSH"

        Начало инструкции для хакеров: "Итак, вы сломали SSH"

        Начало инструкции для админов: "Итак, вам сломали SSH"


  1. gotch
    19.05.2026 21:36

    [email protected]

    Ну вы молодец. Прогнали через нейросеть статью и запостили не читая, и не понимая о чём она. Это уровень.


  1. Sivchenko_translate Автор
    19.05.2026 21:36

    Для понимания - нейросеть в этом блоге не используется никогда и никакая. Перевод делается вручную, впрочем, могу с вами согласиться, что какие-то вещи у нейросети получаются лучше. Вставки [email protected] остались при копировании абзацев из оригинала, но замечания справедливы - действительно, в некоторых примерах автор противопоставляет root@internal-web.int и root@vuln-server.int, поэтому без них не обойтись. Все эти адреса-примеры я вернул.


    1. trinxery
      19.05.2026 21:36

      Скрытый текст

      Ой, а откуда в переводе другой статьи с другого сайта взялся [email protected]? Не говоря о том, что вы не вычитываете машинный перевод.


  1. xaerowalk
    19.05.2026 21:36

    Если вы за «красных» в области компьютерной безопасности, то, чтобы обрести в сети суперсилу и в дальнейшем бесчинствовать, вы должны понимать сеть лучше, чем те, кто её проектировал

    После такого начала я ожидал чего-то большего, чем описание работы десятка ключей ssh...


  1. vaniacer
    19.05.2026 21:36

    Просто оставлю это здесь sshto - ssh с интерфейсом.


  1. diafour
    19.05.2026 21:36

    Чуть устарели сведения, в современных дистрибутивах по умолчанию ключи ed25519

    OpenSSH 9.5 (октябрь 2023):Это официальная дата смены значения по умолчанию в коде OpenSSH. В заметках к этому релизу указано, что ssh-keygen теперь по умолчанию генерирует ключи Ed25519


  1. Daiichi
    19.05.2026 21:36

    Поскольку export | grep PROXYME=TRUE

    Использование переменной среды для условного включения SSH Proxy, который ProxyJump — это не очень удобный и красивый способ. Вместо него желательно использовать суффиксы в имени хоста.

    Ниже приведён способ настройки в .ssh/config суффиксов имени хоста для обеспечения различных способов подключения к одному и тому же серверу как напрямую, так через промежуточный SSH Proxy.

    Допустим, у нас есть хост server, с именем srv-3-14 в домене company.com, и нам нужно обеспечить к нему доступ как напрямую, когда мы сидим непосредственно в локальной сети company.com, так и удалённо, через граничный маршрутизатор gate сети company.com, подключенный к интернет через двух провайдеров Provider1 и Provider2 с сетями provider1.net и provider2.net, соответственно.

    Вначале оформим подключение к граничному маршутизатору gate через его внешние интерфейсы в сетях провайдеров под именами SSH хостов gate+wan1 и gate+wan2 через провайдеров Provider1 и Provider2, соответственно.

    Для удобства примем, что оба провайдера включили адрес интерфейса gate в выделенные ими для подключения транспортных сетях в свои поддомены DNS с именами b2b.

    Match originalhost "gate+*" originalhost "*+wan1,*+wan1+*"
      HostName gw-company-net.b2b.provider1.net
    
    Match originalhost "gate+*" originalhost "*+wan2,*+wan2+*"
      HostName gw-company-net.b2b.provider2.net

    Теперь вместо того, чтобы писать длинную и утомительную команду

    ssh gw-company-net.b2b.provider1.net

    мы можем написать

    ssh gate+wan1

    и получить то же самое.

    Далее оформим подключение к маршрутизатору gate из внутренней сети.

    Допустим, интерфейс gate для подключения ко внутренней сети внесён в домен company.com под именем gw-5-1. Допустим также, что доступ к маршрутизатору осуществляется от имени gwuser, и ещё мы страдаем паранойей и перевесили SSH с порта по умолчанию на случайный порт 38262.

    Обратите внимание на то, что имя пользователя и номер порта будут одинаковыми независимо от того, через какой интерфейс мы будет подключаться к нашему маршрутизатору, поэтому команды настройки этих параметров нам следует внести в наиболее общий блок конфигурации, описывающий подключение к маршрутизатору с любыми суффиксами в его имени.

    Match originalhost "gate,gate+*"
      HostName gate.company.com
      User gwuser
      Port 38262

    Настало время приступить к настройке доступа к нашему серверу server.

    Вначале оформим подключение к серверу снаружи через SSH proxy.

    Match originalhost "server+*" originalhost "*+wan1,*+wan1+*"
      ProxyJump gate+wan1
    
    Match originalhost "server+*" originalhost "*+wan2,*+wan2+*"
      ProxyJump gate+wan2

    Если нам нужно обеспечить проброс портов через наш сервер server, самое время внести эти сведения в файл конфигурации.

    Допустим, нам нужно отобразить порты сервера 445/tcp и 8080/tcp в локальные порты на машине, с которой мы к нему подключились и организовать SOCKS5 прокси через наше подключение к серверу server.

    Match originalhost "server+*" originalhost "*+vpn,*+vpn+*"
      DynamicForward 127.100.100.100:1080
      LocalForward 127.100.100.100:4445 127.0.0.1:445
      LocalForward 127.100.100.100:8080 127.0.0.1:8080

    Теперь мы сможем подключаться к портам 445 и 8080 сервера как к портам 4445 и 8080 локального адреса 127.100.100.100, а также пользоваться SOCKS5 прокси с этим же адресом. Почему для локального отображения портов следует использовать любой другой адрес из сети 127.0.0.0/8, за исключением 127.0.0.1? Чтобы случайно не отобразить порты удалённой машины в уже открытые локальными сервисами порты с теми же номерами.

    Ну и, наконец, оформим непосредственное подключение к серверу server через внутреннюю сеть company.com, в которой он зарегистрирован под именем srv-3-14, как это было указано выше. Допустим, что доступ к нему нам разрешён от имени srv_user.

    Match originalhost "server,server+*"
      HostName srv-3-14.company.com
      User srv_user

    Сведём конфигурацию воедино.

    Match originalhost "gate+*" originalhost "*+wan1,*+wan1+*"
      HostName gw-company-net.b2b.provider1.net
    
    Match originalhost "gate+*" originalhost "*+wan2,*+wan2+*"
      HostName gw-company-net.b2b.provider2.net
    
    Match originalhost "gate,gate+*"
      HostName gate.company.com
      User gwuser
      Port 38262
    
    Match originalhost "server+*" originalhost "*+wan1,*+wan1+*"
      ProxyJump gate+wan1
    
    Match originalhost "server+*" originalhost "*+wan2,*+wan2+*"
      ProxyJump gate+wan2
    
    Match originalhost "server+*" originalhost "*+vpn,*+vpn+*"
      DynamicForward 127.100.100.100:1080
      LocalForward 127.100.100.100:4445 127.0.0.1:445
      LocalForward 127.100.100.100:8080 127.0.0.1:8080
    
    Match originalhost "server,server+*"
      HostName srv-3-14.company.com
      User srv_user

    Теперь мы сможем подключаться к серверу server из внутренней сети командой

    ssh server

    а снаружи — командами

    ssh server+wan1

    ssh server+wan2

    через провайдеров Provider1 и Provider2, соответственно.

    Если же нам потребуется настроенное нами для server отображение портов и SOCKS5 прокси, мы можем просто добавить к имени server суффикс vpn, причём как до, так и после любых других настроенных нами суффиксов wan1 и wan2:

    ssh server+vpn

    ssh server+vpn+wan1

    ssh server+vpn+wan2

    ssh server+wan1+vpn

    ssh server+wan2+vpn

    Следует, однако, помнить, что наша конфигурация не сообщит об использовании не настроенных нами суффиксов. Такие суффиксы будут молча пропущены ssh без вывода сообщения об ошибке.


  1. Zachelovek
    19.05.2026 21:36

    Хабр, нужен совет специалистов по SSH:

    В том же PuTTY есть немало настроек, например, в Connection -> SSH (обмен ключами, ключи, шифры, ...).

    Можно ли там настроить какие-то манипуляции с пакетами наподобие того, как это делается у современных ПНВ, чтобы соединение не резалось особо активными провайдерами, считающими, что я иду "куда-то не туда"?

    Я попробовал повыбирать там разные варианты, получилось немного улучшить время до разрыва, но работать нормально всё равно невозможно.

    Если нужно что-то настроить со стороны сервера, то сделать это тоже могу.


    1. MountainGoat
      19.05.2026 21:36

      SSH прятаться не умеет. Когда его делали, не то, что цели – а и мысли такой не было. Поэтому и как туннель он так себе, а ещё он от congestion страдает если им Ютуб смотреть. Выглядит, как будто провайдер скорость режет, хотя это SSH сам давится.

      Если хочется SOCKS-ов, посмотрите на ssltunnel или shadowsocks.


      1. Zachelovek
        19.05.2026 21:36

        как туннель он так себе, а ещё он от congestion страдает если им Ютуб смотреть

        Мне в основном работать и время от времени файлы перебрасывать (совсем огромных нет).

        посмотрите на ssltunnel или shadowsocks

        Полагаю, что если даже SSH режется, то теневые носки зарежут и подавно.

        Stunnel (полагаю, вы имели в виду его) выглядит занятно, если вы им пользовались, может быть сможете подсказать пару моментов? А то я с наскока не понял.

        Подключение к нему можно защитить паролем? А то там всё про ключи, да ещё и соединение Линукс-Линукс. А мне обычно с винды подключаться нужно.

        Ну и то же самое, что с носками: стоит ли начинать эксперименты с stunnel, если SSH режется? Не отрежут и его так же?


  1. nuclight
    19.05.2026 21:36

    Если вы за «красных» в области компьютерной безопасности, то, чтобы обрести в сети суперсилу и в дальнейшем бесчинствовать, вы должны понимать сеть лучше, чем те, кто её проектировал

    Чё? Они всегда назывались “черными”.

    команда для этого: ssh -N -f -L 1337:127.0.0.1:80 root@internal-web.int. Эта команда выполняется на campfire.int. Это сложная команда

    Уууу… После такого введения и вот это считать СЛОЖНОЙ командой…

    Чтобы вывести меню справки по консоли, нажмите ~?. Если вы умеете работать с vim, то эта комбинация может напомнить вам ведущий символ. Итак, таким образом открывается справочная консоль. Две из имеющихся в ней опций я нахожу очень полезными. Во-первых, это ~, убивающая ваш сеанс

    …и ни слова о том, что это работает только после Enter. Выделения моноширинным тоже нет, и получается ошибка - сессию убьет тильда-точка, но никак не тильда-запятая.

    В общем, что-то действительно интересное было только про Match, остальная часть статьи - вода для чайников.


    1. MountainGoat
      19.05.2026 21:36

      Они всегда назывались “черными”.

      Только у нас и только в казённой литературе. А в мире всегда было red team/blue team


      1. nuclight
        19.05.2026 21:36

        Первый раз слышу вообще. Всегда было black hat и white hat. Ну и, у нас “красные директора” и “черные зоны” - это ни разу не казённая литература, а народное как раз.


      1. Tsimur_S
        19.05.2026 21:36

        Red team/blue team это терминология CTF(capture the flag), где две команды WHITE HAT которые борются друг с другом.

        Black hat просто по определению участвовать в CTF не может, или он не будет black hat. Так что red-blue и white-black это разных оси дихотомии.

        То есть тут важно что имел в виду автор. Если он имел в виду настоящего злоумышленника который хочет поломать сервер Пентагона то правильно использовать тут black. Если он имел в виду участвующих в соревновании CTF то red.


  1. MountainGoat
    19.05.2026 21:36

    По поводу агента.

    В KeePassXC есть возможность сложить ключи SSH в хранилище и подключиться к SSH агенту. В результате, чтобы куда-то зайти, нужно открыть KeePassXC в фоне, и с любого терминала заходи куда хочешь или коммить из IDE на гитхаб.

    Получается, что украсть ключи сложнее, а пользоваться удобнее. KeePassXC умеет разблокироваться по паролю, по YubiKey, по биометрии. На Linux работает даже если KeePassXC в флатпаке.

    В итоге в ~/.ssh у меня лежит один только файл config. А функцию known_hosts я отключаю, у меня всё по ключам.

    У Bitwarden видел похожий пункт в справке, но не вчитывался.

    P.S. Осторожно, на KeePassXC очень много поддельных сайтов. Только https://keepassxc.org


    1. nuclight
      19.05.2026 21:36

      В итоге в ~/.ssh у меня лежит один только файл config. А функцию known_hosts я отключаю, у меня всё по ключам.

      А коим боком свои ключи к known_hosts серверов?


      1. MountainGoat
        19.05.2026 21:36

        А чем ещё может быть опасна подмена сервера, кроме как стырить пароль? Тем более что я сразу пойму, что не туда попал, так как у меня всегда есть свои кастомизации.


        1. nuclight
          19.05.2026 21:36

          Так классический MitM - это не “не туда” попал, это именно “туда”, но с перехватом. Что там может быть ценного кроме паролей, сильно зависит от назначения сервера. Как минимум, промежуточный сервер может открыть дополнительные шеллы внутри той же сессии и продолжить там что-то делать, когда ничего не подозревающий пользователь уже отключился.


          1. MountainGoat
            19.05.2026 21:36

            Верно. Но по ключу ему не светит. SSH не передаёт закрытый ключ при логине, даже успешном. Так что вредный сервер не сможет перекинуть меня на нужный.


          1. Kobagugi
            19.05.2026 21:36

            Если трафик перехвачен, MitM позволяет пробросить агент авторизации дальше

            И вот тогда твои ключи радостно пойдут гулять по всей инфре


    1. Daiichi
      19.05.2026 21:36

      В KeePassXC есть возможность сложить ключи SSH в хранилище и подключиться к SSH агенту. В результате, чтобы куда-то зайти, нужно открыть KeePassXC в фоне, и с любого терминала заходи куда хочешь или коммить из IDE на гитхаб.

      Данная схема работает также в Windows 10/11 со штатным виндовым портом OpenSSH и ssh-agent'ом из его состава, однако есть небольшой нюанс.

      К своему невероятному удивлению я заметил, как ключи SSH, загруженные KeePassXC в виндового ssh-агент'а, успешно пережили перезагрузку системы, и мне даже удалось подключиться с их помощью к своим машинам по SSH без разблокировки своей базы паролей, где они лежат. С линуксовым ssh-agent'ом я такого не замечал.

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


  1. MountainGoat
    19.05.2026 21:36

    Насчёт безопасности.

    Помните, что SSH светит наружу, есть ли у него данный публичный ключ. То есть любой майор с тремя классами образования может узнать, что вот этот сервер менеджерится тем же чуваком, что и вон тот сервер, а вон его профиль на Гитхабе: если у чувака один ключ на все случаи жизни.

    Помогает иметь секретный, стойкий к брутфорсу юзернейм. Получить список юзеров нельзя. Можно его брутить, но fail2ban не спит, да и брутить 20 символов по сети просто так никто не будет. Тут важно не спалить юзернейм через другой сервис, типа почты.

    А если вы используете ssh-agent, то знайте, что он по очереди посылает при коннекте публичные ключи, пока один не подойдёт. То есть, как только вы подключитесь к чужому/не тому серверу, даже если он вас не пустит, вы предъявите ему к осмотру все ключи, которые у вас есть. Дальше см. выше.

    Пруфы 1 2 3


    1. Daiichi
      19.05.2026 21:36

      Помните, что SSH светит наружу, есть ли у него данный публичный ключ.

      Насколько мне известно, SSH сервер не сообщает наружу открытые ключи, которые лежат в связке ключей учётной записи пользователя authorized_keys.

      Также, насколько я понимаю, атакующий может только убедиться в том, что уже имеющийся у него конкретный открытый ключ лежит в связке открытых ключей authorized_keys некоей учётной записи с уже известным атакующему именем.


      1. MountainGoat
        19.05.2026 21:36

        Да, именно так.

        А вот клиент с ssh-agent, в свою очередь, светит серверу все публичные ключи, которые у него есть.

        А гитхабы всякие светят ваш публичный SSH ключ (которым вы коммитите) вообще всем.


        1. Daiichi
          19.05.2026 21:36

          А вот клиент с ssh-agent, в свою очередь, светит серверу все публичные ключи, которые у него есть.

          Да понятно, что светит. Однако хорошо было бы уточнить, что это можно использовать в целях деанонимизации только если товарищ майор сообразил включить запись попыток аутентификации SSH.

          А гитхабы всякие светят ваш публичный SSH ключ (которым вы коммитите) вообще всем.

          Гитхабы не светят. Вы сами светите свой публичный SSH ключ, когда что-то коммитите в гитхаб. И если Вам не нравится, что товарищ майор спалит ваш единственный открытый ключ на все случаи жизни, заведите себе отдельный открытый ключ для гитхаба и настройте себе в ~/.ssh/config отдельный Host для github.com c IdentityFile, в котором укажите путь к этому ключу:

          Host github.com *.github.com
            IdentityFile ~/.ssh/me@github.com

          А если не хотите каждый раз вводить пароль к этому отдельному ключу для гитхаба, настройте себе запуск отдельной копии ssh-agent'а, укажите ему специфический сокет с помощью ключа -a, например, ~/.ssh/ssh-agent-for-github и после IdentityFile допишите IdentityAgent с указанием пути к этому сокету, а также AddKeysToAgent yes:

          Host github.com *.github.com
            IdentityFile ~/.ssh/me@github.com
            IdentityAgent ~/.ssh/ssh-agent-for-github
            AddKeysToAgent yes

          Сам не пробовал, но, согласно документации, ssh после первой попытки зайти на github.com и любые хосты в этом домене, должен спросить пароль ключа ~/.ssh/me@github.com и засунуть его в указанного агента, если пароль подойдёт. А при следующих попытках входа ssh должен, по идее, брать ключ уже из агента.


  1. Kobagugi
    19.05.2026 21:36

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