포트의 위험은 그 뒤에서 응답하는 것만큼이다
포트가 열려 있다는 말의 뜻은 하나입니다. 테스트한 곳에서 오는 TCP 연결을 받아 주는 프로그램이 있다는 것입니다. 그게 문제인지는 그 프로그램에 대한 세 가지 질문에 달려 있습니다.
- 인터넷 전체에서 접근할 수 있어야 하나? 공개 웹사이트는 그래야 합니다. 데이터베이스는 아닙니다.
- 인증을 요구하고, 그 인증이 강한가? 비밀번호 없는 Redis나 추측하기 쉬운 비밀번호의 RDP는 이름만 ‘인증’입니다.
- 패치돼 있나? 인터넷에 노출된 서비스의 원격 코드 실행 버그는 공개 후 며칠 안에 악용되는 일이 많고, 자동 스캐너는 IPv4 인터넷 전체를 쉬지 않고 훑습니다.
첫 번째 질문에서 걸리는 것은 나머지 두 질문의 답이 아무리 좋아도 인터넷에서 닫아야 합니다. 데이터를 지키는 데 비밀번호 하나나 패치 수준 하나에만 기대지 않는 것, 그게 심층 방어입니다.
인터넷에 열면 안 되는 포트
| 포트 | 서비스 | 노출되면 위험한 이유 |
|---|---|---|
| 23 | Telnet | 비밀번호가 평문으로 오갑니다. 노출된 Telnet은 대부분 기본 계정이 그대로인 저가 기기이고, Mirai 봇넷이 바로 이런 기기들로 만들어졌습니다. |
| 135, 139, 445 | Windows RPC, 파일 공유(SMB) | 웜처럼 퍼지는 익스플로잇의 표적입니다. 2017년 WannaCry가 SMB로 퍼졌습니다. SMB가 인터넷에서 닿아야 할 이유는 없습니다. |
| 3389 | 원격 데스크톱(RDP) | 비밀번호 대입이 끊이지 않고, 랜섬웨어 조직의 흔한 침투 경로입니다. 인증 전 단계의 심각한 버그도 나온 적이 있습니다(BlueKeep, CVE-2019-0708). |
| 5900 | VNC | 비밀번호가 약하거나 없는 경우가 많고, 패치 안 된 빌드가 흔합니다. |
| 1433, 3306, 5432 | SQL Server, MySQL, PostgreSQL | 쉬지 않고 대입 공격을 받습니다. 비밀번호가 약하면 데이터를 잃고, DB 로그인이 서버의 코드 실행으로 이어지는 경우도 있습니다. |
| 6379 | Redis | 예전 설정은 비밀번호 없이 돌았습니다. 접근한 공격자는 모든 데이터를 읽고 바꿀 수 있고, 예전 버전에서는 Redis 자체 명령으로 서버에 파일을 쓸 수도 있었습니다. |
| 27017 | MongoDB | 3.6 이전 버전은 기본으로 모든 인터페이스에서 들었습니다. 노출된 데이터베이스를 지우고 몸값을 요구하는 메모를 남기는 공격이 여러 차례 휩쓸었습니다. |
| 9200 | Elasticsearch | 8.0 이전에는 보안 기능이 기본으로 꺼져 있었습니다. 노출된 클러스터에서 대량의 개인정보가 유출되곤 했습니다. |
| 2375 | TLS 없는 Docker API | Docker 데몬을 완전히 제어할 수 있고, 이는 사실상 서버의 root 권한입니다. |
| 11211 | Memcached | 인증이 없고, UDP 모드가 아주 큰 증폭 공격에 악용됐습니다. |
다른 포트의 관리 화면도 같은 목록에 들어갑니다. Jenkins, Kibana, phpMyAdmin, Tomcat manager, 공유기 관리 페이지처럼 8080이나 8443에 자주 올라가는 것들입니다. 웹 페이지이긴 하지만 낯선 사람을 위한 것이 아닙니다.
조건부로 공개해도 되는 포트
- 22 (SSH). 키 로그인만 허용하고(
sshd_config의PasswordAuthentication no) 패치돼 있다면 공개해도 됩니다. 내 주소나 VPN에서만 허용하면 더 좋습니다. - 21 (FTP). 로그인과 데이터가 평문입니다. SSH 위의 SFTP로 바꾸세요. FTP를 꼭 유지해야 한다면 FTPS를 쓰고 익명 로그인을 끄세요.
- 25 (SMTP). 내 도메인의 메일을 받는 메일 서버에는 필요합니다. 다른 서버에는 필요 없습니다. 노출 여부와 상관없이 아무 메일이나 대신 보내 주는 오픈 릴레이가 아닌지 확인하세요.
- 53 (DNS). 내 존의 권한 서버라면 필요합니다. 인터넷 전체의 재귀 질의에 답하는 리졸버는 증폭 공격에 악용되므로, 재귀는 내 네트워크로만 제한하세요.
- 110, 143 (POP3, IMAP). 쓸 수는 있지만 로그인이 암호화되지 않은 채 오갈 수 있습니다. 처음부터 TLS인 993·995를 쓰고 평문 포트는 끄세요.
- 80, 443, 8080, 8443 (웹). 웹 서버라면 열려 있는 게 당연합니다. 위험은 무엇을 서비스하느냐에 있습니다. 디버그 페이지, 관리 화면, 기본 설치 페이지.
실제로 무엇이 듣고 있는지 찾기
스캔보다 서버에서 시작하세요. 듣고 있는 모든 소켓과 프로세스가 나옵니다.
sudo ss -ltnp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=812,fd=7))
LISTEN 0 151 127.0.0.1:3306 0.0.0.0:* users:(("mysqld",pid=640,fd=21))
LISTEN 0 511 0.0.0.0:6379 0.0.0.0:* users:(("redis-server",pid=702,fd=6))
읽어야 할 칸은 Local Address입니다. 127.0.0.1이나 [::1]이면 그 기계 자신만 연결할 수 있습니다. 0.0.0.0, [::], *이면 모든 인터페이스이고, 서비스와 인터넷 사이에 남은 것은 방화벽뿐입니다. 위 예시에서 MySQL은 괜찮고 Redis가 문제입니다.
UDP 서비스는 -u를 붙여(ss -lunp) 보세요. Windows에서는 PowerShell의 Get-NetTCPConnection -State Listen이나 관리자 권한 명령 프롬프트의 netstat -abno를 쓰면 됩니다.
바깥에서 확인하기
서버에서 본 것은 노출될 수 있는 것이고, 바깥 점검이 보여 주는 것은 실제로 노출된 것입니다.
포트 점검은 내 네트워크 바깥에서 흔한 TCP 포트 15개를 테스트합니다. 21, 22, 25, 53, 80, 110, 143, 443, 993, 995, 3306, 3389, 5432, 8080, 8443입니다. 이 중 21, 25, 3306, 3389, 5432가 열려 있으면 민감한 포트로 표시합니다. 위 표의 다른 포트, 예를 들어 445, 6379, 27017은 기본 목록에 없습니다. host:port처럼 넣어서 하나씩 확인하세요.
결과를 바르게 읽기 위한 한계도 적어 둡니다. TCP만 보므로 SNMP나 DNS 리졸버 같은 UDP 서비스는 빠집니다. 호스트명이나 IPv4 주소를 받으므로 IPv6 주소는 따로 테스트하세요. IPv6 방화벽 규칙은 별도 규칙 세트라 잊히기 쉽습니다. 3초를 기다리고, 무응답과 거부를 똑같이 취급합니다. 그리고 Cloudflare 네트워크에 있는 호스트는 테스트할 수 없습니다. 웹 트래픽에는 상관없습니다. Cloudflare는 웹 포트만 전달하니까요. 대신 그 뒤의 원 서버 IP를 테스트하세요.
내가 관리하는 다른 기계에서 전체를 훑으려면 nmap을 씁니다.
nmap -Pn -p 21-23,25,53,80,110,135,139,143,443,445,1433,2375,3306,3389,5432,5900,6379,8080,8443,9200,11211,27017 203.0.113.10
내 소유이거나 테스트 허가를 받은 주소만 스캔하세요.
서브도메인에 노출된 관리자 페이지는 열린 포트와는 다른 문제입니다. 관리자 페이지 노출 점검이 도메인의 서브도메인들에서 그런 페이지를 찾아 줍니다.
닫는 방법, 좋은 순서대로
1. 서비스를 localhost에 바인드
기계 바깥에서 그 서비스가 필요 없다면 아예 네트워크에서 듣지 않게 하세요. 나중에 방화벽 실수가 있어도 노출되지 않으므로 가장 강한 해결책입니다.
# MySQL / MariaDB, 예: /etc/mysql/mysql.conf.d/mysqld.cnf
bind-address = 127.0.0.1
# PostgreSQL, postgresql.conf
listen_addresses = 'localhost'
# Redis, redis.conf
bind 127.0.0.1 -::1
protected-mode yes
# MongoDB, mongod.conf
net:
bindIp: 127.0.0.1
다른 기계의 애플리케이션 서버가 DB에 접근해야 한다면 사설 인터페이스(10.x, 172.16-31.x, 192.168.x)에만 바인드하고, 공인 인터페이스에는 절대 하지 마세요. 서비스를 재시작하고 ss -ltnp로 확인하세요.
2. 기본 거부 방화벽
서비스하는 것만 허용하고 나머지는 거부합니다.
sudo ufw default deny incoming
sudo ufw allow 443/tcp
sudo ufw allow 80/tcp
sudo ufw allow from 198.51.100.7 to any port 22 proto tcp # 내 관리용 주소
sudo ufw enable
SSH 규칙을 enable보다 먼저 넣으세요. 안 그러면 내가 갇힙니다.
3. Docker를 조심하기
“방화벽에서 막았는데요”라는 당황스러운 상황 뒤에 흔히 있는 함정입니다. docker run -p 6379:6379 redis는 Docker 자체의 iptables 규칙으로 포트를 게시합니다. 컨테이너로 가는 트래픽은 ufw가 관리하는 INPUT 규칙에 닿기 전에 NAT되어 전달됩니다. 방화벽은 거부라고 말하는데 컨테이너는 인터넷에서 그대로 닿습니다. ufw가 아닌 다른 서버 방화벽도 같은 사각지대가 있을 수 있으니, 규칙 목록을 믿지 말고 바깥에서 테스트하세요.
그 호스트만 쓰는 포트라면 localhost에만 게시하세요. -p 127.0.0.1:6379:6379, Compose라면 "127.0.0.1:6379:6379". 더 좋은 방법은 DB 포트를 아예 게시하지 않고 컨테이너끼리 Docker 네트워크로 통신하게 하는 것입니다. 게시한 포트를 걸러야 한다면 Docker가 사용자용으로 남겨 둔 DOCKER-USER 체인에 규칙을 넣으세요.
공식 Redis 이미지는 컨테이너 안에서는 정상 연결까지 막을 수 있다는 이유로 protected mode를 끈 채 배포돼 왔다는 점도 기억하세요. 비밀번호 없이 게시된 Redis 컨테이너는 완전히 열려 있습니다.
4. 클라우드 보안 그룹
AWS, Google Cloud, Azure 등에서는 보안 그룹이 기계 앞의 방화벽입니다. 22, 3389, DB 포트에 0.0.0.0/0을 허용한 규칙이 전형적인 실수이고, 잠깐 테스트하려고 열었다가 남겨 둔 경우가 많습니다. 알려진 주소로 제한하거나, 규칙을 지우고 클라우드 자체의 접속 경로를 쓰세요. AWS Systems Manager Session Manager와 Google Cloud의 IAP TCP 전달은 들어오는 포트를 하나도 열지 않고 인스턴스에 접속하게 해 줍니다.
5. 관리용 서비스는 VPN, 배스천, 터널을 거쳐
원격으로 꼭 써야 하는 서비스라면 앞에 인증된 입구 하나를 두세요.
가끔 DB에 접속하는 정도라면 SSH 터널로 충분합니다.
ssh -L 5432:127.0.0.1:5432 admin@203.0.113.10
# 그다음 클라이언트를 localhost:5432로 연결
팀이라면 WireGuard 같은 VPN(또는 그 위에 만든 관리형 메시 VPN)으로 관리 포트를 구성원만 닿는 사설 주소에 두세요. RDP라면 Microsoft의 Remote Desktop Gateway가 HTTPS 위에서 같은 역할을 합니다. 어느 쪽이든 인터넷에는 서비스 열두 개 대신 단단히 잠근 입구 하나만 보이게 됩니다.
6. SSH나 RDP를 계속 열어 둬야 한다면
SSH는 키 로그인만 허용하고 root 로그인을 끄세요(PermitRootLogin no). RDP는 네트워크 수준 인증(NLA)을 켜고 계정 잠금 정책을 적용하세요. fail2ban 같은 도구로 대입 속도를 늦추세요. 포트 번호를 바꾸는 것은 로그 소음을 줄일 뿐이지 접근 통제가 아닙니다.
집 네트워크와 공유기
집 IP를 스캔하면 아무도 열었던 기억이 없는 포트가 자주 나옵니다.
- UPnP는 네트워크 안의 기기가 스스로 공유기 포트를 열게 해 줍니다. 카메라, 게임기, NAS가 그렇게 합니다. 필요하지 않다면 UPnP를 끄고 공유기의 포트 포워딩 목록을 점검하세요.
- 80, 443, 8080, 8443에 열린 공유기 원격 관리. 끄고, 공유기는 내부에서 관리하세요.
- NAS나 카메라 관리 페이지로 가는 예전 포트 포워딩. 지우고 제조사 앱이나 VPN을 쓰세요.
예상하지 못한 포트를 찾았다면
먼저 프로세스를 확인하세요. sudo ss -ltnp 'sport = :포트' 또는 sudo lsof -i :포트. 설치해 놓고 잊은 서비스라면 위 방법 중 하나로 닫으면 됩니다.
한동안 닿을 수 있었고 데이터나 로그인을 담고 있었다면, 다른 누군가가 이미 찾았다고 가정하세요. 서비스 로그에서 모르는 연결을 찾고, 비밀번호와 키를 교체하고, 데이터가 바뀌지 않았는지 확인하세요. 설명할 수 없는 열린 포트가 있다면 기계 전체를 꼼꼼히 볼 가치가 있습니다.
다시 확인하기
바꿀 때마다 IPv4와 IPv6로 바깥 점검을 다시 하고, 서버에서 ss -ltnp를 돌리세요. 그리고 습관으로 만드세요. 새 컨테이너, 테스트 설치, ‘잠깐만’ 열어 둔 보안 그룹 규칙이 이런 포트가 열리는 흔한 경로입니다. 내가 관리하는 기계에서 nmap을 주기적으로 돌리고 지난 결과와 비교하면 일찍 잡을 수 있습니다.
내 도메인에서 바로 확인
무료, 가입 불필요. 이 가이드가 다루는 항목을 바로 검사하고 조치 방법을 알려드립니다.