들어가며
이 글은 특정 제품이나 디버깅 도구를 설명하려는 글이 아니다. 브라우저·모바일 앱·CLI 클라이언트가 공통으로 쓰는 HTTP(S) 스택에서, Proxy가 트래픽을 가로막을 때 무슨 일이 벌어지는지, TLS가 그 지점에서 어떤 보장을 주는지, 그리고 MITM Proxy와 SSL Pinning이 그 보장을 어떻게 흔드는지를 순서대로 정리한다.
도구 이름(Charles, Proxyman 등)은 동작을 눈에 익히기 위한 예로만 등장한다. 실제 운영에서는 기업망의 Forward Proxy, CDN 앞단의 Reverse Proxy, 개발자 PC의 패킷 캡처 등 같은 TLS·Proxy 원리가 반복된다.
스택에서의 위치
HTTP는 애플리케이션 계층 프로토콜이다. 메서드, 경로, 헤더, 바디를 정의하고, 아래 계층에는 기대하지 않는다. 전송은 보통 TCP 위에서 이루어지고, 잘 알려진 포트는 80(HTTP)과 443(HTTPS)이다.
HTTPS는 HTTP를 TLS로 감싼 것이다. TCP 연결이 잡힌 뒤, 애플리케이션 데이터를 보내기 전에 TLS 핸드셰이크로 암호화 파라미터와 서버 신원(인증서)을 맞춘다. 그 다음부터 같은 소켓 위에 HTTP 요청·응답이 실린다.
Proxy는 IP 라우터와 달리 애플리케이션을 이해하는 중계자다. Forward Proxy는 클라이언트 쪽 정책(캐시, 필터, 로깅)에 가깝고, Reverse Proxy는 서버 앞단의 진입점(로드밸런싱, TLS 종료, WAF)에 가깝다. 둘 다 “HTTP 메시지를 받아서 다시 보낸다”는 점에서는 같지만, 배치와 신뢰 경계가 다르다.
HTTPS를 다룰 때 클라이언트가 설정하는 Proxy 주소는, 평문 HTTP를 중계하는 것과 달리 보통 CONNECT로 TLS 터널을 뚫는 쪽에 가깝다. 클라이언트는 Proxy와 먼저 TCP로 붙고, CONNECT host:443 식으로 끝점까지의 바이트 스트림 파이프를 요청한다. 그 안에서 TLS는 클라이언트와 원격 서버 사이의 종단 간(end-to-end) 협상으로 이어지려 한다. 그래서 신뢰할 수 있는 Proxy만 두면 원래는 Proxy가 애플리케이션 내용을 볼 수 없다.
아래에서는 먼저 Proxy의 역할을 짚고, TLS 핸드셰이크와 인증서 체인을 정리한 뒤, 의도적으로 종단 간 TLS를 깨는 MITM Proxy와, 그에 맞서는 SSL Pinning을 이어서 본다.
Proxy
클라이언트와 서버 사이에 끼어서 트래픽을 중계하는 애플리케이션 계층 중계자다. 목적에 따라 두 종류로 나뉜다.
- Forward Proxy: 클라이언트 대신 아웃바운드 요청을 전달. 캐싱, 필터링, 정책 집행, 익명화. 기업망에서 흔하다.
- Reverse Proxy: 서버 앞에 서서 인바운드 요청을 받음. 로드밸런서, CDN 엣지, API 게이트웨이. 클라이언트는 보통 Reverse Proxy의 주소만 본다.
패킷 캡처(MITM 디버거) 는 Forward Proxy에 가깝고, 클라이언트가 보내는 대상 호스트명을 알아야 하므로 SNI(Server Name Indication) 가 TLS에 포함된다는 점과 맞물린다. (TLS 핸드셰이크 절에서 다시 짚는다.)
디버깅용 도구는 Forward Proxy에 자체 CA로 서명한 인증서를 끼워 넣는 MITM(Man-in-the-Middle) 방식으로 동작하는 경우가 많다. 이건 “정상적인 Forward Proxy”와는 신뢰 모델이 다르다.
평문 HTTP라면 Proxy가 메시지를 그대로 읽을 수 있다. HTTPS라면 위에서 말한 것처럼 기본적으로는 터널 안의 TLS가 종단 간이라 Proxy가 애플리케이션 페이로드를 보려면 추가 장치가 필요하다.
TLS 핸드셰이크
HTTPS는 TLS 위에서 HTTP를 실어 나른다. 데이터를 암호화하기 전에 클라이언트와 서버가 누구와 통신하는지 맞추는 과정이 필요하다. 이게 TLS 핸드셰이크다.
클라이언트의 첫 메시지 ClientHello에는 지원하는 암호 스위트 목록과, 가상 호스트를 가리키는 SNI가 들어간다. 같은 IP에 여러 도메인이 올라간 경우 서버가 어떤 인증서를 내보낼지 정하는 데 쓰인다.
핵심은 서버 인증서 검증 단계다. 클라이언트는 서버가 보낸 인증서가 신뢰할 수 있는 CA가 서명한 것인지 확인한다. 그 판단에 쓰는 루트는 운영체제나 런타임이 들고 있는 신뢰 저장소(trust store) 다. 브라우저는 자체 CA 목록을 덧씌우는 경우도 있지만, 원리는 같다.
인증서 체인 (Certificate Chain)
인증서는 계층 구조다.
Root CA (운영체제/브라우저에 내장)
└─ Intermediate CA
└─ 서버 인증서 (api.example.com)
클라이언트는 서버 인증서의 서명을 Intermediate CA로 검증하고, Intermediate CA의 서명을 Root CA로 검증한다. Root CA는 이미 기기에 신뢰 목록으로 내장되어 있다.
MITM Proxy가 HTTPS를 보는 법
평문 HTTP라면 Proxy가 그냥 중계하면 된다. 근데 HTTPS는 암호화되어 있어서 내용을 볼 수 없다.
종단 간 TLS를 의도적으로 가로막는 MITM Proxy는 이걸 다음 방식으로 돌파한다:
- 클라이언트가
api.example.com으로 HTTPS 요청을 보낸다 - Proxy가 클라이언트에게 자기가 만든 인증서(
api.example.com주체로 보이게 만든 것)를 보낸다 - 클라이언트가 그 인증서를 신뢰하면 → 클라이언트와 Proxy 사이에 TLS 터널 성립
- Proxy는 실제 서버와 별도로 TLS 연결 수립
- Proxy는 양쪽 사이에서 평문 트래픽을 볼 수 있다
즉 TLS 세션이 두 갈래다. 클라이언트는 “진짜 서버”와 말하고 있다고 생각하지만, 실제로는 먼저 Proxy와 TLS를 맺고, Proxy가 다시 서버와 TLS를 맺는 구조다.
클라이언트가 Proxy의 인증서를 신뢰하려면 → Proxy가 발급한 Root CA를 기기의 신뢰 목록에 추가해야 한다. 개발용 도구는 설치 마법사로 그걸 안내하는 경우가 많다. 운영망에서 허용하는 SSL 검사 프록시도 같은 계열의 신뢰 주입이 필요하다.
SSL Pinning이 이걸 막는 이유
SSL Pinning(인증서 고정)은 애플리케이션이 OS의 기본 신뢰 목록만 따르지 않고, 기대하는 서버 신원을 코드나 번들 안에 따로 박아 두는 기법이다. 모바일 네이티브 앱이나 일부 SDK에서 쓴다.
운영체제의 신뢰 목록 대신 앱이 직접 기대하는 인증서나 공개키를 지정한다.
일반 검증: "이 인증서가 신뢰할 CA가 서명했는가?"
Pinning: "이 인증서가 내가 알고 있는 바로 그것인가?"
핀으로 고정할 수 있는 대상:
- 인증서 전체 (Certificate Pinning): 인증서가 갱신될 때마다 앱 업데이트 필요
- 공개키 (Public Key Pinning): 인증서가 갱신돼도 공개키가 같으면 통과 → 더 유연함
MITM Proxy가 가짜 인증서를 끼워도, 앱이 기대하는 공개키와 다르면 TLS 핸드셰이크에서 끊긴다.
브라우저는 보통 Pinning 대신 HPKP 같은 메커니즘을 거의 쓰지 않고, CT(Certificate Transparency) 와 CA 생태계 쪽 정책에 더 기대는 편이다. 반면 네이티브 앱은 배포 단위가 느리고 공격 면이 다르기 때문에 Pinning을 두는 경우가 있다.
MITM Proxy는 CA 체인 검증은 통과할 수 있어도 (기기에 Root CA를 추가했다면), Pinning 검증은 우회할 수 없다. 코드나 번들에 박힌 공개키를 바꾸려면 애플리케이션 자체를 바꿔야 하기 때문이다.
용어 설명
- TLS (Transport Layer Security): 네트워크 통신을 암호화하는 프로토콜. HTTPS = HTTP over TLS
- CA (Certificate Authority): 인증서에 서명하는 신뢰 기관. DigiCert, Let's Encrypt 등
- SNI (Server Name Indication): TLS ClientHello에 실리는 호스트명. 같은 IP에 여러 HTTPS 사이트가 있을 때 서버 인증서 선택에 쓰인다
- Proxy: 클라이언트와 서버 사이에서 트래픽을 중계하는 서버. Forward Proxy와 Reverse Proxy로 나뉜다
- MITM (Man-in-the-Middle): 두 통신 주체 사이에 끼어 트래픽을 감청·조작하는 공격/기법
- SSL Pinning: 앱이 신뢰할 인증서 또는 공개키를 코드에 고정하는 보안 기법
- 핸드셰이크 (Handshake): 통신 시작 전 두 주체가 암호화 방식, 인증서를 협상하는 과정