today keys : DNSSEC, NSEC, NSEC3, 부재 증명, NXDOMAIN, insecure 위임, bogus, Opt-Out, 존 열거
지난 편에서 신뢰가 루트부터 최종 도메인까지 이어지는 과정을 살펴봤습니다. 그런데 DNS 응답에는 "여기 있습니다"만 있는 것이 아닙니다. "그런 이름은 없습니다", "그 도메인은 DNSSEC을 쓰지 않습니다" 같은 부정 응답도 존재하며, 이런 응답 역시 위조되면 문제가 됩니다.
DNSSEC은 이를 위해 NSEC과 NSEC3이라는 별도 레코드를 둡니다. 이번 편에서는 이 레코드가 언제 만들어지고 어떤 구조인지, 그리고 "존재하지 않음"과 "서명되지 않음"을 각각 어떻게 증명하는지 다룹니다. 마지막으로 리졸버가 내리는 세 가지 판정 결과를 정리하겠습니다.
1. "없다"는 응답도 서명되어야 한다
DNSSEC의 원리는 레코드에 서명을 붙이는 것입니다. 그런데 여기에 근본적인 문제가 하나 있습니다.
있는 레코드 → 서명을 붙일 대상이 존재 → RRSIG 생성 가능
없는 레코드 → 서명을 붙일 대상이 없음 → 무엇에 서명하나?
존재하지 않는 것에는 서명을 붙일 수 없습니다. 그렇다고 부정 응답을 서명 없이 내보내면 공격자가 이를 위조할 수 있습니다.
부정 응답이 필요한 상황은 크게 두 가지입니다.
첫째, 존재하지 않는 이름을 질의받았을 때
zigi.cloudflare.com 처럼 존에 없는 이름을 물으면 NXDOMAIN을 응답해야 합니다.
이 응답이 위조되면 멀쩡한 서비스를 "없는 도메인"으로 만들어버리는 서비스 거부 공격이 가능합니다.
둘째, 자식 존에 DS가 없을 때
지난 편에서 본 것처럼 부모 존은 자식의 DS를 통해 신뢰를 넘겨줍니다.
그런데 자식이 DNSSEC을 적용하지 않았다면 DS가 없습니다.
이때 "DS가 없다"는 응답이 위조 가능하다면, 공격자가 서명된 도메인의 DS를 지워서 검증 자체를 무력화할 수 있습니다.
공격자가 부모 존의 DS 응답을 가로채 삭제
↓
리졸버는 "이 도메인은 DNSSEC을 안 쓰는구나"라고 판단
↓
이후 검증 없이 통과 → 공격자가 위조 응답을 마음대로 주입
이런 공격을 다운그레이드 공격이라고 합니다. DNSSEC을 적용한 도메인도 DS만 지우면 무력화되는 셈입니다.
2. NSEC / NSEC3은 언제 만들어지는가
"없다"는 사실을 데이터로 만든다면, 그 데이터는 언제 생성될까요.
질의받을 때마다 즉석에서 만들어 서명한다면, 개인키를 온라인에 두고 실시간으로 서명 연산을 해야 합니다.
부하도 크고 보안상으로도 좋지 않습니다.
DNSSEC은 이 문제를 미리 만들어두는 방식으로 해결합니다.
NSEC과 NSEC3은 질의 시점이 아니라 존 서명 시점에 일괄 생성됩니다.
여기서 말하는 존 서명이란,
평문 존 파일을 입력받아 DNSKEY와 RRSIG 같은 DNSSEC 레코드가 추가된 새 존 파일을 만들어내는 일괄 작업입니다.
구체적으로 어떤 명령으로 무엇이 산출되고 언제 실행하는지는 다음 편에서 다루기로 하고,
여기서는 부재 증명 레코드도 이때 함께 만들어진다는 점만 확인하고 넘어가겠습니다.
관리자가 존 서명 실행 (dnssec-signzone 등)
↓
존 안의 모든 이름을 정렬
↓
인접한 이름끼리 연결하는 NSEC(또는 NSEC3) 레코드를 전체 생성
↓
생성된 각 레코드에 ZSK로 서명 → RRSIG 부착
↓
서명된 존 파일에 함께 저장 → 질의가 오면 해당 레코드를 꺼내서 응답
즉 부재 증명 레코드는 A 레코드와 마찬가지로 존 안에 실제로 존재하는 레코드입니다.
질의 시점에는 이미 만들어진 것을 찾아서 돌려줄 뿐입니다.
여기서 생성 단위를 정확히 짚어둘 필요가 있습니다.
NSEC은 레코드 수가 아니라 이름(owner name) 수만큼 만들어집니다.
www.cloudflare.com. A 104.16.132.229
www.cloudflare.com. A 104.16.133.229
www.cloudflare.com. AAAA 2606:4700::6810:84e5
레코드는 3줄이고 RRset은 2개(A 묶음, AAAA)이지만, 이름은 www 하나뿐이므로 NSEC은 1개만 생성됩니다.
이 이름에 어떤 타입이 있는지는 뒤에서 볼 타입 비트맵에 함께 담기기 때문입니다.
그리고 당연한 이야기지만, 이 레코드들은 존을 서명해야만 생깁니다.
DNSKEY, RRSIG와 마찬가지로 서명 작업의 산출물이므로,
DNSSEC을 적용하지 않은 존에는 NSEC도 NSEC3도 존재하지 않습니다.
한 가지 덧붙이면, 이 존 서명은 최초 1회로 끝나는 작업이 아닙니다.
레코드가 추가·변경될 때는 물론이고, 변경이 없더라도 RRSIG 유효기간이 만료되기 전에 주기적으로 다시 서명해야 합니다.
특히 이름이 추가되거나 삭제되면 정렬 순서가 바뀌므로 앞뒤 NSEC3 레코드까지 함께 갱신됩니다.
3. NSEC 레코드의 구조
NSEC은 "이 이름 다음에 오는 이름은 무엇인지"를 담습니다.
blog.cloudflare.com. 3600 IN NSEC mail.cloudflare.com. A AAAA RRSIG NSEC
└────────┬────────┘ └────────┬────────┘
다음 이름 타입 비트맵
| 필드 | 값 | 의미 |
|---|---|---|
| Owner Name | blog.cloudflare.com. |
이 레코드가 속한 이름 |
| Next Domain Name | mail.cloudflare.com. |
정렬 순서상 바로 다음 이름 |
| Type Bit Maps | A AAAA RRSIG NSEC |
이 이름에 존재하는 레코드 타입 목록 |
이 레코드 한 줄이 두 가지를 동시에 증명합니다.
- blog와 mail 사이에 다른 이름은 없다 → 그 구간의 이름을 물으면 존재하지 않음이 증명됨
- blog.cloudflare.com에는 A, AAAA, RRSIG, NSEC만 있다 → MX나 TXT를 물으면 없음이 증명됨
앞의 것을 이름의 부재 증명, 뒤의 것을 타입의 부재 증명이라고 볼 수 있습니다.
이 두 가지가 뒤에서 각각 NXDOMAIN 증명과 DS 부재 증명에 쓰입니다.
4. NSEC3 레코드의 구조
NSEC에는 한 가지 부작용이 있습니다.
blog 다음이 mail 이라는 정보를 계속 따라가면 존 전체의 이름 목록을 알아낼 수 있습니다.
이를 존 열거(Zone Enumeration)라고 합니다.
apex → blog → mail → shop → www → apex (한 바퀴)
내부 시스템 이름이 노출될 수 있으므로 실무에서는 부담스러운 특성입니다.
NSEC3은 이름을 해시한 값으로 나열해 이 문제를 해결합니다.
1AVVQN74SG75UKFVF25DGCETHGQ638EK.cloudflare.com. 3600 IN NSEC3 1 0 10 AABBCCDD (
5SU1SD8TSFVIF2SJVCU3DOLS8M31VC2H
A RRSIG )
| 필드 | 값 | 의미 |
|---|---|---|
| Owner Name | 1AVVQN74... |
원래 이름의 해시값 (Base32) |
| Hash Algorithm | 1 |
SHA-1 |
| Flags | 0 |
Opt-Out 사용 여부 |
| Iterations | 10 |
해시 반복 횟수 |
| Salt | AABBCCDD |
해시에 섞는 값 |
| Next Hashed Owner | 5SU1SD8T... |
정렬 순서상 다음 해시값 |
| Type Bit Maps | A RRSIG |
존재하는 타입 목록 |
구조는 NSEC과 같고, 이름 자리에 해시값이 들어갔을 뿐입니다.
해시는 단방향이므로 순회해도 원래 이름을 역산할 수 없습니다. 현재는 NSEC3이 사실상 표준입니다.
5. 존재하지 않는 이름 증명하기 (NXDOMAIN)
이제 첫 번째 용도를 살펴보겠습니다. zigi.cloudflare.com을 질의했다고 가정합니다.

핵심은 구간(range)으로 증명한다는 점입니다.
존에 없는 이름은 무한히 많으므로 하나하나 레코드를 만들 수는 없습니다.
대신 "A와 B 사이에는 아무것도 없다"는 레코드를 미리 만들어두고,
질의받은 이름이 그 사이에 들어가면 존재하지 않음이 증명되는 구조입니다.
이 방식 덕분에 존 서명 시점에 미리 생성해둔 유한한 레코드만으로 무한한 이름의 부재를 증명할 수 있습니다.
6. 서명되지 않은 도메인 증명하기 (DS 부재)
두 번째 용도입니다. 여기서는 증명 주체가 자기 존이 아니라 부모 존이라는 점이 다릅니다.
어느 존에게 묻는가
먼저 용어를 정리하겠습니다. "부모 존"은 해당 도메인 바로 위 계층의 존을 가리킵니다.
cloudflare.com의 부모는 com이고, com의 부모는 루트입니다.
그리고 리졸버는 처음부터 부모 존을 알고 찾아가는 것이 아닙니다.
지난 편에서 본 대로 항상 루트에서 출발해 계층을 따라 내려갑니다.
서명되지 않은 도메인 nosign-example.com을 조회한다고 가정하고 전체 흐름을 따라가 보겠습니다.

참고로 첫 번째 단계에서 com의 DS가 따라오는 것은 com존이 DNSSEC으로 서명되어 있기 때문입니다.
현재 루트 존에 등록된 TLD 대부분은 서명되어 있으며,
특히 gTLD는 ICANN 레지스트리 계약상 DNSSEC 적용이 의무 사항입니다.
다만 일부 ccTLD는 아직 적용하지 않은 곳이 있어, TLD 단계에서 이미 insecure 위임으로 처리되는 경우도 존재합니다.
여기서 짚어둘 점은 두 번째 단계까지는 서명된 존을 상대로 정상적인 검증이 그대로 수행된다는 것입니다.
최종 목적지 도메인이 서명되지 않았다고 해서 루트나 com구간의 검증을 건너뛰지 않습니다.
체인이 com까지 정상적으로 이어지고, 그 다음 고리가 없다는 사실을 com이 서명으로 증명해주는 구조입니다.
타입 비트맵이 DS 부재를 증명한다
네 번째 단계에서 리졸버가 실제로 확인하는 것은 NSEC3의 타입 비트맵입니다. com존에 nosign-example.com에 대한 위임은 있지만 DS는 없는 경우, 비트맵은 이렇게 나옵니다.
Type Bit Maps: NS
NS는 있는 DS가 없습니다.
리졸버는 이 비트맵을 보고 "이 이름에는 위임(NS)은 있지만 DS는 존재하지 않는다"를 확인합니다.
기존 DNSSEC Part3 포스팅에서 다룬 "타입의 부재 증명"이 여기에 쓰이는 것입니다.
그리고 이 레코드에 붙은 RRSIG를 com의 ZSK로 검증하면, DS가 없다는 사실이 부모 존에 의해 서명된 정보임이 증명됩니다.
공격자가 DS를 지우기만 해서는 이 서명된 NSEC3까지 만들어낼 수 없으므로 다운그레이드 공격이 차단됩니다.
7. Opt-Out - 대형 TLD의 예외 처리
한 가지 예외가 있습니다. com처럼 수억 개의 도메인을 담은 TLD는 서명되지 않은 위임이 대다수인데,
그 모든 이름에 NSEC3을 만들면 존 크기가 감당하기 어려워집니다.
이를 위해 NSEC3에는 Opt-Out 플래그(Flags 값 1)가 있습니다.
이 플래그가 켜져 있으면 서명되지 않은 위임은 NSEC3 레코드를 개별 생성하지 않고,
인접한 해시 구간이 그 범위를 포괄하는 것으로 처리합니다.
Opt-Out 미사용 → 모든 이름에 NSEC3 생성 → DS 부재가 명시적으로 증명됨
Opt-Out 사용 → 서명 안 된 위임은 구간으로만 포괄 → DS 부재가 간접적으로 증명됨
무엇이 생략되는가
생략 대상은 DS가 없는 위임뿐입니다. 나머지는 모두 NSEC3이 그대로 만들어집니다.
| 대상 | NSEC3 |
|---|---|
| 존 apex | 생성 |
| 존이 직접 보유한 이름 (A, MX 등) | 생성 |
| DS가 있는 위임 | 생성 |
| DS가 없는 위임 | 생략 |
조건에 '위임'이 붙는 이유는, 위임이 아닌 일반 이름은 애초에 DS를 가질 일이 없어 생략할 근거가 없기 때문입니다.
TLD 존은 내용물이 거의 전부 위임이라 효과가 크지만, 일반 도메인 존에서는 Opt-Out을 켜도 줄어드는 양이 미미합니다.
참고로 Opt-Out은 NSEC3에만 있는 기능입니다.
NSEC에는 Flags 필드가 없어 모든 이름을 빠짐없이 연결해야 합니다.
대형 TLD가 NSEC3을 택하는 이유는 존 열거 방지뿐 아니라 이 점도 큽니다.
실무적으로 com, net 등 대형 gTLD는 Opt-Out을 사용합니다.
이 때문에 서명되지 않은 도메인의 부재 증명 강도가 다소 약해지지만,
어차피 그 도메인은 검증 대상이 아니므로 실질적인 보안 영향은 제한적이라고 보는 것이 일반적인 해석입니다.
8. 세 가지 판정 상태
지난 편과 이번 편의 내용을 종합하면 검증 리졸버의 판정은 세 가지로 나뉩니다.
| 상태 | 조건 | 응답 | 클라이언트가 보는 것 |
|---|---|---|---|
| secure | 사슬 전체 통과 | 정상 응답 | ad 플래그 부착 |
| insecure | DS 부재가 증명됨 | 정상 응답 | ad 플래그 없음 |
| bogus | 검증 실패 | SERVFAIL | 응답 없음 |
secure
- 트러스트 앵커부터 최종 레코드까지 모든 검증을 통과한 상태입니다.
- 리졸버는 응답에 ad(Authenticated Data) 플래그를 세워 "내가 검증했고 진짜다"를 알립니다.
insecure
- DNSSEC이 적용되지 않았음이 증명된 상태입니다.
- 정상 응답이지만 ad 플래그는 붙지 않습니다.
- 겉보기에는 검증을 하지 않는 리졸버의 응답과 구분되지 않지만, 내부적으로는 부재 증명 검증을 거친 결과입니다.
bogus
- 서명이 있는데 검증에 실패한 상태입니다.
- 대조 불일치, 서명 불일치, 유효기간 만료 등이 원인입니다.
- 이 경우 리졸버는 응답을 차단하고 SERVFAIL을 반환합니다.
- "믿을 수 없는 데이터라면 아예 주지 않는다"는 것이 DNSSEC의 설계 방침입니다.
지난 편 마지막에 ad 플래그가 붙지 않는 경우가 두 가지라고 했는데, 바로 insecure와 bogus입니다.
insecure는 정상 응답에 플래그만 없는 것이고, bogus는 응답 자체가 오지 않습니다.
1편에서 DNSSEC이 가용성을 보장하지 않는다고 했던 이유도 여기 있습니다.
설정 오류로 bogus 판정이 나면 멀쩡해 보이는 도메인이 통째로 조회되지 않습니다.
DNSSEC 4번쨰 포스팅은 내용이 좀 길어져서,
직접 확인해보는 부분은 다음 4-2 포스팅으로 이어서 하겠습니다.