소프트웨어 개발보안 가이드 분석(2021) : 부적절한 전자서명 확인
부적절한 전자서명 확인이란?
부적절한 전자서명 확인 취약점은 전자 서명 또는 디지털 서명 시스템의 보안 취약점으로 전자 서명의 유효성을 검증하는 과정에서 충분한 보안 검사를 수행하지 않아 발생합니다. 이 취약점을 통해, 공격자는 위변조된 파일을 이용하여 악성코드로 시스템을 감염시킬 수 있습니다.
공격 메커니즘
부적절한 전자서명 확인 취약점을 통해 발생할 수 있는 공격 메커니즘은 다음과 같습니다.
- 서명 위조 공격: 공격자가 위조된 전자 서명을 생성하여 위변조된 데이터를 정상적인 것처럼 보이게 만드는 공격입니다.
- 중간자 공격(Man-In-The-Middle, MITM): 공격자가 통신 중인 두 사용자 사이에 전송되는 서명을 가로채고, 원본 데이터에 대한 위조된 서명을 삽입한 후, 이를 다시 전달하는 공격입니다.
- 재전송 공격(Replay Attack): 공격자가 네트워크 상에서 데이터 패킷을 가로채고, 그 데이터를 저장한 뒤에 나중에 다시 전송하여 시스템이나 네트워크 상의 인증 과정을 속이거나 잘못된 정보를 전달하게 만드는 공격 방법으로, 전자서명 확인 시 취약점이 존재한다면, 공격자는 유효한 전자서명이 포함된 메시지를 재전송함으로써 시스템을 속일 수 있습니다.
이러한 공격들과 부적절한 전자서명 확인 취약점으로 인해, 애플리케이션은 위변조된 소프트웨어 업데이트나 변조된 문서, 잘못된 인증서를 사용한 웹 사이트를 신뢰하게 만드는 보안 위협에 직면할 수 있습니다.
취약한 웹 애플리케이션의 예
다음은 서명 생성 및 검증 과정에서 취약점이 존재하여 유효하지 않은 서명을 통과시키는 취약한 취약한 전자서명 취약점을 가진 예제 코드입니다.
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class VulnerableDigitalSignature {
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException {
// 공격자가 생성한 악의적인 공개 키
String maliciousPublicKeyString = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArDq9BgU25YMRWsByEWXt\n" +
"2sVZ2AjNesMfx0lATh+LXsS0bwtG1QF5iFwVo2sfjaV6wMNm92z8zF5AI3cgqfof\n" +
"weDzYXM95SFAhMciUWWF43D6rFY6ZALQwP3Tyt+8Y8tmn70JumtYFEqZLFv7pBUy\n" +
"RVf1o1ysN88hPtUHjGDDkJjxX3IOq4KmjbIncrYD3a3hLapmUf6teRhfOPf3Ptnt\n" +
"kNhJIlbrNou3LYu7o8uIapNSlQFgC43WJi2jqR8i3VS0cJQXlGPeoPcE8zX+KFZt\n" +
"7sivG5OGyYpxmS1nEydFSDn5ZgVm+FvZ1+tnqwZBGg0tT5EUhB6LyWGtbPND5QsZ\n" +
"IQIDAQAB";
byte[] maliciousPublicKeyBytes = Base64.getDecoder().decode(maliciousPublicKeyString);
// 원본 데이터
String originalData = "Original Data";
// 악의적으로 변조된 서명
String forgedSignature = "RwAt9nCMWvHtBCxFKxV+jy1LMxkDkIOvRw0m7gCPLSx0Oujm8DfZ6SmU/mEhJ/y1H4C58I7jg8i8zGXhErPbJg==";
byte[] forgedSignatureBytes = Base64.getDecoder().decode(forgedSignature);
// 악의적인 공개 키를 사용하여 서명 검증 시도
PublicKey maliciousPublicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(maliciousPublicKeyBytes));
Signature maliciousSignature = Signature.getInstance("SHA256withRSA");
maliciousSignature.initVerify(maliciousPublicKey);
maliciousSignature.update(originalData.getBytes());
boolean isMaliciousSignatureValid = maliciousSignature.verify(forgedSignatureBytes);
if (isMaliciousSignatureValid) {
System.out.println("악의적인 서명이 유효하게 검증되었습니다.");
} else {
System.out.println("악의적인 서명이 유효하지 않습니다. 서명 검증 실패.");
}
}
}
이 코드에서는 악의적인 공개 키를 사용하여 서명을 검증하고 있습니다. 공격자가 서명을 생성할 때 공개 키를 사용하여 원하는 서명을 생성하고, 통과시키는 예시로 이러한 공개 키는 신뢰할 수 있는 발행 기관에 의해 발급되거나 검증되지 않았기 때문에, 공격자는 위조된 서명 또는 메시지를 유효한 것처럼 만들 수 있습니다. 이 코드에서는 공격자가 제공한 공개 키(maliciousPublicKeyString)와 위조된 서명(forgedSignature)을 사용하여, 원본 데이터(originalData)에 대한 서명 검증을 시도합니다. 서명 검증 과정(maliciousSignature.verify(forgedSignatureBytes))에서 true가 반환되면, 이는 악의적인 서명이 검증 과정을 통과했음을 의미합니다. 실제로는 이러한 검증 과정이 신뢰할 수 없는 키와 서명을 사용함으로써 보안에 취약점이 발생하게 됩니다.
애플리케이션에서 디지털 서명을 사용할 때는 항상 신뢰할 수 있는 CA(인증 기관)로부터 발급받은 공개 키를 사용해야 하며, 서명 검증 로직을 구현할 때에도 이러한 보안 관련 베스트 프랙티스를 따라야 합니다. 이러한 방식으로만 서명의 진위와 데이터의 무결성을 신뢰할 수 있습니다.
시큐어코딩 적용 방법
부적절한 전자서명 확인 취약점을 방지하기 위해서는 다음과 같은 방법이 있습니다.
- 신뢰할 수 있는 인증 기관(CA) 사용: 디지털 서명을 검증할 때는 반드시 신뢰할 수 있는 CA로부터 발급받은 인증서를 사용합니다.
- 안전한 알고리즘 사용: 강력하고 안전한 서명 알고리즘을 사용해야 합니다. 예를 들어, SHA-1과 같이 약하거나 취약한 해시 함수 대신 SHA-256 또는 그 이상의 안전한 알고리즘을 사용합니다.
- 인증서 체인 검증: 인증서가 신뢰할 수 있는 루트 CA에 의해 서명되었는지 확인하고, 인증서 체인이 유효한지 검증합니다. 이 과정은 서명된 인증서의 발급 경로가 신뢰할 수 있는지 확인합니다.
- 서명 검증: 서명이 생성되었을 때 그것이 유효한지 검증해야 합니다. 즉, 공개 키 인프라(PKI)를 통해 전자서명이 유효한지, 데이터가 변조되지 않고 서명한 사람이 실제로 그 사람이 맞는지 확인합니다.
- 전송 보안: 서명된 데이터를 전송하는 동안 안전한 통신 채널을 사용하여 중간자 공격을 방지합니다.
- 일회용 서명(One-Time Signature): 서명이 한 번만 사용되도록 하여 재전송 공격을 방지할 수 있습니다.
- 시간 유효성 검증: 인증서의 유효 기간, CRL(Certificate Revocation List) 또는 OCSP(Online Certificate Status Protocol)을 통한 인증서 폐지 상태 등을 확인하여 인증서의 시간 유효성을 검증합니다.
다음은 시큐어코딩이 적용된 코드의 에제입니다.
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class DigitalSignatureVerification {
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException {
// 공개 키
String publicKeyString = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwLIMTHFAqmFAHvB8mZ20\n" +
"d7TyqX9uX3k+3el6GJH5zIKshxv6FDPfDz0t3N7IcXZyRkigX1pE5sqQ2Xv6z64C\n" +
"iNXxwe8Jt81sKNTZ3Q6IDMPOXkPRZD2NNjBjvA+nZd6L1fnBk7Xe+unD7ZXqSfdU\n" +
"NcF1sA+3jVtAG8I9E9vSL3ZCWcRd9YZyhzMRwA5xJx6Q9gEZWuyXcoxxI2W3+z6t\n" +
"L4ay/gKrQV5Bo8B+J17C5BjRtEFlxZr6l3zKZJpP3mI0piMUAd7gq6YOgBL/FmBv\n" +
"gdDJ35ZtmLg6wDmHgYxNfOcY8dUw9ZfIFKt3f7aH9KYQ+W0mM4DrcMxMrWfa6A7F\n" +
"lwIDAQAB";
byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyString);
// 서명된 데이터
String signedData = "IgkNCWcB9gJkowv2Lu2tGJxrvAmK7t5BBH/yB51OyGDDOq3RKmGah1N9BQBb5PRrA/DgHCT08zE45Vn+V+31Y4CrLY8pgl6F7wkl9xmsiGn1vIkgtgQ7L";
byte[] signedDataBytes = Base64.getDecoder().decode(signedData);
// 서명 검증
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update("Original Data".getBytes());
boolean isVerified = signature.verify(signedDataBytes);
if (isVerified) {
System.out.println("서명이 유효합니다.");
} else {
System.out.println("서명이 유효하지 않습니다.");
}
}
}
위 코드는 RSA 알고리즘을 사용하여 서명을 생성하고 검증하는 예시입니다. 이를 통해 서명이 유효한지 검증함으로써 부적절한 전자서명 확인 취약점을 방지할 수 있습니다.