DEV Community

바람의평온
바람의평온

Posted on

내 MySQL 3306 포트가 인터넷에 통째로 열려 있었다: bind 0.0.0.0의 위험과 SSH 터널로 안전하게 사용하기

내 MySQL 3306 포트가 인터넷에 통째로 열려 있었다: bind 0.0.0.0의 위험과 SSH 터널로 안전하게 사용하기

어느 날 갑자기, 집에서 쓰던 홈캠의 포트 개방 상태를 점검하다가, 문득 제가 운영 중인 웹서버의 MySQL 데이터베이스 포트가 어떻게 되어 있는지 궁금해졌습니다. 처음에는 뭐 문제 없을 거라고 생각했죠. 왜냐하면 제 Next.js 앱이 같은 서버 안에서 DB에 잘 붙어서 돌아가고 있었거든요. '잘 도니까 괜찮다'는 이 안일한 생각이 큰 함정이라는 걸 그때는 미처 몰랐습니다. 외부에서 telnet으로 제 서버의 3306 포트에 접속 시도를 해봤는데, 세상에, MySQL 서버 버전 배너가 그대로 노출되는 걸 보고 정말 깜짝 놀랐습니다. 제 DB 포트가 전 세계 어디서나 접근 가능한 상태였다는 사실을 깨달았을 때의 아찔함이란… 저처럼 첫 아이를 키우는 아빠가 모든 게 처음이듯, 개발에서도 이렇게 예상치 못한 부분에서 허둥댈 때가 있네요. 이 글은 제가 직접 겪고 해결한 이 경험을 다른 개발자분들과 나누고자 작성했습니다.

이 글에서 다루는 것

  • MySQL bind-address 0.0.0.0 설정이 외부 노출로 이어질 수 있는 위험성
  • 클라우드 환경에서 DB 포트와 보안 그룹 설정의 중요성
  • telnet과 ss 명령어를 활용하여 DB 포트 개방 여부를 확인하는 방법
  • MySQL을 로컬에서만 접근하도록 안전하게 설정하는 방법
  • 외부에서 DB에 안전하게 접속하기 위한 SSH 터널 활용법

대상: DB를 같은 서버의 앱에서만 쓰는데 무심코 외부에 열어둔 1인 개발자
난이도: 초급

예상치 못하게 마주친 MySQL 3306 포트의 '열린 문'

저는 주로 웹 서비스 개발에 집중하다 보니, 인프라나 보안 쪽은 '잘 돌아가면 됐지' 하는 마음이 컸던 것 같습니다. 그러다 어느 날, 다른 장비의 포트 점검을 하던 중 문득 제 웹서버의 MySQL 포트도 한번 확인해볼까 하는 생각이 들었습니다. 별 기대 없이 다른 PC에서 제 서버IP의 3306 포트로 telnet 접속을 시도해봤습니다. 보통은 연결이 거부되거나 아무런 응답이 없어야 하는데, 화면에 MySQL 서버의 버전 정보가 그대로 뜨는 것을 보고 깜짝 놀랐습니다. 이 말은 즉, 전 세계 어디에서든 제 서버의 MySQL 3306 포트에 접속을 시도할 수 있고, 최소한 제 DB 서버가 무엇인지 공격자가 파악할 수 있다는 의미였습니다. 순간 머릿속이 하얘지면서 '이게 정말 사실인가?' 싶더군요. 아래는 제가 외부에서 접속 시도했을 때 보였던 것과 비슷한 상황을 보여주는 명령어입니다. telnet 서버IP 3306 # 이 명령어를 실행했을 때 MySQL 버전 정보가 보인다면 외부에 노출된 것입니다.

bind-address 0.0.0.0과 클라우드 보안 그룹: 위험한 조합

왜 이런 일이 벌어졌을까요? 저는 이렇게 찾아봤는데, 원인은 크게 두 가지가 겹쳐서 발생한 것이었습니다. 첫째, MySQL 설정 파일(mysqld.cnf)에서 bind-address 값이 0.0.0.0으로 되어 있었더군요. 이 설정은 MySQL 서버가 모든 네트워크 인터페이스로부터의 접속을 허용하겠다는 의미입니다. 즉, 로컬 네트워크뿐만 아니라 외부 인터넷을 통한 접속 시도까지 모두 받아들이겠다는 뜻이죠. 저는 로컬 Next.js 앱이 127.0.0.1(localhost)로 DB에 접속하니 당연히 로컬에서만 접근 가능한 줄 알았습니다. 하지만 MySQL은 0.0.0.0으로 열려있으니, 앱은 127.0.0.1로 잘 붙지만 DB는 전 세계에 문을 활짝 열어둔 꼴이 된 겁니다. 둘째, 제가 사용하는 클라우드 인스턴스의 보안 그룹(방화벽)에서 3306 포트가 막혀 있지 않았습니다. 보통 클라우드 서비스는 기본적으로 모든 포트를 막아두고 필요한 포트만 열도록 권장하는데, 제가 초기 세팅할 때 이 부분을 놓쳤던 것 같습니다. 이 두 가지가 합쳐져서 제 DB가 인터넷에 완전히 노출되었던 거죠. 특히 버전 배너 노출은 공격자가 어떤 MySQL 버전을 사용하는지 알 수 있게 하여, 해당 버전에 알려진 취약점을 노리기 쉽게 만드는 안 좋은 정보가 될 수 있습니다.

MySQL 로컬 바인딩과 SSH 터널로 보안 강화하기

문제를 확인했으니 이제 해결해야죠. 가장 먼저 한 일은 MySQL이 외부 접속을 아예 받지 않도록 설정하는 것이었습니다. MySQL 설정 파일인 /etc/mysql/mysql.conf.d/mysqld.cnf 파일을 열어 bind-address 설정을 수정했습니다. 아래처럼 127.0.0.1로 바꾸는 것이죠. # /etc/mysql/mysql.conf.d/mysqld.cnf [mysqld] bind-address = 127.0.0.1 이 설정은 MySQL 서버가 로컬 호스트(127.0.0.1)에서만 접속을 받도록 제한합니다. 이렇게 하면 같은 서버 안에서 돌아가는 제 Next.js 앱은 localhost로 DB에 계속 잘 붙을 수 있고, 외부에서는 아예 접속이 불가능해집니다. 설정을 바꾼 뒤에는 MySQL 서비스를 꼭 재시작해야 변경 사항이 적용됩니다. 그리고 외부에서 DBeaver 같은 GUI 툴로 DB 작업을 해야 할 때가 있는데, 이때는 3306 포트를 직접 여는 대신 SSH 터널을 경유하도록 했습니다. SSH 터널은 SSH 포트(기본 22번)만 열어두고 그 안으로 DB 트래픽을 안전하게 통과시키는 방식입니다. 이 방법은 외부 노출을 최소화하면서도 필요한 작업을 할 수 있게 해줍니다. 이 방법이 '정답'이라고 제가 감히 말씀드리기는 어렵겠지만, 제 환경에서는 가장 안전하고 효과적인 해결책으로 보였습니다.

변경 사항 확인과 안전한 DB 운영의 시작

설정 변경 후에는 정말 잘 적용되었는지 확인하는 것이 중요합니다. 먼저 외부에서 다시 telnet으로 3306 포트에 접속 시도를 해봤습니다. 이번에는 'Connection refused' 메시지가 뜨면서 접속이 거부되는 것을 확인할 수 있었습니다. 아, 이제야 좀 안심이 되더군요. 그리고 서버 내부에서 MySQL이 어떤 주소로 리슨하고 있는지 확인하기 위해 아래 명령어를 사용했습니다. sudo ss -tlnp | grep 3306 이 명령어를 실행했을 때 출력 결과에 127.0.0.1:3306이라고 보인다면, MySQL이 로컬 호스트에서만 접속을 받고 있다는 뜻입니다. 제 Next.js 앱도 여전히 정상적으로 DB에 접속하여 작동하는 것을 확인했습니다. DBeaver 같은 외부 DB 툴은 SSH 터널 설정을 통해 문제없이 DB에 연결할 수 있었습니다. 이번 경험을 통해 '앱이 잘 도니까 괜찮다'는 생각이 얼마나 위험한지 뼈저리게 느꼈습니다. 로컬 앱은 127.0.0.1로 잘 붙어 돌면서도 DB는 0.0.0.0으로 열려 있을 수 있다는 점을 꼭 기억해야겠습니다. 외부에서 정말 접속해야 하는 경우가 아니라면, DB는 무조건 localhost 바인딩 + SSH 터널 조합이 가장 안전한 방법 중 하나라고 저는 생각합니다. 저처럼 모든 게 처음인 아빠가 아이를 키우며 하나씩 배워가듯, 개발도 이렇게 실수를 통해 배우고 성장하는 것 같네요.

마무리

이렇게 제 웹서버의 MySQL 3306 포트가 전 세계에 활짝 열려 있었던 아찔한 경험과 그 해결 과정을 공유해 드렸습니다. '잘 되는데 뭐' 하고 넘어갈 수 있었던 부분을 꼼꼼히 들여다보니, 생각지도 못한 보안 취약점을 발견하게 되었네요. 개발자로서 이런 부분을 놓치지 않고 챙기는 것이 얼마나 중요한지 다시 한번 깨닫는 계기가 되었습니다. 혹시 이 글을 읽으시는 분들도 자신의 DB 포트가 어떻게 설정되어 있는지 한 번쯤 점검해보시길 바랍니다. 작은 관심이 큰 보안 사고를 막을 수 있을 거라 생각합니다. 저의 허둥지둥 기록이 다른 분들께 조금이나마 도움이 되었으면 좋겠네요.

Top comments (0)