"우리 서버는 요청이 잘 가는데요, 우리는 문제가 없어요" 라는 말은 조심스러워 해야 하는 말이다.
구체적인 원인을 찾는데만 3개월이 걸린 이슈가 있었다.
PG사(결제 대행사) 서버가 우리쪽 서버로 "완료" 통지(Webhook)를 보냈는데, 우리 서버에는 아무런 요청도, 로그도 남지 않는 현상이 있었다. PG사는 "너희 서버가 503(Service Unavailable)을 응답하고 있어"라고 주장했고, 나는 "Nginx, Apache 로그를 다 뒤져봐도 요청 온 흔적조차 없다" 고 응답했다.
PG사의 웹훅 버그 가능성부터 /etc/hosts 설정을 통해 전혀 다른 IP로 DNS 캐싱이 되어 있는 것은 아닌지 의심했다. 하지만 PG사는 단순히 'curl' 요청 결과만을 근거로 문제가 없다는 답변만 반복할 뿐이었다. 이런 무의미한 3개월의 시간을 쏟기보다는 기술적 문제를 증명해야겠다는 판단에 PG사의 IP 주소를 요청한 후 패킷 레벨의 네트워크 레이어 분석을 추적했다.
원인을 찾는 과정 1 (네트워크 패킷 분석)
PG사의 웹훅 요청에 503 응답이 나왔는데 우리 애플리케이션 로그(Access/Error Log)에 남지 않는다는 건 해당 요청이 우리 애플리케이션 레벨에 도착하지 못했다는 것을 의미한다. 503을 응답하는건 결국 로드베런서, WAS 의 역할인데 우리쪽에는 로그가 없으니 네트워크 레이어에도 요청이 도착하지 않는다는 것을 전제로 우리 서버에서 tcpdump를 사용하여 패킷 데이터를 캡처했다.
-- PG사에서 전달받은 서버 IP를 추가
sudo tcpdump -i any host [PG사의 IP] -nn -v -X
예상과는 다르게 네트워크 레이어에서 캡쳐된 데이터가 있어 16진수 데이터를 분석하기 scapy 라이브러리를 사용하여 TLS Alert Message 를 찾아보았다. 물론 와이어샤크를 이용해 분석도 해보았지만 스크립트를 만들어 분석하는게 더 효율적이었다.
from scapy.all import IP, TCP
import binascii
def analyze_tls_packet(hex_str):
# 공백 제거 및 바이너리 변환
hex_str = "".join(hex_str.split())
try:
data = binascii.unhexlify(hex_str)
except:
print("Invalid Hex Data")
return
pos = 0
packet_count = 0
print(f"{'No.':<4} | {'Source IP':<15} | {'Info'}")
print("-" * 60)
while pos < len(data):
try:
ip_pkt = IP(data[pos:])
pkt_len = ip_pkt.len
packet_count += 1
src = ip_pkt.src
payload = bytes(ip_pkt[TCP].payload)
info = ""
# TLS Handshake (0x16)
if payload.startswith(b'\x16\x03'):
info = "TLS Client Hello (Handshake Start)"
# TLS Alert (0x15)
elif payload.startswith(b'\x15\x03'):
alert_level = payload[5] # 1: Warning, 2: Fatal
alert_desc = payload[6] # Error Code
error_map = {
46: "Certificate Unknown (46)",
48: "Unknown CA (48)",
70: "Protocol Version Mismatch (70)"
}
err_msg = error_map.get(alert_desc, f"Code {alert_desc}")
info = f"TLS ALERT: {err_msg} (Level: {alert_level})"
if info:
print(f"{packet_count:<4} | {src:<15} | {info}")
pos += pkt_len
except:
pos += 1
analyze_tls_packet("hex 메세지")
실행 결과 Protocol Version Mismatch (70) 이라는 것을 알게 되었고 PG사 서버가 우리에게 접속을 시도하자마자 우리 서버가 Protocol Version 이 다르다고 연결을 끊어버렸다.
원인을 찾는 과정 2 (네트워크 패킷 분석)
위 결과를 통해 PG사에서는 사용이 중단된 TLS 1.1 이하의 버전을 사용한다고 판단되었다.
그래서 우리 서버에서는 TLS 1.0/1.1 을 허용해주고 다시 시도를 하니 인증서 체인(Chain) 에 대한 문제가 발생했다.
처음엔 우리 서버 설정 문제인가 싶었지만 패킷을 다시 뜯어보니 ISRG Root X1 인증서의 신뢰 문제였다.
- 상황: 우리 서버는 Let's Encrypt의 최신 루트 인증서인 ISRG Root X1 체인을 사용중이다.
- 원인: PG 사의 서버(클라이언트)의 OS나 Docker 이미지가 너무 오래된 버전이라 최신 루트 인증서를 '신뢰할 수 있는 기관'으로 인식하지 못했다.
- 결과 (Certificate Unknown): 클라이언트가 "이 인증서 발급자가 누군지 모르겠다" 며 Certificate Unknown (46) 응답과 함께 연결을 끊어버렸다.
- 503 에러: 연결이 끊기자, 상대방 측의 프록시 서버(Gateway)가 연결이 안되기 때문에 503 Service Unavailable 에러를 반환했다.
이 원인은 PG사와 우리의 서버가 호환되지 않기 때문에 발생한 문제이다. 한쪽은 레거시인데 다른 한쪽은 보안 패치가 되어있기 때문이다.
- 2021년, Let's Encrypt는 루트 인증서를 DST Root CA X3에서 ISRG Root X1으로 교체했었다.
- 하지만 상대방 서버의 신뢰 Trust Store는 2021년 이전에 멈춰 있다.
- 이로 인해 최신 표준 인증서를 들이밀어도 "알지 못하는 못한 기관이라며" 거절당한 것이다.
https://letsencrypt.org/ko/docs/dst-root-ca-x3-expiration-september-2021/
DST Root CA X3 루트 인증서 만료 (2021년 9월)
2021년 9월 30일부터 오래된 브라우저들과 기기들이 Let’s Encrypt 인증서를 신뢰하는 방식에 작은 변화가 생깁니다. 만약 당신이 일반적인 웹사이트를 운영하고 있다면 대다수의 방문자들은 여전
letsencrypt.org
해결 방법
결국 이 문제는 내가 해결할 수 있는 문제가 아니었다. 만약 다른 인증서로 교체하거나 다운그레이를 하면 해결될 문제이긴 하나 보안을 뒤로하면서 내 시간을 써가면서 해결하기 보단 PG사에 분석과 정과 원인, 해결 방법을 알려주고 2021년도부터 발표된 이슈들을 첨부하여 문제를 조치해달라고 문의를 남겨두었다.
- https://datatracker.ietf.org/doc/html/rfc8996
- https://cert.crosscert.com/공지웹-브라우저-tls-1-0-tls-1-1-프로토콜-지원-중단-예정/
RFC 8996: Deprecating TLS 1.0 and TLS 1.1
This document formally deprecates Transport Layer Security (TLS) versions 1.0 (RFC 2246) and 1.1 (RFC 4346). Accordingly, those documents have been moved to Historic status. These versions lack support for current and recommended cryptographic algorithms a
datatracker.ietf.org
[공지]웹 브라우저 TLS 1.0, TLS 1.1 프로토콜 지원 중단 예정 - SSL 인증서 발급,종류,가격비교 | 한국
Chrome, IE, Edge 등 주요 브라우져에서 2020년부터 TLS 1.0, TLS 1.1 암호화 프로토콜 통신 지원이 중단되어 안내 해드립니다. Apple Safari, Google Chrome, Microsoft Edge, Mozilla Firefox 를 포함한 주요 웹 브라우저들
cert.crosscert.com
요약하자면
- 어플리케이션 레벨의 로그가 없으면 네트워크 로그를 분석해보자
애플리케이션 레벨에 로그가 남지 않는다면 네트워크 레이어에 답이 있을 수 있다. 우리쪽 서버 로그에는 어떤 503 에러도 기록되지 않았기에 이것은 상대측 프록시 서버의 문제라고 판단하였다. 실제로 패킷 분석을 통해 이를 입증할 기술적 근거를 확보할 수 있었다. - 보안은 타협하지 말자
당장의 이슈 해결을 위해 TLS 버전을 낮추거나 인증서 설정을 중단된 버전으로 변경했다면, 변경도 불가능할 뿐더러 서비스는 잠재적인 취약점을 안게 되었을 것이다. 2021년 TLS 1.1 이하 지원 중단 당시 많은 서비스가 겪었던 혼란처럼 근본적인 해결책이 아닌 임시방편은 반드시 미래의 기술 부채로 돌아온다.
'실무 경험 > 실무 개발 & 협업' 카테고리의 다른 글
| 휴먼에러를 방지하는 방법 1 - git add -p(git add partial (or patch) (0) | 2025.04.24 |
|---|---|
| [Block Chain] - Integer overflow 그리고 underflow (0) | 2025.04.21 |
| 2022 - 휴가만 쓰면 발생하는 우마카세가 삼겹살로 바뀌던 날: 크리스마스 전전날의 악몽 (TLS 이슈) (0) | 2024.02.29 |
| 2023 - Bulk Mailler 실종 사건 (0) | 2024.02.29 |
| [문서화] - API 문서 도입 + 작성하기 (0) | 2024.02.16 |