← 中文资源

简体中文版本

子域接管

子域接管认领指向“已释放第三方资源”的悬空 DNS 记录——被删除的 SaaS 租户、未注册的存储桶——然后在你域名下提供攻击者内容。

阅读英文原文

概念定义

主机名是指针加目标。目标失效而指针仍在时,谁认领了目标谁就继承主机名。用户看到的是相同域名、甚至相同的小锁图标,背后却换了主人。

工作机制

攻击者枚举指向服务端点的 CNAME——GitHub Pages、Heroku、S3 静态站点、客服厂商——确认目标无人认领后注册之。这里承载的内容带着你的域名进入钓鱼、旧式 Cookie 作用域与 OAuth 重定向白名单的误配。

使用场景

下线流程必须原子地同时移除 DNS 与厂商资源;定期扫描把 CNAME 目标与“可认领资源指纹库”比对,赶在攻击者之前对悬空记录告警。

常见误区

删除资源不等于删除风险——留在 DNS 里的记录才是漏洞。NXDOMAIN 也不是判定信号,目标是“CNAME 指向未认领”;部分接管类型需要特定响应且依赖厂商版本,自动指纹库才跟得上。厂商会自动为认领资源签发证书,CT 日志会暴露接管时间线;分片在子域上的客户继承每次接管的波及面。

代码示例

import re
from pathlib import PurePosixPath
from urllib.parse import unquote

NAME_STAR = re.compile(r"filename\*\s*=\s*([^;]+)", re.IGNORECASE)
NAME = re.compile(r'filename\s*=\s*(?:"([^"]*)"|([^;]+))', re.IGNORECASE)


def safe_basename(value: str) -> str | None:
    candidate = PurePosixPath(value.replace("\\", "/")).name.strip()
    if candidate in {"", ".", ".."} or "\x00" in candidate:
        return None
    return candidate


def attachment_filename(header: str) -> str | None:
    match = NAME_STAR.search(header)
    if match:
        value = match.group(1).strip().strip('"')
        charset, _, remainder = value.partition("'")
        _, _, encoded = remainder.partition("'")
        try:
            decoded = unquote(
                encoded or remainder,
                encoding=charset or "utf-8",
                errors="strict",
            )
        except (LookupError, UnicodeDecodeError):
            decoded = unquote(encoded or remainder)
        return safe_basename(decoded)
    match = NAME.search(header)
    if not match:
        return None
    value = match.group(1) if match.group(1) is not None else match.group(2)
    return safe_basename(unquote(value.strip()))


def main() -> None:
    headers = [
        'attachment; filename="report final.pdf"',
        "attachment; filename=../../etc/passwd",
        "attachment; filename*=UTF-8''%E5%B9%B4%E6%8A%A5.pdf",
        'attachment; filename=".."',
    ]
    for header in headers:
        print(attachment_filename(header))


if __name__ == "__main__":
    main()
package main

import (
	"crypto/sha256"
	"crypto/subtle"
	"fmt"
)

func ConstantTimeEqual(a, b string) bool {
	left := sha256.Sum256([]byte(a))
	right := sha256.Sum256([]byte(b))
	return subtle.ConstantTimeCompare(left[:], right[:]) == 1
}

func main() {
	secret := "s3cr3t-token"
	fmt.Println(ConstantTimeEqual(secret, "s3cr3t-token"))
	fmt.Println(ConstantTimeEqual(secret, "s3cr3t-tokeX"))
	fmt.Println(ConstantTimeEqual(secret, "short"))
}