1. "자물쇠만 달면 끝인 줄 알았다"
HTTPS 인증서(SSL)를 샀습니다. Nginx 설정 파일에 Listen 443을 적었습니다.
브라우저 주소창에 초록색 자물쇠가 떴습니다.
"아, 이제 우리 사이트는 안전하구나! 고객님들의 소중한 정보를 훌륭하게 지켜냈어."
저는 발을 뻗고 잤습니다. 하지만 해커들은 잠들지 않더군요. 어느 보안 세미나에서 충격적인 시연을 봤습니다. 공용 와이파이(Starbucks WiFi)에서 해커가 제 사이트의 로그인 정보를 가로채는 데 걸린 시간은 단 1초였습니다. 분명히 HTTPS를 적용했는데 말이죠.
범인은 바로 "http://로 접속하는 첫 순간"이었습니다.
2. 리다이렉트의 함정 - 0.1초의 빈틈
사용자들은 주소창에 https://naver.com이라고 꼼꼼하게 치지 않습니다.
그냥 naver.com이라고 치죠. 귀찮으니까요.
그러면 브라우저는 기본적으로 http://naver.com으로 접속을 시도합니다. (요즘 크롬은 https부터 시도하기도 하지만, 아직 완벽하지 않습니다.)
서버는 당연히 이렇게 응답합니다.
"어? HTTP로 오셨네요? 보안 때문에 HTTPS로 다시 오세요." (301 Moved Permanently)
사용자의 브라우저는 "아, 넵!" 하고 https://naver.com으로 다시 접속합니다.
이 과정은 0.1초 만에 일어나서 우리는 눈치채지 못합니다.
하지만 해커는 이 0.1초의 틈을 노립니다. 이를 SSL Stripping 공격이라고 합니다.
사용자가 처음 http://로 문을 두드리는 순간, 해커가 공유기(Router) 레벨에서 중간에 끼어들어 "내가 네이버 서버야" 하고 가짜 응답을 보내버리는 거죠.
사용자는 여전히 http:// 통신을 하고 있는데, 화면은 네이버 로그인 창과 똑같습니다. 아이디와 비번을 입력하면? 해커에게 고스란히 전송됩니다.
3. HSTS: 브라우저에게 문신을 새기다
이 문제를 해결하려면 브라우저가 처음부터 아예 http로 접속 시도조차 하지 않게 만들어야 합니다. 그게 바로 HSTS (HTTP Strict Transport Security)입니다.
HSTS는 서버가 브라우저에게 보내는 강력한 경고장입니다.
"야, 잘 들어. 앞으로 내 사이트에 올 때는 무조건, 절대적으로 HTTPS로만 와. 혹시라도 사용자가 실수로
http://라고 쳐도, 네가 알아서https://로 바꿔서 와. 알겠지?"
이 경고장을 받은 브라우저는 내부 메모장(HSTS Cache)에 이렇게 적어둡니다.
naver.com = 무조건 HTTPS
이제 사용자가 주소창에 http://naver.com을 쳐도, 브라우저는 서버에 요청을 보내기도 전에 자체적으로(Internal Redirect) https://로 바꿔버립니다.
네트워크를 타기 전에 바꿔버리니 해커가 끼어들 틈조차 없는 것이죠.
4. HSTS 헤더 해부하기
HSTS를 적용하는 건 생각보다 쉽습니다. 응답 헤더에 한 줄만 추가하면 됩니다.
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
하나씩 뜯어볼까요? 의미를 모르면 대형 사고를 칠 수 있습니다.
4.1. max-age=31536000
"이 규칙을 1년(31,536,000초) 동안 기억해."
브라우저는 이 기간 동안 절대 HTTP로 접속하지 않습니다.
보통 1년(31536000)이나 2년(63072000)으로 설정합니다.
4.2. includeSubDomains
"내 자식들(서브도메인)도 다 포함이야."
api.naver.com, blog.naver.com, test.naver.com 등 모든 서브도메인에 적용됩니다.
⚠️ 주의할 점: 혹시 사내 테스트 서버(dev.internal.naver.com)가 HTTPS 설정이 안 되어 있나요?
이 옵션을 켜는 순간, 그 테스트 서버도 접속이 불가능해집니다. 브라우저가 강제로 HTTPS로 연결하려고 하는데, 서버는 HTTPS를 안 받으니까 "연결 거부"가 뜨죠.
사내의 모든 서브도메인이 HTTPS를 지원하는지 반드시 확인하고 켜야 합니다.
4.3. preload
"전 세계 브라우저의 소스코드에 내 이름을 박제해 줘." 이건 좀 무서운 옵션입니다. 아래에서 자세히 설명하죠.
5. Preload List: 영원히 지워지지 않는 명단
HSTS에도 약점이 하나 있습니다. 바로 "최초의 접속(First Contact)"입니다. 브라우저가 경고장(HSTS 헤더)을 받으려면, 적어도 한 번은 접속을 해야 하잖아요? 그 첫 만남은 여전히 HTTP일 수 있고, 그때 공격당할 수 있습니다.
그래서 구글(Chrome) 팀이 "HSTS Preload List"라는 걸 만들었습니다. 이건 크롬 브라우저 설치 파일 안에 아예 하드코딩된 명단입니다.
"여기에 적힌 사이트들(facebook.com, naver.com, toss.im 등)은 태어날 때부터 HTTPS임. 토 달지 마."
여기에 내 사이트를 등록하면, 사용자가 태어나서 처음 내 사이트에 접속하더라도 브라우저는 HTTPS로 연결합니다. "맨 처음의 위험"까지 원천 봉쇄하는 거죠.
Preload 등록 방법
- 헤더에
preload옵션을 추가합니다. - hstspreload.org 사이트에 가서 내 도메인을 입력합니다.
- 조건을 만족하면 대기열에 올라가고, 몇 주 뒤 크롬/파이어폭스/사파리 업데이트에 반영됩니다.
☠️ 주의 - 돌아올 수 없는 강을 건너지 마라
Preload 등록은 신중, 또 신중해야 합니다. 만약 실수로 등록했는데, 나중에 사정이 생겨서 HTTP를 다시 써야 한다면? (예: 레거시 시스템 연동) 못 씁니다. 절대 못 씁니다.
Preload 명단에서 삭제 신청을 해도, 전 세계 사용자의 브라우저가 업데이트될 때까지 수개월이 걸립니다. 그동안 사용자들은 "접속 불가" 화면만 보게 됩니다. 회사가 망할 수도 있는 문제입니다.
6. 실제 - 환경별 적용 가이드
Nginx 설정
가장 일반적인 설정입니다. ssl_certificate 설정 아래에 추가하세요.
server {
listen 443 ssl http2;
server_name mysite.com;
ssl_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mysite.com/privkey.pem;
# 여기가 핵심! 항상(always) 헤더를 내려보냅니다.
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
}
Express (Node.js) 설정
Node.js 백엔드라면 helmet 미들웨어를 쓰는 게 가장 편하고 안전합니다.
const express = require('express');
const helmet = require('helmet');
const app = express();
// HSTS만 따로 설정하거나, helmet() 기본값 사용
app.use(helmet.hsts({
maxAge: 31536000, // 1년
includeSubDomains: true, // 서브도메인 포함
preload: true // 프리로드 리스트 신청 준비 완료
}));
로컬 개발 환경 (Localhost)
"로컬 개발할 때도 HTTPS 써야 하나요?"
네, 쓰는 게 좋습니다. httpOnly 쿠키나 SameSite 설정 때문에 로컬에서도 HTTPS가 필요할 때가 많거든요.
하지만 localhost는 HSTS가 적용되지 않는 경우가 많습니다. 브라우저가 개발 편의를 위해 봐주거든요.
대신 mkcert를 써서 로컬 인증서를 발급받고, .dev 도메인(예: myapp.dev)을 /etc/hosts에 등록해서 쓰면 HSTS가 작동합니다. (.dev 도메인은 전체가 Preload List에 등록되어 있습니다!)
7. 단계별 적용 전략 (쫄보를 위한 가이드)
무턱대고 max-age=1년을 때려 박았다가 사이트가 먹통이 되면 곤란합니다.
저는 이렇게 점진적으로 적용했습니다.
- 1단계 (간 보기):
max-age=300(5분). 문제가 생기면 5분만 욕먹으면 됩니다. "아 잠시 서버 점검 중입니다"라고 둘러대세요. - 2단계 (확신):
max-age=86400(1일). 하루 동안 트래픽과 로그를 지켜봅니다. 서브도메인에서 에러가 안 나는지 체크합니다. - 3단계 (배포):
max-age=31536000(1년). 이제 뒤로 갈 수 없습니다. - 4단계 (쐐기):
preload옵션 추가 후 구글에 등록 신청. 이제 당신의 사이트는 전 세계적으로 "HTTPS 전용"으로 공인받았습니다.
7.5. 해커의 시선 - SSL Stripping은 어떻게 일어나는가?
지피지기면 백전백승. 해커가 어떻게 여러분의 사이트를 터는지 알아야 막을 수 있습니다. 툴(Tool) 이름은 언급하지 않겠습니다만, 과정은 이렇습니다. (절대 따라하지 마세요, 철창갑니다.)
- ARP Spoofing: 해커가 공유기(Gateway)인 척 위장해서, 피해자의 모든 인터넷 트래픽이 해커의 노트북을 거쳐가게 만듭니다.
- HTTP 감지: 피해자가
http://bank.com을 요청하는 순간을 딱 잡습니다. - MITM (Man-In-The-Middle): 해커는 은행 서버와
Release버전으로 HTTPS 통신을 맺습니다. 하지만 피해자에게는 HTTPS를 벗겨내고(Strip) 그냥 HTTP로 응답을 줍니다. - 정보 탈취: 피해자는 자물쇠가 없는 줄도 모르고 아이디/비번을 입력합니다. 해커는 이걸 고스란히 저장합니다.
HSTS는 이 과정 중 2번을 원천 차단합니다. 브라우저가 아예 HTTP 요청을 안 보내니까요.
7.9. HSTS 적용 전 필수 체크리스트
이거 확인 안 하고 적용하면 장애 보고서 쓰게 됩니다.
- HTTP 리다이렉트: 80포트(HTTP)로 들어오면 443(HTTPS)으로 301 리다이렉트가 잘 되고 있는가?
- 서브도메인 전수 조사:
dev,stage,admin,legacy,jira등 모든 서브도메인이 HTTPS 인증서를 가지고 있는가? - 인증서 만료일: 자동 갱신(Certbot 등)이 켜져 있는가? HSTS 환경에서 인증서 만료는 곧 "서비스 다운"입니다.
- 루트 도메인 vs www:
example.com과www.example.com둘 다 커버가 되는가? - Preload 신청: 위 조건들이 3개월 이상 안정적으로 유지되었는가? (그제서야
preload옵션을 켜세요)
TLS 1.3과 차세대 프로토콜 QUIC (HTTP/3) 제대로 파보기
HTTPS가 느리다는 편견은 옛말입니다. TLS 1.3이 등장하면서 혁명이 일어났거든요.
- 0-RTT (Zero Round Trip Time): 예전(TLS 1.2)에는 암호화 통신을 시작하려면 "안녕? 내 열쇠 받아라. 너도 줘라. 확인됐다." 하면서 4번이나 왔다 갔다(Handshake) 해야 했습니다. TLS 1.3은 이걸 1번으로 줄였고, 재접속할 때는 0번으로 줄였습니다.
- QUIC (HTTP/3): 이제는 TCP도 버리고 UDP 기반의 QUIC을 씁니다. TCP의 고질병인 "데이터 패킷 하나가 막히면 뒤에 줄 선 애들도 멈추는 현상(Head-of-Line Blocking)"을 해결했죠. 구글, 유튜브는 이미 이걸로 여러분에게 영상을 쏘고 있습니다.
보안은 더 이상 속도의 족쇄가 아닙니다.
7.99. 보안의 끝판왕: Certificate Transparency (CT)
HSTS보다 더 깊은 심연에는 Certificate Transparency (인증서 투명성)가 있습니다. 예전에는 해커가 몰래 "가짜 네이버 인증서"를 발급받으면, 브라우저는 속수무책이었습니다. CA(인증 기관)가 발급했으니까 믿을 수밖에요.
이제는 CT 로그라는 전 세계 공용 장부에 "나 인증서 발급했어!"라고 기록하지 않으면, 크롬이 "이 인증서는 장부에 없으므로 가짜다!"라고 차단해 버립니다.
우리가 브라우저를 켤 때마다, 크롬은 뒤에서 이 CT 로그를 확인하고 있습니다. 웹 보안은 이렇게 보이지 않는 곳에서 치열하게 발전하고 있습니다.
7.995. 자주 묻는 질문 (FAQ)
Q: HSTS를 설정하면 SEO(검색 엔진 최적화)에 문제없나요? A: 오히려 좋습니다. 구글은 HTTPS 사이트에 가산점을 줍니다. 그리고 301 리다이렉트를 통해 "이 사이트는 이제 HTTPS가 본진이야"라고 알려주므로, 검색 랭킹(Link Juice)도 안전하게 이전됩니다.
Q: 인증서가 만료되면 HSTS 사이트는 어떻게 되나요? A: 접속 불가 상태가 됩니다. 일반 사이트는 "안전하지 않음" 경고를 무시하고 들어갈 수 있지만, HSTS가 걸린 사이트는 "뒤로 가기" 버튼밖에 안 뜹니다. 그러니 인증서 자동 갱신(Certbot)은 생명줄입니다.
Q: 서브도메인 하나만 HTTP로 쓰고 싶은데...
A: includeSubDomains 옵션을 끄셔야 합니다. 하지만 보안상 권장하지 않습니다. 해커가 그 HTTP 서브도메인의 쿠키를 탈취해서 메인 도메인을 공격할 수 있거든요.
8. 마무리 - 보안은 사용자를 믿지 않는 것
보안의 제1원칙은 "사용자를 믿지 마라"입니다.
"사용자가 알아서 HTTPS로 들어오겠지"라고 믿는 건 직무유기입니다.
사용자는 귀찮아서 http://도 안 치고 그냥 naver.com만 칩니다.
HSTS는 서버가 할 수 있는 가장 강력한 안전벨트입니다.
지금 바로 여러분의 사이트 헤더를 개발자 도구의 Network 탭에서 확인해 보세요.
Strict-Transport-Security가 없다면, 여러분의 문은 반쯤 열려있는 겁니다.
해커가 그 틈을 비집고 들어오기 전에, 단단히 잠그시길 바랍니다.