← 中文资源

简体中文版本

DNSSEC

DNSSEC 为 DNS 应答附加数字签名链,使解析器能验证记录确实来自区域所有者且传输途中未被篡改,但它不加密流量。

阅读英文原文

概念定义

DNSSEC 是一组为区域数据附加非对称签名的扩展:每个被签名的记录集携带 RRSIG,公钥放在 DNSKEY 记录中,父区域的 DS 记录把信任链锚定到根。它保证真实性与完整性,不提供机密性。

工作机制

区域用区域签名密钥签署每个记录集,密钥签名密钥签署 DNSKEY 集,父区域发布 DS 哈希锚定委派。RRSIG 的时间戳限定有效期,签名到期后必须按计划重签。NSEC 与 NSEC3 记录证明某个名称不存在。

使用场景

邮件完整性(MX 记录)、服务重定向的 NS 与地址记录、以及任何被攻破就会把用户引向攻击者设施的域名。unbound、systemd-resolved 等递归解析器默认执行验证,运营失误会立即以 SERVFAIL 形式暴露。

常见误区

DNSSEC 只签名不加密,查询与应答仍是明文可观察。签名过期或密钥轮换出错的直接后果是整个域名在验证解析器面前“变暗”,因此监控 RRSIG 到期时间是核心运维任务。NSEC 允许区域遍历而 NSEC3 哈希名称;DNSSEC 数据还会放大响应体积,加剧分片与放大攻击的考量。

代码示例

package main

import (
	"context"
	"fmt"
	"net"
	"sort"
	"time"
)

func Lookup(ctx context.Context, host string) ([]string, error) {
	addresses, err := net.DefaultResolver.LookupHost(ctx, host)
	if err != nil {
		return nil, err
	}
	sort.Strings(addresses)
	return addresses, nil
}

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	addresses, err := Lookup(ctx, "localhost")
	if err != nil {
		fmt.Println("lookup failed:", err)
		return
	}
	for _, address := range addresses {
		fmt.Println(address)
	}
}
export function parseContentDisposition(header) {
  if (typeof header !== "string" || header.trim() === "") {
    throw new TypeError("header must be a non-empty string");
  }
  const segments = splitParameters(header);
  const type = segments.shift().trim().toLowerCase();
  if (type !== "inline" && type !== "attachment") {
    throw new Error(`unsupported disposition type "${type}"`);
  }
  const parameters = new Map();
  for (const segment of segments) {
    const equals = segment.indexOf("=");
    if (equals === -1) continue;
    const name = segment.slice(0, equals).trim().toLowerCase();
    if (name === "") continue;
    parameters.set(name, unquote(segment.slice(equals + 1).trim()));
  }
  return { type, filename: resolveFilename(parameters), parameters };
}

function splitParameters(header) {
  const segments = [];
  let current = "";
  let quoted = false;
  for (const character of header) {
    if (character === '"') quoted = !quoted;
    if (character === ";" && !quoted) {
      segments.push(current);
      current = "";
      continue;
    }
    current += character;
  }
  segments.push(current);
  return segments;
}

function unquote(value) {
  if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
    return value.slice(1, -1).replace(/\\(.)/g, "$1");
  }
  return value;
}

function resolveFilename(parameters) {
  const extended = parameters.get("filename*");
  if (extended !== undefined) {
    const match = /^([\w-]+)'([\w-]*)'(.+)$/.exec(extended);
    if (match !== null) {
      try {
        return sanitize(decodeURIComponent(match[3]));
      } catch {
        return sanitize(extended);
      }
    }
  }
  const plain = parameters.get("filename");
  return plain === undefined ? null : sanitize(plain);
}

function sanitize(filename) {
  return filename.split(/[\\/]/).pop() || null;
}

console.log(parseContentDisposition('attachment; filename="report.pdf"'));
console.log(parseContentDisposition("attachment; filename*=UTF-8''%E4%B8%AD%E6%96%87.pdf"));
console.log(parseContentDisposition('inline; filename="../../etc/passwd"'));