소프트웨어 개발보안 가이드 분석(2021) : 부적절한 인증서 유효성 검증
인증서를 확인하지 않거나 인증서 확인 절차를 적절하게 수행하지 않아, 악의적인 호스트에
연결되거나 신뢰할 수 없는 호스트에서 생성된 데이터를 수신하게 되는 보안약점
소프트웨어 개발보안가이드(2021), 한국인터넷진흥원
취약점에 대한 이해
웹 애플리케이션과 서버 간의 안전한 통신을 보장하기 위해 SSL/TLS 인증서는 필수적입니다. 이 인증서들은 사용자와 서버 간에 안전한 연결을 수립하는 데 사용되며, 통신의 암호화 및 신뢰성을 보장합니다. 하지만, 부적절한 인증서 유효성 검증은 다양한 보안 취약점을 일으킬 수 있습니다. 인증서 확인 절차를 적절히 수행하지 않으면, 악의적인 호스트에 연결되거나 신뢰할 수 없는 호스트에서 생성된 데이터를 수신할 위험이 있습니다.
공격 메커니즘
부적절한 인증서 유효성 검증으로 인해 발생할 수 있는 보안 위협에는 다음과 같은 것들이 있습니다:
- 중간자 공격(Man-in-the-Middle, MitM): 공격자가 클라이언트와 서버 간의 통신에 침입하여 데이터를 가로채거나 조작할 수 있습니다.
- 피싱 공격: 사용자를 속여 위조된 사이트에 접속하게 만들어 개인 정보를 탈취할 수 있습니다.
- 데이터 유출: 암호화되지 않거나 약하게 암호화된 데이터가 공격자에 의해 쉽게 읽힐 수 있습니다.
중간자 공격(Man-in-the-Middle Attack)을 개념적으로 보여주는 다이어그램을 통해 좀 더 상세히 알아보면,
- 공격자: 공격자는 컴퓨터와 해골로 표시된 아이콘을 통해 묘사되며, 사용자와 서버 사이의 통신을 가로채고 있음을 나타냅니다. 이는 공격자가 통신 내용을 도청하거나 조작할 수 있음을 의미합니다.
- 부적절한 인증서 사용: 사용자와 서버 사이의 통신에 사용되는 인증서에 "인증서 불허"라는 레이블이 붙어 있어, 인증서가 유효하지 않거나 신뢰할 수 없음을 암시합니다.
- 서버: 서버는 데이터베이스 서버를 나타내는 아이콘으로, 클라이언트와 통신하고 있습니다. 서버 측에는 "인증 종료"라고 적혀 있어, 서버가 인증을 마쳤거나 인증서가 있는 상태를 나타내지만, 이미 공격자에 의해 통신이 감지되거나 조작될 수 있는 상태입니다.
보안대책 : 인증서 유효성 검증 절차 구현
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) 필드에 명시된 값과 일치하는지 확인합니다.