SSD Nodes Learn
가이드 Matt Connor작성자 Matt Connor · 업데이트됨 2026-07-24

UFW IPv6 설정 방법 및 VPS 보안 취약점 해결

UFW가 IPv4만 필터링하여 IPv6 포트가 노출되는 보안 문제를 해결하십시오. VPS에서 IPv6 주소로 서비스가 무방비하게 열려 있는 원인과 확인 방법을 설명합니다.

IPv6 방화벽의 함수 (한 문장 요약)

방화벽이 IPv4를 보호하고 있습니다. VPS에는 거의 확실히 공용 IPv6 주소도 할당되어 있으며, 많은 서비스가 기본적으로 해당 주소에서 대기 중입니다. 방화벽이 IPv4만 다루거나 IPv4만 필터링하는 클라우드 방화벽에 의존한다면, IPv4 측은 차단된 것처럼 보여도 모든 서비스가 IPv6를 통해 인터넷 전체에서 접근 가능해집니다. curl으로 포트를 테스트하여 연결 거부를 확인하면 안전하다고 착각할 수 있습니다. 하지만 공격자는 동일한 포트에 IPv6로 접속하여 침투할 수 있습니다.

이 가이드는 일반적인 Ubuntu 24.04 VPS에서 이러한 보안 공백이 발생하는 원인과 노출된 항목을 확인하는 방법, 그리고 이를 해결하는 방법을 설명합니다. UFW는 원인이 아닙니다. 최신 Ubuntu 설치 환경에서 UFW는 이미 IPv6를 처리합니다. 보안 노출은 UFW 주변의 계층과 사용자가 인지하지 못한 서비스의 대기 상태에서 발생합니다.

VPS에 IPv6가 기본적으로 할당되는 이유

오늘날 거의 모든 VPS는 IPv4 주소와 함께 공인 IPv6 주소를 제공하며, 종종 전체 /64를 포함합니다. 다음 명령어로 확인하십시오:

ip -6 addr show scope global
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
    inet6 2001:db8:2a::1/64 scope global

해당 2001:db8:2a::1는 IPv4 주소와 마찬가지로 인터넷 어디에서나 라우팅이 가능합니다. 현재 어떤 서비스가 대기 중인지 확인하십시오:

sudo ss -tlnp
State   Recv-Q  Local Address:Port   Process
LISTEN  0       0.0.0.0:22           sshd
LISTEN  0       [::]:22              sshd
LISTEN  0       127.0.0.1:5432       postgres
LISTEN  0       [::]:8080            docker-proxy

Local Address 열을 주의 깊게 보십시오. 0.0.0.0:22은 "모든 IPv4 주소에서 대기"를 의미합니다. [::]:22은 "모든 IPv6 주소에서 대기"를 의미합니다. 127.0.0.1:5432은 loopback에 바인딩되어 공인 주소가 아니므로 Postgres 라인은 안전합니다. 두 개의 [::] 라인은 IPv6를 통해 인터넷 전체의 요청에 응답하며, docker-proxy 라인은 실행 중임을 잊기 쉬운 형태입니다.

대부분의 데몬은 기본적으로 ::에 바인딩됩니다. Linux에서 :: 소켓은 일반적으로 IPv4도 함께 수용하기 때문입니다. 따라서 새 서버의 기본 설정은 "모든 위치의 두 스택 모두에서 응답"하는 상태입니다. 방화벽만이 이를 차단할 수 있는 유일한 수단입니다. 따라서 한 가지 스택만 감시하는 방화벽은 심각한 보안 문제를 일으킬 수 있습니다.

IPv6 공백이 발생하는 실제 원인

네 가지 일반적인 원인이 있습니다. 특정 서버에서 이 중 하나 또는 여러 개가 동시에 발생할 수 있습니다.

1. IPv4만 필터링하는 클라우드 방화벽. 많은 제공업체의 방화벽과 security-group 제품은 IPv4를 중심으로 개발되었습니다. 따라서 IPv6를 무시하거나, 사용자가 직접 별도의 IPv6 규칙을 추가해야 합니다. 만약 제공업체 대시보드의 방화벽이 유일한 방화벽인데 IPv6를 지원하지 않는다면, IPv4의 port 22 설정과 관계없이 [::] 서비스가 외부에 노출됩니다. 제공업체의 방화벽 문서를 확인하여 IPv6 관련 내용을 반드시 확인하십시오.

2. ip6tables가 없는 수동 iptables 설정. iptables 명령은 IPv4 테이블만 수정합니다. IPv6는 별도의 명령인 ip6tables을 사용하며, 자체적인 규칙을 가집니다. 만약 iptables -A INPUT ... 라인으로만 구성된 방화벽 스크립트를 작성하고 그에 대응하는 ip6tables 규칙을 작성하지 않았다면, IPv6 방화벽은 비어 있는 상태가 됩니다. 기본 정책이 ACCEPT인 빈 INPUT chain은 모든 접속을 허용합니다:

sudo ip6tables -L INPUT -n
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

위 출력 결과가 한 화면에 나타난다면 함정에 빠진 것입니다. IPv4는 필터링되지만, IPv6는 모든 접속을 허용하고 있는 상태입니다.

3. 방화벽을 우회하여 포트를 공개하는 Docker. docker run -p 8080:80를 실행하면 Docker는 UFW보다 우선순위가 높은 자체 규칙을 삽입합니다. 따라서 ufw status에서 해당 포트를 차단하더라도 공개된 포트는 접속이 가능하며, 최신 Docker에서는 IPv6에도 동일하게 적용됩니다. Docker가 UFW를 우회하는 이유와 컨테이너 포트를 올바르게 필터링하는 방법에서 해당 메커니즘과 해결 방법을 설명합니다. 공개 포트 선언 방식은 VPS에서 Docker Compose를 사용하는 기본 방법을 참조하십시오.

4. IPv6가 비활성화된 UFW. UFW는 IPv6를 처리할 수 있지만, 설정이 활성화되어 있어야 합니다. 설정을 확인하십시오:

grep IPV6 /etc/default/ufw

최신 Ubuntu는 IPV6=yes을 제공하므로 UFW가 두 스택 모두에 규칙을 적용합니다. 만약 오래된 이미지나 가이드에서 IPV6=no를 확인했다면, 작성된 모든 UFW 규칙은 IPv4에만 적용되며 IPv6는 관리되지 않는 상태입니다.

노출된 항목을 정확히 확인하십시오

추측하지 마십시오. 외부에서 측정하십시오. 먼저 리스너 목록을 나열하고 ::에 바인딩된 모든 항목을 확인하십시오:

sudo ss -tlnp | grep '::'

그 다음, 다른 머신에서 서버의 public IPv6 주소로 접속하여 닫혀 있다고 생각되는 port를 시도하십시오:

curl -6 -v http://[2001:db8:2a::1]:8080/

만약 페이지나 banner가 반환된다면, 해당 port는 IPv6에서 열려 있는 상태입니다. 닫힌 port는 Connection refused 또는 timeout을 반환합니다. 전체 현황을 파악하려면, 서버 외부에서 nmap을 사용하여 IPv6 주소를 스캔하십시오:

nmap -6 2001:db8:2a::1

nmap이 IPv6에서 open이라고 보고하는 모든 port는 IPv4 스캔 결과와 상관없이 인터넷 전체에서 접근 가능한 port입니다. IPv4와 IPv6 스캔 결과를 나란히 비교하는 것이 차이점을 찾는 가장 빠른 방법입니다. -6에서는 open 상태이지만 IPv4에서 closed 상태인 항목은 방화벽 설정이 누락된 service입니다.

Close the gap

UFW가 두 스택 모두를 커버하도록 설정하고, 기본 정책을 deny로 설정하십시오. 변경 사항을 확인한 후, 기본 inbound 정책을 deny로 설정하고 필요한 서비스만 허용하십시오.

sudo sed -i 's/^IPV6=no/IPV6=yes/' /etc/default/ufw
sudo ufw default deny incoming
sudo ufw allow 22/tcp
sudo ufw enable
sudo ufw status verbose

IPV6=yes를 변경할 당시 UFW가 이미 활성화 상태였다면, sudo ufw reload을 실행하기 전까지는 변경 사항이 적용되지 않습니다.

ufw status은 각 규칙을 일반 형식과 (v6) 접미사가 붙은 형식으로 두 번 나열합니다. (v6) 라인이 보이면 UFW가 IPv6를 필터링하고 있는 것입니다.

22/tcp                     ALLOW IN    Anywhere
22/tcp (v6)                ALLOW IN    Anywhere (v6)

iptables를 수동으로 관리한다면, 모든 규칙을 ip6tables에도 동일하게 적용하십시오. 또는 nftables로 전환하십시오. nftables의 inet 테이블은 IPv4와 IPv6를 한 곳에서 처리하므로 이러한 실수를 방지할 수 있습니다. 규칙을 직접 작성하는 경우, 단일 nftables inet 필터 테이블을 사용하는 것이 가장 깔끔한 해결책입니다.

공개할 필요가 없는 서비스는 loopback에 바인딩하십시오. 데이터베이스, 관리 패널 또는 메트릭 엔드포인트는 공개 주소가 거의 필요하지 않습니다. 127.0.0.1::1에 바인딩하여 라우팅 가능한 주소에서 리스닝하지 않도록 설정하십시오. Postgres의 경우 listen_addresses = 'localhost'를 설정하십시오. 앱 서버의 경우 127.0.0.1에 바인딩하고 앞에 리버스 프록시를 배치하십시오. 리스너 자체를 차단하는 것이 방화벽으로 막는 것보다 확실합니다. 도달할 수 있는 대상 자체가 없어지기 때문입니다.

Docker의 공개 포트를 보호하는 용도로 UFW를 신뢰하지 마십시오. 컨테이너 포트를 모든 인터페이스가 아닌 특정 주소(예: -p 127.0.0.1:8080:80)에 공개하여, 호스트와 의도적으로 프록시를 설정한 대상에서만 포트에 접근할 수 있도록 하십시오. 컨테이너를 반드시 공개해야 하는 경우에는 Traefik 리버스 프록시 뒤에 배치하고, 각 앱이 아닌 프록시만 공개하십시오.

클라우드 제공업체의 방화벽에 IPv6 규칙을 추가하십시오. 그렇지 않다면 해당 방화벽이 IPv6를 관리하지 않는다는 점을 인지하고, 호스트의 UFW 또는 nftables가 해당 역할을 수행하도록 하십시오.

실제 차단 여부를 확인하십시오

변경 사항을 적용한 후 동일한 외부 테스트를 다시 실행하십시오:

curl -6 -v http://[2001:db8:2a::1]:8080/
nmap -6 2001:db8:2a::1

이전에 응답했던 포트는 이제 연결을 거부하거나 타임아웃이 발생해야 하며, nmap은 해당 포트가 filtered 또는 closed 상태라고 보고해야 합니다. 포트가 여전히 open 상태라면 위에서 언급한 네 가지 원인을 다시 점검하십시오. 규칙이 적용되지 않은 채 ::에 여전히 바인딩된 서비스, UFW보다 앞선 Docker 규칙, 또는 IPv6를 아예 인식하지 못하는 제공업체 방화벽이 원인일 수 있습니다.

민감한 서비스를 공용 인터넷에서 완전히 분리하는 것이 더욱 안전합니다. SSH 및 관리 패널을 WireGuard VPN 뒤로 배치하고 포트를 방화벽으로 보호하여 터널을 통해서만 응답하도록 설정하십시오. 이렇게 하면 IPv6 노출 문제가 해당 서비스에는 적용되지 않습니다. 공용 상태로 남은 서비스에 대한 brute-force 스캔을 지연시키려면, 기본 deny 설정이 된 방화벽 위에 SSH 앞단에 Fail2ban 배치를 추가하십시오.

포트 개념 자체가 생소하다면, 포트의 정의와 서비스 리스닝 방식을 먼저 읽어보시기 바랍니다.

FAQ

UFW는 기본적으로 IPv6를 차단합니까?

최신 Ubuntu 24.04 설치 환경에서는 그렇습니다. UFW는 /etc/default/ufw에서 IPV6=yes를 읽어 각 규칙을 IPv4와 IPv6 모두에 적용합니다. ufw status(v6) 접미사가 붙은 IPv6 규칙을 보여줍니다. IPV6=no(오래된 이미지 또는 오래된 튜토리얼 사용 시), IPv4만 필터링하는 제공업체 방화벽을 사용하는 경우, 또는 Docker가 UFW를 거치지 않고 포트를 공개하는 경우 문제가 발생합니다. grep IPV6 /etc/default/ufw로 설정을 확인하십시오.

VPS가 IPv6로 무엇을 노출하고 있는지 어떻게 확인합니까?

sudo ss -tlnp를 실행하고 로컬 주소가 [::]으로 시작하는 모든 리스너를 확인하십시오. 이는 모든 IPv6 인터페이스에서 응답함을 의미합니다. 그 다음, 다른 기기에서 curl -6 -v http://[YOUR:IPV6::ADDR]:PORT/을 사용하여 서버의 공용 IPv6 주소에 직접 테스트하거나 nmap -6 YOUR:IPV6::ADDR으로 스캔하십시오. IPv6 스캔에서는 열려 있지만 IPv4에서는 닫혀 있는 포트가 보안 취약점입니다.

UFW에서 차단되었다고 나오는데 왜 Docker 컨테이너 포트에 접속할 수 있습니까?

-p로 포트를 공개하면 Docker가 UFW보다 우선하여 자체 방화벽 규칙을 삽입합니다. 따라서 ufw status에 거부(denied)로 표시되어도 공개된 포트에 접속할 수 있습니다. 이는 IPv4에서 발생하며, Docker의 IPv6 지원이 활성화된 경우 IPv6에서도 발생합니다. -p 127.0.0.1:8080:80과 같은 특정 주소로 공개하거나, 컨테이너를 리버스 프록시 뒤에 두고 프록시 포트만 공개하십시오.

IPv4 방화벽이 완벽하다면 IPv6 방화벽이 여전히 필요합니까?

네, 필요합니다. IPv4와 IPv6는 방화벽 규칙이 서로 다른 별개의 네트워크 스택입니다. 완벽한 IPv4 규칙 세트는 IPv6 트래픽에 아무런 영향을 주지 않습니다. VPS에 공용 IPv6 주소가 있다면(대부분의 VPS가 해당됨), ::에서 리스닝 중인 모든 서비스는 IPv6 방화벽 규칙이나 루프백 바인딩이 차단하기 전까지 IPv6를 통해 접속이 가능합니다.

서비스를 IPv4 전용 또는 localhost 전용으로 리스닝하게 하려면 어떻게 합니까?

서비스 자체 설정에서 바인드 주소(bind address)를 설정하십시오. IPv4 루프백 전용은 127.0.0.1에, IPv6 리스너 없이 모든 IPv4 주소에 바인드하려면 0.0.0.0를 사용하십시오. Postgres는 listen_addresses를 사용하고, SSH는 ListenAddress를 사용하며, 대부분의 앱 서버는 host 또는 bind 플래그를 제공합니다. sudo ss -tlnp으로 결과를 확인하고 Local Address[::]이 더 이상 나타나지 않는지 확인하십시오.