소프트웨어 개발보안 가이드 분석(2021) : 부적절한 인증서 유효성 검증

인증서를 확인하지 않거나 인증서 확인 절차를 적절하게 수행하지 않아, 악의적인 호스트에

연결되거나 신뢰할 수 없는 호스트에서 생성된 데이터를 수신하게 되는 보안약점

소프트웨어 개발보안가이드(2021), 한국인터넷진흥원

 

취약점에 대한 이해

웹 애플리케이션과 서버 간의 안전한 통신을 보장하기 위해 SSL/TLS 인증서는 필수적입니다. 이 인증서들은 사용자와 서버 간에 안전한 연결을 수립하는 데 사용되며, 통신의 암호화 및 신뢰성을 보장합니다. 하지만, 부적절한 인증서 유효성 검증은 다양한 보안 취약점을 일으킬 수 있습니다. 인증서 확인 절차를 적절히 수행하지 않으면, 악의적인 호스트에 연결되거나 신뢰할 수 없는 호스트에서 생성된 데이터를 수신할 위험이 있습니다.

공격 메커니즘

부적절한 인증서 유효성 검증으로 인해 발생할 수 있는 보안 위협에는 다음과 같은 것들이 있습니다:

  • 중간자 공격(Man-in-the-Middle, MitM): 공격자가 클라이언트와 서버 간의 통신에 침입하여 데이터를 가로채거나 조작할 수 있습니다.
  • 피싱 공격: 사용자를 속여 위조된 사이트에 접속하게 만들어 개인 정보를 탈취할 수 있습니다.
  • 데이터 유출: 암호화되지 않거나 약하게 암호화된 데이터가 공격자에 의해 쉽게 읽힐 수 있습니다.

중간자 공격(Man-in-the-Middle Attack)을 개념적으로 보여주는 다이어그램을 통해 좀 더 상세히 알아보면,

사용자와 서버 사이의 인증서 기반 통신이 취약할 때 발생할 수 있는 보안 위험
  1. 공격자: 공격자는 컴퓨터와 해골로 표시된 아이콘을 통해 묘사되며, 사용자와 서버 사이의 통신을 가로채고 있음을 나타냅니다. 이는 공격자가 통신 내용을 도청하거나 조작할 수 있음을 의미합니다.
  2. 부적절한 인증서 사용: 사용자와 서버 사이의 통신에 사용되는 인증서에 "인증서 불허"라는 레이블이 붙어 있어, 인증서가 유효하지 않거나 신뢰할 수 없음을 암시합니다.
  3. 서버: 서버는 데이터베이스 서버를 나타내는 아이콘으로, 클라이언트와 통신하고 있습니다. 서버 측에는 "인증 종료"라고 적혀 있어, 서버가 인증을 마쳤거나 인증서가 있는 상태를 나타내지만, 이미 공격자에 의해 통신이 감지되거나 조작될 수 있는 상태입니다.

 

보안대책 : 인증서 유효성 검증 절차 구현

private boolean verifyCertificate(X509Certificate cert, String host) {
    try {
        // 인증서의 CN과 호스트 이름 일치 여부 확인
        if(!cert.getSubjectX500Principal().getName().contains(host)) {
            return false;
        }
        // 인증서가 신뢰할 수 있는 CA에 의해 서명되었는지 확인
        cert.checkValidity();
        return true;
    } catch (CertificateExpiredException | CertificateNotYetValidException e) {
        return false;
    }
}
  • 인증서의 Common Name(CN) 확인: 인증서의 CN이 실제 호스트 이름과 일치하는지 확인합니다.
  • 신뢰할 수 있는 발급기관 확인: 인증서가 신뢰할 수 있는 기관(CA, RootCA)에 의해 서명되었는지 확인합니다.
  • 인증서의 유효기간 확인: 인증서가 현재 유효한 기간 내에 있는지 검증합니다.
  • 인증서의 해지 여부 확인: CRL(Certificate Revocation Lists) 또는 OCSP(Online Certificate Status Protocol)를 사용하여 인증서의 해지 여부를 확인합니다.
  • 안전한 암호화 알고리즘 사용 여부 확인: 인증서가 안전한 암호화 알고리즘을 사용하여 생성되었는지 확인합니다.

※ 코드 예제 : Java에서의 안전한 유효성 검증 사례

JAVA에서 SSL 인증서의 유효성을 검증하는 코드는 여러 단계를 거쳐야 하며, 기본적인 구현은 다음과 같습니다. 이 예시는 인증서의 유효성을 검증하고, 호스트 이름이 인증서의 CN(Common Name)과 일치하는지 확인하는 과정을 포함합니다.

import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLPeerUnverifiedException;

public class SSLCertVerification {

    public static void main(String[] args) {
        try {
            URL url = new URL("https://www.example.com");
            URLConnection urlConnection = url.openConnection();
            if (urlConnection instanceof HttpsURLConnection) {
                HttpsURLConnection httpsConnection = (HttpsURLConnection) urlConnection;
                httpsConnection.connect();

                // Get certificate from server
                Certificate[] certs = httpsConnection.getServerCertificates();
                for (Certificate cert : certs) {
                    if (cert instanceof X509Certificate) {
                        X509Certificate x509Cert = (X509Certificate) cert;

                        // Verify the certificate's validity period
                        x509Cert.checkValidity();

                        // Verify the hostname against the certificate's subject
                        if (!x509Cert.getSubjectX500Principal().getName().contains("CN=" + url.getHost())) {
                            throw new SSLPeerUnverifiedException("Hostname verification failed.");
                        }

                        // Here you should verify the certificate's chain of trust, not shown for brevity

                        System.out.println("Certificate is valid and the host matches the certificate.");
                    }
                }
            }
        } catch (CertificateException e) {
            System.out.println("The certificate is not valid.");
            e.printStackTrace();
        } catch (SSLPeerUnverifiedException e) {
            System.out.println("The peer's SSL certificate cannot be verified.");
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

이 코드는 기본적인 유효성 검증을 수행하며, 실제 프로덕션 환경에서는 다음과 같은 추가 검증이 필요합니다:

  • 인증서 체인의 신뢰성: 인증서가 신뢰할 수 있는 CA에 의해 발급되었는지 체크합니다.
  • CRL 또는 OCSP를 통한 인증서 해지 상태 확인: 인증서가 해지되지 않았는지 실시간으로 확인합니다.
  • 보다 정교한 호스트명 확인: 호스트명이 인증서의 SAN(Subject Alternative Name) 필드에 명시된 값과 일치하는지 확인합니다.

  • 이전 소프트웨어 개발보안 가이드 분석(2021) : 부적절한 전자서명 확인
  • 다음 소프트웨어 개발보안 가이드 분석(2021) : 사용자 하드디스크에 저장되는 쿠키를 통한 정보노출