반응형

 

작성일: 2024년 6월 4일

 

Network Traffic을 다루는 앱을 개발하다보면, TLS 또는 HTTPS로 암호화한 통신 내용을 봐야 할 때가 있다.

원칙적으로는 TLS 통신에서 제3자가 패킷 내용을 열람하는 것이 불가능하지만,

내 PC에 저장된 Master Secret 값을 이용하면 복호화가 가능하다. (내 PC는 제3자가 아니니까 이런 복호화가 가능한 얘기가 된다)

 

참고: 3가지 복호화 방법

Master Secret 값을 이용하는 경우 외에도 TLS Payload를 복호화(Decryption)하는 방법이 있다.

Wireshark 매뉴얼에 보면, Master Secret을 포함하여 총 3가지 방법을 소개하고 있다.

방법 A : (Pre)-Master Secret 정보가 담겨있는 Key load file을 사용하여 복호화하기

이 방법이 범용적으로 사용되는 복호화 방법.

Diffie-Hellman(DH) Key 교환이 발생하더라도 (Pre)-Master Secret 정보를 이용하면 복호화가 된다.

이 블로그의 대부분 내용이 이 방식으로 복호화하는 방법이 설명되어 있다.

 
방법 B : RSA private key (예:  PEM 포맷의 private key 파일)을 이용하여 복호화하기

TLS 연동할 Peer 노드(예: 서버)의 RSA private key를 한번만 Wireshark에 등록하면 되므로 가장 편하게 설정할 수 있다.

단, 아래와 같이 조건을 모두 만족하는 경우에만 RSA private key를 사용하여 복호화하는 것이 가능하다.

  • 조건1: Cipher suite가 (EC)DHE 계열이 아니어야 한다.
  • 조건2: TLS 1.0 ~ 1.2만 허용됨. TLS 1.3에서는 복호화할 수 없음.
  • 조건3: Wireshark에 등록할 Private key는 반드시 Server certificate이어야 한다. client certificate 또는 CA(Certificate Authority) certificate은 안 된다.
  • 조건4: TLS handshake 절차에서 'ClientKeyExchange' 과정이 포함되어야 함
즉, 위 4가지 조건을 모두 만족해야 햐는데 현실적으로 요즘 TLS 통신 환경에서 RSA private key를 이용하여 복호화하는 것이 거의 어렵다는 뜻 ㅠㅠ

 

방법 C : Pre-Shared-Key(PSK) 정보를 이용하여 복호화하기

수준 높은 보안을 요구하는 곳에서 PSK를 사용하지 않고,
PSK를 사용하는 것이 흔한 경우가 아니니까, 설명을 생략~

 

 

 

그렇다면,  Master Secret이라는게 무엇이길래 패킷 복호화가 가능한 것일까?

만약 Server / Client 간 통신이 mTLS 방식으로 연결된다면, 아래와 같이 절차를 따를 것이다.

  1. Client는 Server certificate을 받아서 믿을만한 Network peer인지 확인 (즉, Root CA가 서명한 Certificate인지 확인)
  2. Server는 Client certificate을 받아서 믿을만한 Network peer인지 확인
  3. 서로 믿을만하다고 판단되면, 대칭키 교환 (여기서 교환한 대칭키는 Server, Client가 패킷을 암호화 및 복호화 할 때 사용)
  4. Server, Client가 서로 암호화한 패킷을 주고, 받을 쪽에서는 패킷을 받으면 복호화 처리 (이 때 대칭키를 사용함)

위 mTLS 핸드쉐이킹 절차 3번에서 교화한 대칭키만 있으면, Server / Client 간에 주고 받은 암호화된 HTTPS(또는 TLS) 패킷을 복호화할 수 있다.

 

이론적으로는 위와 같고, 실제로 Chrome browser 또는 Firefox browser로 HTTPS 패킷을 보내고 받은 내용을 복호화하려면 아래와 같이 해야 한다.

 

 

##
## Firefox 브라우저로 테스트할 때.
## 
$ export SSLKEYLOGFILE=/tmp/sslkey.log
$ open /Applications/Firefox.app

##
## Chrome 브라우저로 테스트할 때.
##
$ export SSLKEYLOGFILE=/tmp/sslkey.log 
$ open /Applications/Google\ Chrome.app

 

 

Wireshark을 macOS에서 실행한 경우, 아래의 순서로 설정 화면을 열어 본다.

 

[ Wireshark ]  ->  [ Preferences ]  ->  [ Protocols ]  ->  [ TLS ]  ->  [ (Pre)-Master-Secret log filename ]

 

위 순서로 설정 화면을 오픈하고, 아래 예시 화면처럼 Master-Secret log 파일 경로를 입력하면 된다.

 

 

 

[ OK ] 버튼을 클릭하고, Firefox 브라우저에서 웹 브라우징을 해보면

TLS의 Payload가 눈으로 볼 수 있는 HTTP Payload로 복호화된 것을 확인할 수 있다. 

 

 


참고: 위와 동일한 내용이지만 원문을 읽고 싶다면, 아래 문서 링크를 클릭할 것 !

 

https://wiki.wireshark.org/TLS#using-the-pre-master-secret

 

TLS - Wireshark Wiki

Some applications (such as email) use a single port for both unencrypted and encrypted sessions. To change from unencrypted to encrypted, (START)TLS is used. When a single port directly uses the TLS protocol, it is often referred to as SSL.

wiki.wireshark.org

 

 

 


 

반응형

아래 명령 예시처럼 수행하면, tcp syn과 reset 패킷을 확인할 수 있다.

 

$ tcpdump -i enp1s0 -nN "tcp[tcpflags] == tcp-syn or tcp[tcpflags] == tcp-rst"

16:55:16.516453 IP 65.49.1.10.42458 > 112.169.87.89.87: Flags [S], seq 3730493766, win 65535, length 0
16:55:19.755480 IP 140.99.191.218.57246 > 112.169.89.88.26720: Flags [S], seq 417367123, win 1024, length 0
16:55:19.960898 IP 140.99.191.218.57246 > 112.169.89.88.26720: Flags [R], seq 417367124, win 1200, length 0
16:55:19.991267 IP 185.11.61.225.44794 > 112.169.89.88.38956: Flags [S], seq 163297978, win 1024, length 0
16:55:20.146215 IP 185.11.61.225.44794 > 112.169.89.88.38956: Flags [R], seq 163297979, win 1200, length 0
16:55:38.865833 IP 140.99.191.218.57246 > 112.169.89.88.19331: Flags [S], seq 523253283, win 1024, length 0

 

반응형

tcpdump 명령만 이용하면, 출력을 보기 좋게 또는 예쁘게 터미널로 출력하기 어렵다.

보기 좋은 형식으로 출력하려면 tshark 명령을 사용하는 것을 추천한다.

 

 

tshark 명령 사용

tshark을 사용하면, Network Packet이 정말 예쁘게 출력된다.  

아래와 같이 wireshark 패키지를 설치한다.  (참고로 tshark은 wireshark 패키지에 포함되어 있다)

##
## 아래 예시처럼 yum install 명령을 이용하여 wireshark 패키지를 설치한다.
##

$ yum install wireshark
...

##
## 설치 완료 후, 아래 명령과 같이 실행
## 참고: -V 옵션은 Packet을 Decode해서 출력하라는 뜻
##
$ tshark -i eth0 -nV port 53

 

위 예제 명령 정도의 옵션만 사용해도 꽤 보기 좋은 모양으로 IP 네트워크 패킷이 출력된다.

 

 

tcpdump 명령의 -A 옵션 사용해서 가독성 높게 출력하기

tshark을 사용할 수 없는 경우라면, 아래 명령처럼 -A 옵션을 주면, TCP 또는 UDP의 Payload가 ASCII 로 출력된다.

(사람 눈으로 읽을 수 있는, 즉 Readable Character만 터미널에 출력함) 

 

-A 옵션만 사용하면, TCP 세션을 수립하기 위한 SYNACKFIN 패킷도 모두 보여지기 때문에 

Filter에  tcp[32:4] = 0x47455420 조건을 추가한다. ("GET "에 대한 Pointcode 값이다) 

 

 

HTTP GET 요청 메시지만 출력
$ tcpdump -i p2p2 -s 1500 -An 'src net 192.168.0.0/24' and dst port 80 and 'tcp[32:4] = 0x47455420'

 

 

HTTP GET 요청과 응답 메시지 모두 출력
$ tcpdump -i em2 -s 1500 -An 'src net 192.168.0.0/24' and 'tcp[32:4] = 0x48545450' or 'tcp[32:4] = 0x47455420'

 

  또는 아래와 같은 방식을 사용할 수 있다.

(이 방식을 추천함)

$ tcpdump -i em2 -An -s 0 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'

 


참고: 위의 3개 명령 중에서 제일 마지막 명령이 제일 출력 포맷이 예쁘다. 

반응형

Kubernetes cluster 운영 환경에서 Pod간 주고 받은 IP packet을 tcpdump하는 방법을 알아보겠다.

 

 

방식 A:  Container 내부에 tcpdump 프로그램을 설치하고, Container 내부에서 직접 tcpdump 명령을 수행

$  kubectl exec  -n my-namespace  my-pod  -c my-container  -it -- bash
(In-Container) $  apt install -y tcpdump 
... 중간 생략 ...

##
## 참고: Kubernetes Pod 내부의 기본 NIC은 eth0이다.
##      만약, Multus CNI를 사용해서 Multi-NIC을 사용하는 경우라면,
##      eth0 이외에 net1, net2 같은 이름으로 NIC이 있을 것이다.
##      따라서, 내가 운영하는 CNI가 1개인지 또는 어떤 종류의 CNI이 인지에 따라
##      NIC의 이름이 다를 수 있으니까, ifconfig 명령 또는 `ip a` 같은 명령으로
##      NIC 이름을 확인하고 tcpdump 명령을 수행해야 한다.
##
(In-Container) $  tcpdump -i eth0 -w  my-capture.pcap
... 중간 생략 ...

위와 같이 생성한 pcap 파일은 Pod 외부에서 볼 수 없기 때문에

   1) Pod 내부에서 scp 명령으로 외부로 옮겨주던지 

   2) 또는 NFS, Ceph storage 같은 PV를 이용하여 저장하는 것을 권장한다.

 

 

 

방식 B: Worker node에서 특정 Pod의 namespace를 이용하여 tcpdump 명령을 수행

예제를 이용하여 설명해보겠다.

'productpage' Pod가 주고 받은 IP packet을 tcpdump하려는 상황을 가정해보자.

우선 'productpage' Pod가 어느 worker node에 있는지 확인한다.

$  kubectl get pod -o wide
NAME                              READY   STATUS    RESTARTS   AGE     IP           NODE  
... 중간 생략 ...
productpage-v1-6b746f74dc-xfjdz   2/2     Running   0          2d17h   10.244.2.4   worker-1
... 중간 생략 ...

 

'productpage' pod가 worker-1 node에 있으니까, worker-1에 ssh 접속하여 아래의 스크립트를 수행한다.

 

##
## Filename : run-tcpdump.sh
##

#!/usr/bin/bash

container_id=$(docker container ls | grep k8s_istio-proxy_productpage | awk '{ print $1 }')
echo "Container  ID: $container_id"

container_pid=$(docker inspect -f '{{.State.Pid}}' $container_id)
echo "Container PID: $container_pid"

nsenter -t $container_pid  -n /bin/bash -xec 'tcpdump -i any -w productpage-envoy-any.pcap'


## NOTE: After running this script to capture the packet,
##       Use the packet filter as 'tls or http' with your Wireshark program !

위 `run-tcpdump.sh` 스크립트를 수행하고 나서 만들어진 pcap 파일을 Wireshark으로 열어본다.

 

 


그런데, 위 "방식 A, 방식 B"는 Kubernetes를 사용했을 때나 가능한 얘기이고,
OCP(Openshift Container Platform)을 사용하는 경우라면 위 방식으로 container 내부의 network packet을 dump할 수 없다 !!!
왜? OCP, OKD Cluster의  Worker Node에는 'docker' command가 없다.
그러면 어떻게 하는가?
아래와 같이 "방식 C"를 따라해야 한다.

 

방식 C:  OCP의 Node에서 특정 Pod의 namespace를 이용하여 tcpdump 명령을 수행

예제를 이용하여 설명해보겠다.

(worker2 node에 Pod가 구동되어 있다고 가정하고 설명한다)

 

##
## Working Location:  Bastion Node, or PC (MacBook)
##

$  oc debug node/worker1.andrew.space

Starting pod/worker1.andrew.space-debug ...
To use host binaries, run `chroot /host`
Pod IP: 10.10.11.12
If you don't see a command prompt, try pressing enter.

sh-4.4# chroot /host

sh-4.4# container_id=$(crictl ps | grep my-example-app | awk '{ print $1 }')

sh-4.4# echo $container_id
378f096238fdd

sh-4.4# container_pid=$(crictl inspect $container_id | jq '.info.pid')

sh-4.4# echo $container_pid
3458673

sh-4.4# nsenter -t $container_pid  -n /bin/bash -xec 'tcpdump -i eth0'

... TCPDUMP 출력 결과 생략 ...

 

 


만약, OCP 또는 OKD Cluster를 구축할 때, OS를 CoreOS를 사용한 경우라면 
위와 같이 `nsenter -t .... 'tcpdump -i eth0'` 명령을 사용할 수 없다.
왜냐고?
CoreOS는 TCPDUMP 명령을 가지고 있지 않기 때문이다. ㅠㅠ
단, 임시로 CoreOS에 tcpdump command를 설치하고 위 절차를 따라하는 꼼수가 있기는 하다.

 

 

+ Recent posts