SSH 터널링 및 포트 포워딩 시각 가이드 (2023)
(ittavern.com)- SSH 터널링은 보안 SSH 연결 위로 TCP 트래픽을 실어, 레거시 프로토콜 암호화·관리 패널 접근·NAT 뒤 서버 접속처럼 제한된 경로를 안전하게 여는 데 쓰임
- 서버 측에서는
AllowTcpForwarding yes가 필요하며, 루프백이 아닌 인터페이스에 포트를 열려면GatewayPorts yes설정과 SSH 서버 재시작이 필요함 ssh -J,ssh -L,ssh -R,ssh -D는 각각 점프호스트, 로컬 포워딩, 원격 포워딩, SOCKS5 기반 동적 포워딩을 담당함- 터널을 백그라운드로 유지하려면
ssh -fN을 쓰고, 끊김 감지는ClientAliveInterval과ClientAliveCountMax같은 하트비트 설정으로 조정함 - SSH 터널링은 UDP를 직접 지원하지 않고 TCP-over-TCP에서 지연과 처리량 문제가 생길 수 있어, VPN 대체재보다는 특정 TCP 경로를 여는 도구에 가까움
SSH 터널링을 쓰는 상황
- SSH 터널링과 포트 포워딩은 SSH 연결을 통해 TCP 트래픽을 클라이언트에서 서버로, 또는 서버에서 클라이언트로 전달함
- TCP 포트와 UNIX 소켓을 사용할 수 있지만, 예시는 TCP 포트 중심임
- 대표적인 활용 방식은 보안, 문제 해결, 연결 우회로 나뉨
- 보안
- FTP 같은 안전하지 않은 연결이나 레거시 프로토콜을 암호화함
- 공개키 인증 기반 SSH 터널로 웹 관리 패널에 접근함
- 80/443 같은 추가 포트를 노출하지 않고 22번 포트만 열 수 있음
- 문제 해결
- 방화벽이나 콘텐츠 필터를 우회함
- 다른 네트워크 경로를 선택함
- 연결
- NAT 뒤에 있는 서버에 접근함
- 점프호스트를 통해 인터넷에서 내부 서버로 들어감
- 로컬 포트를 인터넷에 노출함
- 보안
포트 포워딩 전 확인할 설정
- 예시의 옵션들은 환경에 맞게 조합해 설정할 수 있음
bind_address를 지정하지 않으면 기본값은 localhost임- 로컬과 원격 사용자는 각 머신에서 포트를 열 권한이 필요함
0-1024번 포트는 별도 설정이 없으면 root 권한이 필요함- 그 외 포트는 일반 사용자도 설정할 수 있음
- 클라이언트와 네트워크 방화벽도 포워딩 경로에 맞게 열려 있어야 함
- SSH 서버에서는 포트 포워딩이 활성화되어 있어야 함
AllowTcpForwarding yes- 기본적으로 활성화된 경우가 많지만 서버 설정 확인이 필요함
127.0.0.1이 아닌 인터페이스로 포트를 포워딩하려면 SSH 서버에서GatewayPorts를 활성화함GatewayPorts yes- 설정 후 SSH 서버 서비스를 재시작해야 함
SSH 점프호스트와 다단계 터널
ssh -J는 하나 이상의 호스트를 거쳐 원격 호스트에 투명하게 연결할 때 사용함
ssh -J user@REMOTE-MACHINE:22 -p 22 user@10.99.99.1
- 기본 포트
22를 사용할 때는 포트 표기를 생략할 수 있음 - REMOTE-MACHINE을 점프호스트로 사용하면 연결은 다음처럼 확인됨
[user@REMOTE-MACHINE]$ ss | grep -i ssh
tcp ESTAB 0 0 167.135.173.108:ssh 192.160.140.207:45960
tcp ESTAB 0 0 10.99.99.2:49770 10.99.99.1:ssh
- 예시 주소의 역할은 다음과 같음
167.135.173.108: REMOTE-MACHINE의 공개 IP192.160.140.207: LOCAL-MACHINE의 공개 IP10.99.99.2: REMOTE-MACHINE의 내부 IP10.99.99.1: REMOTE-WEBAPP의 내부 IP
- 여러 점프호스트를 사용할 때는 쉼표로 구분함
ssh -J user@REMOTE-MACHINE:22,user@ANOTHER-REMOTE-MACHINE:22 -p 22 user@10.99.99.1
로컬 포트 포워딩
- 로컬 포트 포워딩은
ssh -L옵션을 사용함 - 첫 번째 예시는 LOCAL-MACHINE의
10.10.10.1:8001을 REMOTE-MACHINE의localhost:8000으로 전달함
ssh -L 10.10.10.1:8001:localhost:8000 user@REMOTE-MACHINE
- REMOTE-MACHINE에서
127.0.0.1에만 바인딩된 웹서버 접근 로그는 다음처럼 보임
127.0.0.1 - - [30/Dec/2022 18:05:15] "GET / HTTP/1.1" 200
- 이 요청은 LOCAL-MACHINE에서 시작됨
- 두 번째 예시는 로컬
8001을 REMOTE-MACHINE을 통해10.99.99.1:8000으로 전달함
ssh -L 8001:10.99.99.1:8000 user@REMOTE-MACHINE
- REMOTE-WEBAPP의 웹서버 접근 로그에는 REMOTE-MACHINE의 내부 IP에서 온 요청으로 남음
10.99.99.2 - - [30/Dec/2022 21:28:42] "GET / HTTP/1.1" 200
원격 포트 포워딩
- 원격 포트 포워딩은
ssh -R옵션을 사용함 - 예시는 REMOTE-MACHINE의
8000을 로컬 쪽localhost:8001또는10.10.10.2:8001로 전달함
ssh -R 8000:localhost:8001 user@REMOTE-MACHINE
ssh -R 8000:10.10.10.2:8001 user@REMOTE-MACHINE
- 특정 원격 인터페이스인
10.99.99.2:8000에서 수신하도록 설정할 수도 있음
ssh -R 10.99.99.2:8000:10.10.10.2:8001 user@REMOTE-MACHINE
- 루프백 인터페이스가 아닌 곳에서 수신하려면 SSH 서버에
GatewayPorts yes가 활성화되어 있어야 함
동적 포트 포워딩과 SOCKS5
- 여러 포트를 전달할 때 SSH는 SOCKS 프로토콜을 사용함
- SOCKS는 투명 프록시 프로토콜이며, SSH는 최신 버전인 SOCKS5를 사용함
- SOCKS5 서버의 기본 포트는 RFC 1928에 정의된
1080임 - 클라이언트는 애플리케이션 계층이나 OS 계층에서 SOCKS 프록시를 사용하도록 설정되어야 함
ssh -D예시는10.10.10.1:5555에 SOCKS 프록시를 엶
ssh -D 10.10.10.1:5555 user@REMOTE-MACHINE
- LOCAL 클라이언트에서는
curl로 연결 경로를 테스트할 수 있음
curl -L -x socks5://10.10.10.1:5555 brrl.net/ip
- 정상 동작하면 REMOTE-MACHINE의 공개 IP가 반환됨
SSH TUN/TAP 터널링
-w플래그로 양방향 터널을 만들 수 있음- 인터페이스는 미리 생성되어 있어야 함
- 이 방식은 테스트되지 않았다는 caveat가 남아 있음
-w local_tun[:remote_tun]
백그라운드 실행과 종료
- 터널을 네이티브 방식으로 백그라운드 실행하려면
-fN을 사용함-f: 백그라운드 실행-N: 셸을 열지 않음
ssh -fN -L 8001:127.0.0.1:8000 user@REMOTE-MACHINE
- 그 외에는
screen이나 다른 도구를 사용할 수 있음 - 백그라운드에서 실행 중인 SSH는 프로세스를 찾아 PID로 종료함
user@pleasejustwork:~$ ps -ef | grep ssh
[...]
user 19255 1 0 11:40 ? 00:00:00 ssh -fN -L 8001:127.0.0.1:8000 user@REMOTE-MACHINE
[...]
kill 19255
SSH 연결 유지와 재접속
- SSH 연결 유지 방법은 여러 가지가 있음
- 타임아웃 처리를 위한 하트비트 옵션은 클라이언트, 서버, 또는 양쪽 모두에 설정할 수 있음
ClientAliveInterval은 연결 유지를 위해 매n초마다 요청을 보냄
ClientAliveInterval 15
ClientAliveCountMax는 반대편에서 응답을 받지 못한 뒤 연결을 종료하기 전까지 보내는 하트비트 요청 수임
ClientAliveCountMax 3
3은 기본값이고,0으로 설정하면 연결 종료를 비활성화함- 예시 설정에서는 응답이 없으면 약 45초 뒤 연결이 끊어짐
- 연결 종료 후 재접속에는
autossh, 스크립트, cronjob 등을 사용할 수 있음
제한과 보안상 주의점
-
UDP
- SSH는 올바른 복호화를 위해 신뢰성 있는 전달에 의존함
- UDP는 신뢰성을 제공하지 않아 SSH 터널에서 지원되지 않고 권장되지 않음
- UDP over SSH tunneling을 다룬 방법이 있지만 테스트되지 않았다는 caveat가 있음
-
TCP-over-TCP
- 오버헤드 때문에 처리량이 낮아지고 지연이 증가함
- 패킷 손실이나 높은 지연이 있는 연결에서는 TCP meltdown이 발생할 수 있음
- TCP over TCP 관련 글을 참고할 수 있음
- OpenVPN-over-TCP를 한동안 사용했을 때 처리량은 UDP보다 낮았지만 안정적으로 동작했으며, 결과는 구성에 크게 의존함
-
VPN 대체재로 쓸 때의 한계
- SSH 터널링을 VPN처럼 사용할 수는 있지만, 더 나은 성능에는 VPN이 더 적합함
-
보안 위험
- 해당 기능이 필요 없다면 비활성화하는 것이 권장됨
- 공격자가 SSH 터널링과 포트 포워딩을 사용해 방화벽과 다른 보안 수단을 피할 수 있음
-
참고 문서
댓글과 토론
Hacker News 의견들
-
2024년에는 SSH 명령을 길게 직접 쓰기보다
~/.ssh/config에 LocalForward, RemoteForward, ProxyJump를 설정하는 편이 좋음
중간 SSH 연결을 여러 번 거쳐야 하는 원격 서버에ssh,scp,rsync로 접근할 때 시간을 크게 줄여줌
예를 들어jump-host-1,jump-host-2,jump-host-3를ProxyJump로 이어두고target-server에 별칭으로 접속하면 됨
LocalForward 0.0.0.0:8080 0.0.0.0:80은 원격의 80번 포트를 로컬 8080으로 전달하고,RemoteForward 0.0.0.0:9022 0.0.0.0:22는 원격 9022로 들어온 SSH 요청을 로컬 22번으로 전달함
LocalForward와RemoteForward에서는 왼쪽이 대상 서버, 오른쪽이 로컬이며,localhost나127.0.0.1대신0.0.0.0을 쓰라는 팁도 있음ssh_config팁을 공유하자면, 집 안이나 자체 호스팅 VPN 안에서는 각 장비에 직접 SSH하고, 밖에서는 점프 호스트를 거치도록 자동 분기해두는 구성이 유용함
Match host *.example.org exec "getent ahosts router.example.org | grep -q ^10.0.0.1"처럼 라우터 주소로 집/VPN 여부를 먼저 감지하고,arp로 MAC 주소가 기대값과 다르면ProxyJump jump.example.org를 쓰게 만들 수 있음
순서가 중요해서 VPN/집 네트워크 감지를 먼저 하고, 그다음 외부일 때 점프 호스트를 적용해야 함- 가끔은 한 번만 쓰고 끝낼 일이라 설정 파일까지 만들고 싶지 않을 때도 있음
짧게 쓸 VPS를 만들어 SSH를 SOCKS 프록시로 쓰면서 지역 제한을 우회하거나, 다른 팀을 잠깐 도와주느라 평소 접근하지 않는 서버에 한 번 들어가야 하는 경우에는 명령행 옵션이 더 낫다고 봄 0.0.0.0을 쓰는 건 위험할 수 있음
모든 네트워크 인터페이스에서 포트를 열 수 있고, 특히 원격 서버에 방화벽이 없으면 무언가를 인터넷 전체에 노출하게 됨
자주 쓰는 터널링이나 포트 전달이라면 설정 파일이 좋지만, 일회성이나 드문 작업이면 명령행 옵션이 더 낫다고 봄- 이런 SSH 설정을 여러 로컬 머신에 표준화해서 배포하려면 어떻게 관리하는지 궁금함
단일 사용자/여러 머신에 맞고, 별도 서버를 항상 켜둘 필요 없이 GitHub 같은 곳에서 설정을 가져오는 GitOps 비슷한 해법을 찾고 있음 - 항상 셸이 열리기를 원하는 건 아니라서, 기본적인
bash/fish함수를 만들어 최소한의 인자만 기억하면 되게 해뒀음
잊어버리면 문서화해둔 함수 정의를 보면 됨
-
내가 일하는 말도 안 되는 기업 환경에서는 SSH 터널링이 필수임
접근 권한을 받거나 포트를 열거나 방화벽/VPN 예외를 얻는 데 관료주의 때문에 몇 주씩 걸릴 때가 있음
이 글은-D를 언급하지만 그 위력을 충분히 설명하지는 않음
ssh -D 8888 someserver를 실행하고 브라우저의 SOCKS 프록시를localhost:8888로 설정하면, 브라우저 트래픽이 전부someserver를 통해 라우팅됨
Firefox는 아직도 시스템 기본값을 바꾸지 않고 이 설정을 할 수 있어서 매우 유용함- 2000년대 중반에는 집 밖에서 웹을 볼 때 거의 표준 방식으로 그렇게 썼음
그런데 회사에 들어가 보니 IP 허용 목록 때문에 인터넷의 임의 서버로 SSH 연결조차 못 했고, 너무 괴로워서 HTTP 터널을 만들고 내가 제어하는 서버를 허용 목록에 넣는 방법까지 알아보다가 결국 이직했음 - 기업 네트워크를 우회하는 건 좋은 생각이 아님
그런 제한은 이유가 있고, 우회는 조직의 절차와 보안에 대한 전문성 부족과 무시로 보일 수 있음
접근 권한을 받는 데 몇 주나 몇 달이 걸리면 그렇게 기다려야 하며, 기밀 정보에 백도어를 열거나 보안을 훼손한 책임을 지고 싶지는 않을 것임
- 2000년대 중반에는 집 밖에서 웹을 볼 때 거의 표준 방식으로 그렇게 썼음
-
새벽 3시에 해본 가장 지저분한 SSH 터널링 해킹은 세 곳의 데이터센터를 잇는 작업이었음
같은 도시권에 있는 세 시설이 인터넷 상위망에는 연결돼 있었지만, 이상한 라우팅 정책 때문에 A는 B에만, B만 C에 연결할 수 있었음
결국 A의 데이터를 B를 거쳐 C로 옮기기 위해 rsync + SSH 터널 + 키 + 라우팅 꼼수를 뒤섞어야 했고, 주문 같은 명령을 몇 번 고쳐 맞춘 뒤 전체가 한 번에 움직이는 걸 보니 마법 같았음
지금 보면 어떻게 할지 너무 명확하지만, 당시 초보였던 입장에서는 큰 성취였고 동기화가 끝난 걸 확인했을 때의 짜릿함이 아직 기억남~/.ssh/config와 ProxyJump를 쓰면 A, B, C, D, E처럼 몇 단계든 사실상 점프해서 이동할 수 있음
-
시각화의 세부 정보가 좋음
네트워킹에는 특히 더 낮은 수준의 연결에서 트래픽을 시각적으로 표현하는 도구가 훨씬 많아졌으면 함- 여기 다이어그램도 볼 만함: https://www.nathanhandy.blog/articles/osi-model-revisited.ht...
물론 정적인 개념 표현일 뿐이고, 대부분의 네트워크 벤더도 어떤 형태의 트래픽 시각화는 제공하지만 보통은 개별 지표나 그래프 수준에 그침
- 여기 다이어그램도 볼 만함: https://www.nathanhandy.blog/articles/osi-model-revisited.ht...
-
리눅스 서버나 IoT 장비가 방화벽 뒤에 있고 고정 IP도 없는데 SSH로 접속하고 싶다면, https://sshreach.me 같은 터널링 서비스를 쓸 수 있음
-
몇 년 동안 터널링을 꽤 많이 썼는데
-J옵션은 몰랐음
몇 달에 한 번 터널이 필요할 때마다 30분씩 설정하느니, 터널을 구성해주는 시각적 도구가 있었으면 좋겠음 -
TCP-over-TCP가 오버헤드를 늘려 처리량을 낮추고 지연 시간을 높이며, 패킷 손실이나 위성망처럼 지연이 큰 연결에서 TCP 멜트다운을 일으킬 수 있다는 설명이 있음
하지만 SSH 터널에서는 TAP/TUN을 쓰지 않는 한 실제로는 문제가 되지 않음
SSH가 TCP 스트림을 풀어서 전달하기 때문임
다만 여러 채널을 쓸 때는 헤드 오브 라인 블로킹 때문에 성능이 떨어질 수 있음- 여러 채널에서 생기는 헤드 오브 라인 블로킹 문제는 별도 SSH 연결 풀을 쓰면 해결할 수 있음: https://github.com/Snawoot/rsp?tab=readme-ov-file#performanc...
-
약 15년 전 대학 네트워크의 방화벽을 우회하려고 SSH 터널을 배웠고, 기본 포트를 443번으로 바꿔야 했음
그 뒤로는 방화벽 우회보다 훨씬 다양한 용도로 계속 쓰고 있음- 방화벽 우회나 로컬 서비스를 네트워크 너머로 노출하는 것 말고 어떤 용도로 재미를 봤는지 궁금함
-
sshuttle을 써보면 터널링이 훨씬 편함
sshuttle -r user@host 10.0.0.0/8처럼 쓰면10/8대역은 자동으로 터널링되고, 사실상 SSH 위의 VPN처럼 동작함 -
SSH 자체에 리다이렉트 기능이 있는지 궁금함
예를 들어 A가 B에 SSH하려고 하면 B가 C로 접속하라고 알려주고, A가 투명하게 C에 직접 연결되며, B는 더 이상 핵심 데이터 경로에 남지 않는 식의 기능이 있는지 궁금함- 가상 IP로 비슷하게 할 수 있을 것 같음
내 방화벽/라우터는 DHCP option 67을 제대로 전달하지 않고 무조건 자기 주소를 보내서, 해당 포트의 PXE 부팅 트래픽이 라우터 IP로 가면 실제 PXE 부팅 서버로 라우팅되도록 가상 IP/규칙을 설정해야 했음 - A가 실제 대상이 C라는 걸 모르면 오해의 소지가 있음
그렇지 않다면 점프 기능을 쓰면 됨:ssh -J B C
B가 경로에 있을 필요가 없고 C에 직접 연결할 수 있다면 그냥 C로 직접 연결하면 되고, 그게 안 된다면 어차피 B는 홉으로 들어가야 함 - B가 C로 포트 전달, 즉 패킷 라우팅을 할 수는 있겠지만 HTTP의 영구 리다이렉트 같은 동등한 기능은 없는 것 같음
문제를 더 설명하면 더 적합한 해법이 있을 수 있음
어느 정도 임베디드된 호스트라면 DNS가 “라우팅”을 맡게 할 수 있지만, 이 경우 호스트 키 지문 검증은 직접 처리해야 함
- 가상 IP로 비슷하게 할 수 있을 것 같음