Этот пост предоставляет безопасный и очень прозрачный способ перенаправления портов или туннелирования одного TCP-соединения, используя только nc
и curl
. Более того, он также обеспечивает более безопасное туннелирование со сквозным шифрованием с помощью openssl
и socat
.
# server host
curl -sSN https://ppng.io/aaa | nc localhost 22 | curl -sSNT - https://ppng.io/bbb
# client host
curl -sSN https://ppng.io/bbb | nc -lp 2222 | curl -sSNT - https://ppng.io/aaa
- Зачем нужна проброска портов из любого места?
- Почему nc и curl?
- Piping Server
- nwtgck / piping-server
- Бесконечная передача данных между любыми устройствами по чистому HTTP с помощью труб или браузеров
- Piping Server
- Передача
- Поток
- Идеи
- Публичные серверы
- Команда переадресации портов
- Читы
- GNU nc vs OpenBSD nc vs socat
- Демонстрация SSH
- Механизм
- Высокая прозрачность
- Универсальное сквозное шифрование
- Веб-браузер, а не только терминал и команды
- E2E шифрование VNC
- Универсальное сквозное шифрование по TLS
- серверный хост
- клиентский хост
- Универсальное сквозное шифрование по SSH
- бонус: Несколько TCP-соединений
- бонус: UDP
- Ссылки
Зачем нужна проброска портов из любого места?
Сегодняшние приложения повсюду. Мы часто хотим получить доступ к приложению на удаленной машине. Полезно сделать так, чтобы удаленное приложение выглядело так, как будто оно находится на локальной машине. Для этого широко используется проброс портов или туннелирование для доступа к частному удаленному порту с локальной машины. Общим подходом для безопасной переадресации портов является использование ssh -L
. Это очень безопасное соединение через SSH. Однако публичное открытие порта SSH может быть запрещено из-за брандмауэра или NAT. Иногда нам требуется беспрепятственная переадресация портов независимо от того, где мы находимся. Кроме того, поскольку SSH очень мощный, он позволяет нам использовать оболочку по умолчанию, даже если мы хотим использовать только проброс портов без произвольного выполнения команд. Кроме того, некоторые среды могут не позволять устанавливать и запускать SSH-сервер, для которого обычно требуются права root.
Почему nc и curl?
Некоторые инструменты позволяют нам перенаправлять порты из любого места. Однако, насколько я знаю, они требуют установки собственной специальной команды. Некоторые из них являются проектами с открытым исходным кодом, но я хотел найти более прозрачный способ, потому что более короткая цепочка доверия должна быть лучше.
Я нашел более прозрачный способ, который использует существующие и широко используемые команды: nc
и curl
. Оригинальная идея проброса портов с помощью socat
, curl
и Piping Server была предложена в японской статье @Cryolite. Между тем, в этой статье представлен способ использования nc
(netcat
), который более широко используется и установлен, чем socat
, и этот способ позволяет простую интеграцию с другими командами, использующими Unix pipe. Это очень полезно при использовании ненадежного сервера, поскольку мы можем интегрировать его с командой шифрования, такой как openssl
.
Piping Server
Здесь представлено введение в Piping Server. Я разработал Piping Server, который бесконечно передает любые данные между любыми устройствами по чистому HTTP/HTTPS.
nwtgck / piping-server
Бесконечная передача данных между любыми устройствами по чистому HTTP с помощью труб или браузеров
Piping Server
Бесконечная передача данных между любыми устройствами по HTTP/HTTPS
Передача
Piping Server очень прост. Вы можете передавать данные следующим образом.
# Send echo 'hello, world' | curl -T - https://ppng.io/hello
# Get curl https://ppng.io/hello > hello.txt
Piping Server передает данные в POST /hello
или PUT /hello
в GET /hello
. Путь /hello
может быть любым, например /mypath
или /mypath/123/
. Отправитель и получатели, указавшие один и тот же путь, могут осуществлять передачу. И отправитель, и получатель могут начать передачу первыми. Первый ожидает другого.
Вы также можете использовать Web UI, например https://ppng.io, в своем браузере. Более современный пользовательский интерфейс можно найти в https://piping-ui.org, который поддерживает шифрование E2E.
Поток
Самым важным является то, что данные передаются потоком. Это означает, что вы можете передавать любые данные бесконечно. Демонстрация ниже передает бесконечный текстовый поток с помощью seq inf
.
Идеи
…
Piping Server – это HTTP-сервер. Отправитель и получатель, указавшие один и тот же путь, например /mypath
, могут осуществлять передачу. На изображении ниже показана концепция Piping Server.
На изображении выше показано, что отправитель, выполняющий POST /mypath
, и получатель, выполняющий GET /mypath
, могут передавать данные. И отправитель, и получатель могут начать передачу первыми. Первый ожидает другого. После установления соединения между отправителем и получателем, /mypath
защищен от соединения до тех пор, пока соединение не будет закрыто. Закрытие пути освобождает путь, и любой может начать новую передачу по этому пути.
Оба метода POST и PUT имеют одинаковый эффект в Piping Server. Вот простой пример передачи “hello” приемнику.
# sender: PUT /mypath
$ echo hello | curl -T - https://ppng.io/mypath
# receiver: GET /mypath
$ curl https://ppng.io/mypath
hello
Сервер передает любые данные, включая текст и двоичные файлы. Кроме того, данные через Piping Server передаются бесконечно. Данные находятся в памяти только на мгновение и никогда не сохраняются. Я опубликовал статью The Power of Pure HTTP, которая показывает, насколько мощным является HTTP и что может делать Piping Server.
Публичные серверы
Вот некоторые доступные публичные серверы.
- https://ppng.io
- https://piping.nwtgck.repl.co
- https://ppng.herokuapp.com
https://piping.nwtgck.repl.co может быть быстрее в зависимости от вашего местоположения. Вы можете разместить один, используя docker run -p 8181:8080 nwtgck/piping-server
мгновенно или другими способами, включая переносимые двоичные файлы: https://github.com/nwtgck/piping-server/wiki/How-to-self-host-Piping-Server.
Команда переадресации портов
Вот основная часть этого поста.
Предположим, у вас есть две машины с именами “server host”, которая обслуживает 22 порт и “client host”, и вы хотите использовать 22 порт на server host как новый 2222 порт на client host. Следующие команды перенаправляют порт 22 на серверном узле на порт 2222 на клиентском узле.
# server host
curl -sSN https://ppng.io/aaa | nc localhost 22 | curl -sSNT - https://ppng.io/bbb
# client host
curl -sSN https://ppng.io/bbb | nc -lp 2222 | curl -sSNT - https://ppng.io/aaa
Читы
Хотя запоминать и набирать команды очень здорово, мы можем зайти на https://piping-server-command.nwtgck.org, чтобы скопировать команды.
GNU nc vs OpenBSD nc vs socat
netcat
(nc
) имеет несколько вариантов. Способы прослушивания различаются следующим образом.
- GNU nc:
nc -lp 2222
. - OpenBSD nc:
nc -l 2222
- socat:
socat TCP-LISTEN:2222,reuseaddr -
.
# client host (GNU nc)
curl -sSN https://ppng.io/bbb | nc -lp 2222 | curl -sSNT - https://ppng.io/aaa
# client host (OpenBSD nc)
curl -sSN https://ppng.io/bbb | nc -l 2222 | curl -sSNT - https://ppng.io/aaa
# client host (socat)
curl -sSN https://ppng.io/bbb | socat TCP-LISTEN:2222,reuseaddr - | curl -sSNT - https://ppng.io/aaa
Демонстрация SSH
Вот демонстрационное видео о перенаправлении 22 порта моего сервера “basil” на мой локальный порт 2222.
SSH очень мощный, потому что вы можете создать SOCKS-прокси с помощью ssh -D 1080 ...
как простую VPN, сделать rsync для быстрой возобновляемой передачи файлов, использовать GUI с помощью ssh -X ...
и смонтировать удаленную файловую систему с помощью SSHFS.
В примере речь идет об SSH, поскольку это буквально известный протокол. Этот метод позволяет перенаправлять любой TCP-порт для одного соединения, не только SSH.
Механизм
Цель туннелирования заключается в следующем, перенаправить 22 порт “серверной программы” на 2222 порт “клиентской программы”.
На схеме ниже показано, как две вышеуказанные команды соединяют друг друга. Все исходящие соединения являются HTTPS. Это означает, что требования к туннелированию такие же, как и при просмотре веб-страниц. Поэтому туннелирование можно выполнять в любом месте.
Вы можете контролировать время проброса портов. Каждая сторона сервера и клиентская сторона может запустить команду первой. Каждая сторона может закрыть туннель с помощью Ctrl+C в любое время, и в это время команда другой стороны выходит из туннеля, освобождая пути. Piping Server гарантирует, что передаваемые данные протокола находятся только в памяти и никогда не сохраняются. Сервер также гарантирует, что после установления проброса портов на путях, никто не сможет установить и получить данные на путях до тех пор, пока предварительно установленный туннель не будет закрыт.
Высокая прозрачность
Я считаю, что “чем меньше вы доверяете, тем больше вы защищены”.
Некоторые инструменты туннелирования требуют установки дополнительного специального CLI. В отличие от них, этот способ использует только команды nc
(netcat) и curl
. Они широко используются и приняты, и большинство людей знают, как они работают, и уже доверились им. Вы просто используете общие команды, не нужно устанавливать дополнительные инструменты и доверять им. Мы можем понять, что команды никогда не отправляют никакой дополнительной информации, такой как частная информация или учетные данные. Это означает отсутствие “черного ящика”. Кроме того, команды достаточно коротки, чтобы их можно было запомнить.
Вся коммуникация снаружи осуществляется по протоколу HTTP/HTTPS. Этот протокол также хорошо знаком, широко используется, принят и ему уже доверяют. Piping Server открыт на GitHub, разработан на TypeScript и Node.js. Другие реализации на Rust и Go предоставляются с открытым исходным кодом. Сервер разработан как можно более простым, чтобы проверить исходный код и уменьшить вероятность ошибок.
Чтобы повысить прозрачность и получить независимость от предварительно размещенных публичных серверов, вы можете самостоятельно разместить Piping Server. Если вы предпочитаете ограничивать трафик с помощью базового auth или пути и т.д., вам пригодится Rich Piping Server, который использует оригинальный Piping Server в качестве библиотеки.
Мы должны доверять нескольким существующим общепринятым инструментам командной строки и Piping Server. Даже если источник сервера является публичным, сервер может иметь уязвимости или может быть злонамеренно модифицирован. Чтобы избежать этих проблем, я введу сквозное шифрование (E2E-шифрование) для обеспечения более высокого уровня безопасности. E2E-шифрование позволяет нам не доверять серверу и делает нас безопасными.
Универсальное сквозное шифрование
В этом разделе описывается безопасное туннелирование, даже если сервер Piping Server не заслуживает доверия. Как вы знаете, весь HTTPS-трафик к серверу шифруется. Однако, что если сервер скомпрометирован или является вредоносным? Туннелирование SSH является одним из решений. Этот раздел предоставляет более универсальный способ сквозного шифрования для любого протокола.
Идея проста: расшифровка входящих и шифрование исходящих потоков происходит следующим образом.
# server host (idea)
curl -sSN https://ppng.io/aaa | <decrypt> | nc localhost 22 | <encrypt> |curl -sSNT - https://ppng.io/bbb
# client host (idea)
curl -sSN https://ppng.io/bbb | <decrypt> | nc -lp 2222 | <encrypt> | curl -sSNT - https://ppng.io/aaa
Реальные команды, использующие openssl
, следующие. Хотя используются команды openssl
, любые команды шифрования могут быть заменены существующими или изобретенными в будущем командами.
# server host
curl -sSN https://ppng.io/aaa | stdbuf -i0 -o0 openssl aes-256-ctr -d -pass "pass:mypass" -bufsize 1 -pbkdf2 | nc localhost 22 | stdbuf -i0 -o0 openssl aes-256-ctr -pass "pass:mypass" -bufsize 1 -pbkdf2 | curl -sSNT - https://ppng.io/bbb
# client host
curl -sSN https://ppng.io/bbb | stdbuf -i0 -o0 openssl aes-256-ctr -d -pass "pass:mypass" -bufsize 1 -pbkdf2 | nc -lp 2222 | stdbuf -i0 -o0 openssl aes-256-ctr -pass "pass:mypass" -bufsize 1 -pbkdf2 | curl -sSNT - https://ppng.io/aaa
- Пароль
mypass
. Вы можете заменить его на более подходящий.
Команды ниже позволяют вводить пароль и скрывают пароль от команд.
# server host
read -p "password: " -s pass && curl -sSN https://ppng.io/aaa | stdbuf -i0 -o0 openssl aes-256-ctr -d -pass "pass:$pass" -bufsize 1 -pbkdf2 | nc localhost 22 | stdbuf -i0 -o0 openssl aes-256-ctr -pass "pass:$pass" -bufsize 1 -pbkdf2 | curl -sSNT - https://ppng.io/bbb; unset pass
# client host
read -p "password: " -s pass && curl -sSN https://ppng.io/bbb | stdbuf -i0 -o0 openssl aes-256-ctr -d -pass "pass:$pass" -bufsize 1 -pbkdf2 | nc -lp 2222 | stdbuf -i0 -o0 openssl aes-256-ctr -pass "pass:$pass" -bufsize 1 -pbkdf2 | curl -sSNT - https://ppng.io/aaa; unset pass
Эти команды также есть в шпаргалке.
Веб-браузер, а не только терминал и команды
Высокая функциональная совместимость имеет значение, так как реализует меньшее количество зависимостей и меньшую блокировку. Этот раздел демонстрирует интероперабельность, заменяя nc и curl на JavaScript.
Piping SSH и Piping VNC работают в веб-браузере. Это означает, что веб-браузер является альтернативным фронтендом curl ... | nc -lp ... | curl ...
и терминала. Эти приложения имеют реализации SSH и VNC (протокол RBF) на JavaScript, благодаря авторам основанных проектов. Это означает, что они работают на вашем локальном устройстве и никогда не обрабатываются на стороне сервера. Сервер является только Piping Server только для передачи данных. Подробнее см. в моем предыдущем посте.
Эти приложения требуют “fetch() upload streaming”, который сейчас доступен только в браузерах на базе Chromium >= 85 с включенными “Experimental Web Platform features”. Все крупные производители положительно отреагировали на “fetch() upload streamingl”. На видео ниже приведен пример использования Piping SSH.
VNC широко используется для удаленного рабочего стола. В примере ниже есть два окна, Ubuntu на виртуальной машине и Piping VNC на Chrome на моей локальной машине. Это показывает относительно плавную работу удаленного рабочего стола.
Способ проброса портов имеет высокую совместимость, поскольку команда проброса, curl ... | nc ... | curl ...
, в примере видеороликов одинакова, даже если клиент работает через веб-браузер. В этом примере fetch()
в JavaScript заменяет nc, curl и terminal. В качестве другого примера, java.net.URLConnection и java.net.Socket в Java и net.Dial() и http в языке Go могут заменить их.
E2E шифрование VNC
Хотя транспорт к Piping Server шифруется с помощью TLS из-за HTTPS, шифрование E2E для VNC безопасно, если сервер недоверенный.
В Piping VNC вы можете поставить галочку в пункте “Шифровать с помощью OpenSSL AES CTR” ниже. Это имеет тот же эффект, что и openssl aes-256-ctr -d -pass "pass:mypass" -bufsize 1 -pbkdf2 -iter 100000 -md sha256
, упомянутый в разделе “Универсальное сквозное шифрование” выше.
Piping VNC автоматически генерирует команду, которую вы должны ввести на стороне хоста сервера.
Как это возможно в Web-браузере? Я сделал https://github.com/nwtgck/openssl-aes-ctr-stream-npm для использования совместимого с OpenSSL шифрования/дешифрования AES CTR в Web-браузере. Концепция очень проста и заключается в следующем.
// Encrypt the ReadableStream `uploadReadableStream`
const encryptedUploadReadableStream = opensslAesCtrStream.aesCtrEncryptWithPbkdf2(
uploadReadableStream,
options
);
// Transfer the encrypted stream
fetch(pipingServerUrl, {
method: "POST",
body: encryptedUploadReadableStream,
});
(упрощенная версия https://github.com/nwtgck/piping-vnc-web/blob/8f2df75c7452b515fcc2ba36fe9cbf6218c673fc/core/websock.js#L205-L214)
Реализация openssl-aes-ctr-stream
короче 200 строк: https://github.com/nwtgck/openssl-aes-ctr-stream-npm/blob/d071752009b0c6262f6c27722b438cb73cdb7cab/src/index.ts. Основные браузеры предоставляют Web Crypto API. openssl-aes-ctr-stream
использует AES-CTR в Web Crypto API.
Вот демонстрационный видеоролик E2E шифрования VNC. Из-за шифрования E2E экран работает медленнее, чем без него.
https://piping-vnc.nwtgck.org
Поскольку протокол OpenSSL является открытым, мы можем сохранять высокую функциональную совместимость с реализацией на другом языке.
Универсальное сквозное шифрование по TLS
В этом разделе представлен способ шифрования предварительно прослушанного TCP-порта с помощью TLS с использованием socat. Это также универсальный способ шифрования E2E. Преимущества этого способа в том, что вы можете использовать хорошо проверенный TLS и шифрование с использованием общих сертификатов вместо пароля.
На хосте сервера создайте сертификаты следующим образом. При этом создаются server.crt
, server.key
и server.pem
.
# server host
openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt -days 3653 -sha256 -nodes -subj '/CN=localhost/' && cat server.key server.crt > server.pem
На клиентском узле создайте сертификаты следующим образом. Создаются client.crt
, client.key
и client.pem
.
# client host
openssl req -x509 -newkey rsa:4096 -keyout client.key -out client.crt -days 3653 -sha256 -nodes -subj '/CN=localhost/' && cat client.key client.crt > client.pem
Передайте server.crt
на клиентский хост и client.crt
на серверный хост соответственно.
серверный хост
Предположим, что серверный хост обслуживает порт 8181, и вы хотите перенаправить порт 8181 клиенту с шифрованием. Откройте два терминала на серверном узле и выполните следующие команды. Обязательно запустите socat в каталоге, в котором есть server.pem
и client.crt
.
socat openssl-listen:4433,reuseaddr,cert=server.pem,cafile=client.crt tcp:localhost:8181
curl -sSN https://ppng.io/aaa | nc localhost 4433 | curl -sSNT - https://ppng.io/bbb
клиентский хост
Откройте два терминала на клиентском узле и выполните следующие команды. Убедитесь, что socat запущен в директории, в которой есть client.pem
и server.crt
.
curl -sSN https://ppng.io/bbb | nc -lp 5433 | curl -sSNT - https://ppng.io/aaa
socat tcp-listen:8282,reuseaddr openssl-connect:localhost:5433,openssl-commonname=localhost,cert=client.pem,cafile=server.crt
Теперь порт 8282 на клиентском узле является расшифрованным TCP-портом, перенаправленным на порт 8181 на серверном узле. Когда протоколом порта 8181 на серверном узле является HTTP, вы можете использовать curl localhost:8282
на клиентском узле.
Вот график, описывающий работу этого E2EE.
[server host 8181 <--> 4433] <-- TLS over HTTPS --> [Piping Server] <-- TLS over HTTPS --> [5433 <--> 8282 client host]
Способ основан на Securing Traffic Between Two Socat Instances Using SSL, найденном на официальной странице socat. Если вы столкнулись с ошибкой Invalid argument
в macOS с socat, вы можете обновить socat 1.7.4.2 или более позднюю версию, которая исправлена.
Универсальное сквозное шифрование по SSH
Вы можете просто использовать ssh -L
следующим образом, если машина на стороне хоста сервера имеет SSH-сервер. SSH также мультиплексирует TCP-соединения через одно SSH-соединение.
Предположим, вы хотите перенаправить порт 8080 на серверном узле с шифрованием на порт 8181 на клиентском узле. Предположим, что серверный узел прослушивает SSH на 22 порту. Команда ниже перенаправляет 22 на 2222 и 8080 на 8181.
# server host
curl -sSN https://ppng.io/aaa | nc localhost 22 | curl -sSNT - https://ppng.io/bbb
# client host 1/2
curl -sSN https://ppng.io/bbb | nc -lp 2222 | curl -sSNT - https://ppng.io/aaa
# client host 2/2
ssh -p 2222 -L 8181:localhost:8080 <server host user>@localhost
Теперь вы можете получить доступ к localhost:8181 на клиентском хосте.
бонус: Несколько TCP-соединений
Представленный здесь способ позволяет перенаправлять одно TCP-соединение. Одного TCP-соединения достаточно для SSH и VNC (протокол RFB). Эта возможность иногда способствует большей безопасности, поскольку одно соединение всегда гарантировано. Однако, например, HTTP обычно требует нескольких TCP-соединений, даже когда доступен HTTP/2, который мультиплексирует потоки запросов и ответов.
Простым решением является мультиплексирование TCP-запросов через Unix pipe. Для мультиплексирования TCP-запросов существует Yamux, созданный компанией Hashicorp, которая создает Vagrant, Terraform и так далее. Протокол Yamux вдохновлен SPDY, который лежит в основе спецификации HTTP/2. Спецификацию протокола можно найти в https://github.com/hashicorp/yamux/blob/master/spec.md. libp2p, которая используется в IPFS, также использует Yamux в качестве одного из вариантов мультиплексов и поддерживает Go и Rust версии библиотек Yamux.
Я сделал yamux CLI и распространяю его портативные двоичные файлы для нескольких платформ. Вы можете установить его с https://github.com/nwtgck/yamux-cli и заменить nc
на yamux
следующим образом.
# server host
curl -sSN https://ppng.io/aaa | yamux localhost 8080 | curl -sSNT - https://ppng.io/bbb
# client host
curl -sSN https://ppng.io/bbb | yamux -l 8181 | curl -sSNT - https://ppng.io/aaa
Вы также можете комбинировать команды yamux и E2E для повышения безопасности.
Эта тема находится в бонусном разделе, так как ее способ требует дополнительного CLI. Способ из раздела “Универсальное сквозное шифрование по SSH” также позволяет использовать несколько TCP-соединений, поскольку ssh -L
также мультиплексирует TCP-соединения.
бонус: UDP
TCP идеально подходит для Unix pipe, потому что TCP передает потоки. В отличие от него, UDP передает дейтаграммы, поэтому для передачи нескольких дейтаграмм через Unix pipe необходимы границы. Я успешно выполнил команду dig
для пересылки DNS, используя nc -u
. Однако несколько дейтаграмм не обрабатываются. Чтобы решить эту проблему, я сделал экспериментальную опцию в yamux
cli для UDP.
# server host (experimental)
curl -sSN https://ppng.io/aaa | yamux -u localhost 53 | curl -sSNT - https://ppng.io/bbb
# client host (experimental)
curl -sSN https://ppng.io/bbb | yamux -ul 1053 | curl -sSNT - https://ppng.io/aaa
“Экспериментальный” означает, что я могу изменить способ передачи данных через Yamux. Датаграммы с одного и того же адреса передаются по одному и тому же потоку Yamux. Структура данных проста, имеет длину датаграммы в 4 байта в сетевом порядке байт и необработанную датаграмму.
В моем эксперименте HTTP/3, HTTP over QUIC, может быть перенаправлен через порт Piping Server. На самом деле, я реализовал HTTP/3 Piping Server через Piping Server.
Ссылки
- Японская статья о пробросе портов с помощью socat и curl
- GitHub репозиторий Pipnig Server
- GitHub репозиторий Piping Server на Rust
- Как самостоятельно разместить Piping Server
- Шпаргалка по командам
Изображение для обложки предоставлено Fabian Jung на Unsplash