curl -vvv로 HTTPS(TLS 통신)를 간단히 들여다보기

10 min

language: ja bn de en es fr hi ko pt ru zh-cn zh-tw

안녕하세요, 무능입니다.

서론

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으로 와일드카드 인증서를 발급받아야 하므로 잘못된 것으로 경고를 받고 있습니다.

그럼 다음에 또 뵙겠습니다. 잘 부탁드립니다.

Related Posts