curl -vvv로 HTTPS(TLS 통신)를 간단히 들여다보기
안녕하세요, 무능입니다.
서론
HTTPS 자체는 TLS 통신이며, HTTP over TLS와 같이 HTTP 통신이 TLS 통신으로 래핑된 것입니다. TLS 통신에만 초점을 맞추면 HTTPS 통신을 지원하는 도메인에 대한 요청과는 다른 것이 되므로, curl -vvv로 상세 디버깅하여 client hello부터 해당 통신을 확인할 수 있습니다. 그렇다고는 하지만 우연히 발견한 것입니다.
살펴보기
자, 다음과 같이 들여다보겠습니다.
curl -vvv -sl
-vvv # 상세 디버그 옵션
-s # 사일런트 모드, 없어도 됨
-I # 헤더만 표준 출력
그럼 살펴보겠습니다.
$ curl -vvv https://soulminingrig.com/ -sI
19:30:07.019276 [0-x] == Info: [READ] client_reset, clear readers
19:30:07.064770 [0-0] == Info: Host soulminingrig.com:443 was resolved.
19:30:07.065031 [0-0] == Info: IPv6: (none)
19:30:07.065180 [0-0] == Info: IPv4: 167.179.75.206
19:30:07.065413 [0-0] == Info: [HTTPS-CONNECT] adding wanted h2
19:30:07.065587 [0-0] == Info: [HTTPS-CONNECT] added
19:30:07.065718 [0-0] == Info: [HTTPS-CONNECT] connect, init
19:30:07.065881 [0-0] == Info: Trying 167.179.75.206:443...
19:30:07.066065 [0-0] == Info: [HTTPS-CONNECT] connect -> 0, done=0
19:30:07.066197 [0-0] == Info: [HTTPS-CONNECT] Curl_conn_connect(block=0) -> 0, done=0
19:30:07.066400 [0-0] == Info: [HTTPS-CONNECT] adjust_pollset -> 1 socks
19:30:07.083945 [0-0] == Info: [HTTPS-CONNECT] connect -> 0, done=0
19:30:07.084308 [0-0] == Info: [HTTPS-CONNECT] Curl_conn_connect(block=0) -> 0, done=0
19:30:07.084750 [0-0] == Info: [HTTPS-CONNECT] adjust_pollset -> 1 socks
19:30:07.095330 [0-0] == Info: [SSL] cf_connect()
19:30:07.095574 [0-0] == Info: [SSL] ossl_connect, step1
19:30:07.098502 [0-0] == Info: ALPN: curl offers h2,http/1.1
19:30:07.098614 [0-0] == Info: [SSL] ossl_connect, step2
19:30:07.099069 [0-0] => Send SSL data, 5 bytes (0x5)
0000: .....
19:30:07.099205 [0-0] == Info: TLSv1.3 (OUT), TLS handshake, Client hello (1):
19:30:07.099360 [0-0] => Send SSL data, 1563 bytes (0x61b)
~생략~
이렇게 엄청난 양의 문자열이 나왔습니다...
통신 확인
여기서 443번 포트에서 DNS 이름 해결이 완료되었습니다.
19:30:07.064770 [0-0] == Info: Host soulminingrig.com:443 was resolved.
여기서 h2, HTTP/2 연결을 요청하여 연결할 수 있었습니다.
19:30:07.065413 [0-0] == Info: [HTTPS-CONNECT] adding wanted h2
19:30:07.065587 [0-0] == Info: [HTTPS-CONNECT] added
19:30:07.065718 [0-0] == Info: [HTTPS-CONNECT] connect, init
19:30:07.065881 [0-0] == Info: Trying 167.179.75.206:443...
19:30:07.066065 [0-0] == Info: [HTTPS-CONNECT] connect -> 0, done=0
그리고 마침내 SSL/TLS 통신이 시작되었습니다.
19:30:07.095330 [0-0] == Info: [SSL] cf_connect()
19:30:07.095574 [0-0] == Info: [SSL] ossl_connect, step1
19:30:07.098502 [0-0] == Info: ALPN: curl offers h2,http/1.1
19:30:07.098614 [0-0] == Info: [SSL] ossl_connect, step2
19:30:07.099069 [0-0] => Send SSL data, 5 bytes (0x5)
그리고 TLS1.3으로 마침내 프로토콜상의 client hello TLS 핸드셰이크가 시작되었습니다!
19:30:07.099205 [0-0] == Info: TLSv1.3 (OUT), TLS handshake, Client hello (1):
클라이언트의 전송으로 패킷 전송이 시작됩니다.
19:30:07.099205 [0-0] == Info: TLSv1.3 (OUT), TLS handshake, Client hello (1):
19:30:07.099360 [0-0] => Send SSL data, 1563 bytes (0x61b)
0000: .........r.q.....GC...._4....k..C.P... ..?...x...HT..09..z4b....
0040: ..,9....<.......,.0.........+./...$.(.k.#.'.g.....9.....3.....=.
0080: <.5./..................soulminingrig.com........................
00c0: .............h2.http/1.1.........1.....6.4......................
0100: ...............................+........-.....3...........RIw..7
이 시점에서 요청 자체에 도메인인 soulminingrig.com이 평문으로 전송되며, 서버 측이 SNI를 지원한다면 일관성이 유지됩니다.
19:30:07.105068 [0-0] == Info: [SSL] ossl_bio_cf_out_write(len=1568) -> 0, 1568
19:30:07.105244 [0-0] == Info: [SSL] ossl_bio_cf_in_read(len=5) -> 81, 0
19:30:07.105355 [0-0] == Info: [SSL] ossl_populate_x509_store, path=/etc/ssl/certs/ca-certificates.crt, blob=0
19:30:07.114557 [0-0] == Info: CAfile: /etc/ssl/certs/ca-certificates.crt
19:30:07.114669 [0-0] == Info: CApath: none
19:30:07.114768 [0-0] == Info: [SSL] SSL_connect() -> err=-1, detail=2
19:30:07.114903 [0-0] == Info: [SSL] SSL_connect() -> want recv
19:30:07.115050 [0-0] == Info: [SSL] cf_connect() -> 0, done=0
이 시점에서 클라이언트 측이 보유한 루트 인증서를 준비하고 있습니다.
아직 연결이 확립되지 않았네요.
Let’s Encrypt의 경우 ISRG Root X1에 해당하는 것으로 보입니다.
Chain of Trust - Let’s Encrypt
$ grep -A 3 "ISRG Root X1" /etc/ssl/certs/ca-certificates.crt
# ISRG Root X1
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
그리고 Server Hello가 돌아왔습니다!
19:30:07.178149 [0-0] == Info: [SSL] ossl_bio_cf_in_read(len=122) -> 0, 122
19:30:07.178363 [0-0] == Info: TLSv1.3 (IN), TLS handshake, Server hello (2):
19:30:07.178583 [0-0] <= Recv SSL data, 122 bytes (0x7a)
0000: ...v....y'...>........&.....,5O....... ..?...x...HT..09..z4b....
0040: ..,9.........+.....3.$... F........`~..l[..uhE..F.P?..V..6
19:30:07.179600 [0-0] == Info: [SSL] ossl_bio_cf_in_read(len=5) -> 0, 5
19:30:07.179730 [0-0] <= Recv SSL data, 5 bytes (0x5)
그리고 서버 측에서 인증서가 돌아왔습니다.
19:30:07.183139 [0-0] <= Recv SSL data, 2049 bytes (0x801)
0000: ...........0...0.................g...4...C..0...*.H.=...021.0...
0040: U....US1.0...U....Let's Encrypt1.0...U....E50...250714140356Z..2
0080: 51012140355Z0.1.0...U....soulminingrig.com0Y0...*.H.=....*.H.=..
00c0: ..B...V...$.....}.hN.f......n@F&...GR.....-.....?z]6d.=..<..eu..
서버 측이 가진 비밀 키를 사용하여 서명이 전송되었습니다.
0000: .
19:30:07.199683 [0-0] == Info: TLSv1.3 (IN), TLS handshake, CERT verify (15):
19:30:07.199941 [0-0] <= Recv SSL data, 79 bytes (0x4f)
여기서 TLS 핸드셰이크가 마침내 종료되고 확립되었습니다.
19:30:07.202057 [0-0] == Info: TLSv1.3 (IN), TLS handshake, Finished (20):
자주 볼 수 없는 TLS 핸드셰이크
처음에 ChatGPT 등에 물어봐도 Wireshark 등을 사용하라고 했지만, Wireshark는 노이즈가 많은 경우가 많아서 간단하게 확인할 수 있는 방법이 없을까 생각했습니다. 그러다 문득 생각나서 시도해 보니 잘 되었습니다.
TLS 핸드셰이크가 실패하는 패턴으로 다음을 확인해 보는 것도 흥미로울 수 있습니다.
badssl.com
$ curl -vvv -sl https://wrong.host.badssl.com/
19:50:53.161049 [0-x] == Info: [READ] client_reset, clear readers
19:50:53.202041 [0-0] == Info: Host wrong.host.badssl.com:443 was resolved.
19:50:53.202233 [0-0] == Info: IPv6: (none)
19:50:53.202326 [0-0] == Info: IPv4: 104.154.89.105
19:50:53.202496 [0-0] == Info: [HTTPS-CONNECT] adding wanted h2
~~~
19:50:53.759424 [0-0] == Info: SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 / secp256r1 / rsaEncryption
19:50:53.759894 [0-0] == Info: ALPN: server accepted http/1.1
19:50:53.760124 [0-0] == Info: [SSL] ossl_connect, step3
19:50:53.760337 [0-0] == Info: Server certificate:
19:50:53.760541 [0-0] == Info: subject: CN=*.badssl.com
19:50:53.760763 [0-0] == Info: start date: Jul 15 20:02:58 2025 GMT
19:50:53.761029 [0-0] == Info: expire date: Oct 13 20:02:57 2025 GMT
19:50:53.761406 [0-0] == Info: subjectAltName does not match hostname wrong.host.badssl.com
19:50:53.761853 [0-0] == Info: SSL: no alternative certificate subject name matches target hostname 'wrong.host.badssl.com'
19:50:53.762409 [0-0] == Info: [SSL] cf_connect() -> 60, done=0
이 경우, *.badssl.com으로 인증서를 발급받았는데 *.host.badssl.com으로 와일드카드 인증서를 발급받아야 하므로 잘못된 것으로 경고를 받고 있습니다.
그럼 다음에 또 뵙겠습니다. 잘 부탁드립니다.