218 lines
7.0 KiB
Markdown
218 lines
7.0 KiB
Markdown
|
|
---
|
|||
|
|
tags: [计算机网络, SSRF, DNS安全, DNSSEC, DoH, NAT穿透]
|
|||
|
|
create time: 2026-05-18 05:00
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# SSRF 与 DNS 安全
|
|||
|
|
|
|||
|
|
## 概述
|
|||
|
|
|
|||
|
|
SSRF(服务端请求伪造)是近年来增长最快的 Web 漏洞之一,它利用的是"服务器代替用户发起请求"的能力。DNS 层面则有从传统查询到 DoH/DoT 的安全演进。
|
|||
|
|
|
|||
|
|
## SSRF(Server-Side Request Forgery)
|
|||
|
|
|
|||
|
|
### 什么是 SSRF?
|
|||
|
|
|
|||
|
|
当应用程序接受一个 URL 作为输入,并在服务端向该 URL 发起请求时,攻击者可以构造特殊 URL 让服务器访问**内部资源**(内网 API、元数据服务、数据库端口等)。
|
|||
|
|
|
|||
|
|
### 典型攻击场景
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// ❌ 危险代码:用户控制 URL,服务器代替用户发起请求
|
|||
|
|
func fetchImageHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
|
url := r.URL.Query().Get("url") // 用户可传入内网地址!
|
|||
|
|
resp, err := http.Get(url) // GET http://169.254.169.254/latest/meta-data/
|
|||
|
|
if err != nil {
|
|||
|
|
http.Error(w, err.Error(), 500)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
defer resp.Body.Close()
|
|||
|
|
io.Copy(w, resp.Body)
|
|||
|
|
// AWS EC2 返回: access_key, secret_key, role... 💀
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### SSRF 常见探测目标
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
目标 说明 危害
|
|||
|
|
────────────────────── ─────────────────────── ────────────────────
|
|||
|
|
169.254.169.254 AWS/Azure/GCP 元数据 获取云实例凭证
|
|||
|
|
10.0.0.x / 172.16.x.x 内网扫描 发现内网服务
|
|||
|
|
file:///etc/passwd 文件读取协议 读取服务器敏感文件
|
|||
|
|
gopher:// / dict:// Gopher/Dict 协议 利用回显放大攻击
|
|||
|
|
docker.sock Docker Socket 远程执行容器命令
|
|||
|
|
localhost:6379 Redis/Memcached 内网渗透跳板
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 安全的 SSRF 防御方案
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
package main
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"errors"
|
|||
|
|
"net"
|
|||
|
|
"net/http"
|
|||
|
|
"net/url"
|
|||
|
|
"time"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
var safeClient = &http.Client{
|
|||
|
|
Timeout: 10 * time.Second,
|
|||
|
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
|||
|
|
return errors.New("no redirect") // 禁止重定向!攻击者可以用 302 绕校验
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// SafeFetch 安全地从一个 URL 获取内容
|
|||
|
|
func SafeFetch(urlStr string) ([]byte, error) {
|
|||
|
|
parsed, err := url.Parse(urlStr)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 第一步:解析域名,拿到 IP
|
|||
|
|
ips, err := net.LookupIP(parsed.Host)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 第二步:检查所有可能的 IP(IPv4 + IPv6)
|
|||
|
|
for _, ip := range ips {
|
|||
|
|
if isUnsafeIP(ip) {
|
|||
|
|
return nil, errors.New("unsafe IP blocked: " + ip.String())
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 第三步:额外检查 Host 字段(防止 DNS rebinding 攻击)
|
|||
|
|
if hostIP := net.ParseIP(parsed.Host); hostIP != nil {
|
|||
|
|
if isUnsafeIP(hostIP) {
|
|||
|
|
return nil, errors.New("host IP unsafe: " + hostIP.String())
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
resp, err := safeClient.Get(urlStr)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
defer resp.Body.Close()
|
|||
|
|
|
|||
|
|
body, _ := io.ReadAll(resp.Body)
|
|||
|
|
return body, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func isUnsafeIP(ip net.IP) bool {
|
|||
|
|
if ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast() ||
|
|||
|
|
ip.IsLinkLocalMulticast() || ip.IsUnspecified() {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
// 额外检查:云元数据地址段
|
|||
|
|
if ip.Equal(net.ParseIP("169.254.169.254")) {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
// Nginx 层面的 SSRF 防御(反向代理入口)
|
|||
|
|
location /api/fetch {
|
|||
|
|
# 禁止直接访问内网
|
|||
|
|
internal; # 只允许内部 rewrite 访问
|
|||
|
|
|
|||
|
|
proxy_pass $arg_url;
|
|||
|
|
proxy_hide_header Access-Control-Allow-Origin;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> [!warning] DNS Rebinding 攻击
|
|||
|
|
>
|
|||
|
|
> 即使你检查了初始 IP,攻击者仍可以将 DNS TTL 设为极小值(如 0 秒),让你的代码先校验合法 IP,然后在第二次请求时 DNS 已返回内网 IP。
|
|||
|
|
> **应对策略**:在整个请求生命周期中持续校验;使用白名单域名而非黑名单 IP;禁用重定向。
|
|||
|
|
|
|||
|
|
## DNS 安全
|
|||
|
|
|
|||
|
|
### DNSSEC —— 给 DNS 加上数字签名
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
传统 DNS(明文无签名): DNSSEC(带签名验证):
|
|||
|
|
Resolver → authoritative → answer Resolver → authoritative → answer + SIG
|
|||
|
|
Resolver 用 DNSKEY 验证签名 ✅
|
|||
|
|
|
|||
|
|
如果签名不对 → 返回 SERVFAIL ❌
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 检查某域名的 DNSSEC 状态
|
|||
|
|
$ dig +dnssec example.com
|
|||
|
|
;; flags: ad; AD 表示 Answer data verified by DNSSEC ✅
|
|||
|
|
|
|||
|
|
# 本地递归解析器配置(bind / unbound)
|
|||
|
|
# /etc/bind/named.conf
|
|||
|
|
dnssec-validation auto; # bind 自动下载根密钥
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### DoH / DoT / DoQ —— 加密 DNS 查询
|
|||
|
|
|
|||
|
|
| 协议 | 载体 | 端口 | 特点 |
|
|||
|
|
|------|------|------|------|
|
|||
|
|
| **DNS over TLS (DoT)** | TCP + TLS | 853 | RFC 7858, 传输层加密 |
|
|||
|
|
| **DNS over HTTPS (DoH)** | HTTP/2 + TLS | 443 | RFC 8484, 伪装成 HTTPS 流量 |
|
|||
|
|
| **DNS over QUIC (DoQ)** | QUIC | 853 | 最新标准, 低延迟 + 抗干扰 |
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# curl 测试 DoH
|
|||
|
|
$ curl -v https://dns.google/resolve?name=example.com&type=A \
|
|||
|
|
-H 'accept: application/dns-json'
|
|||
|
|
* TLS 1.3 connection using TLS_AES_256_GCM_SHA384
|
|||
|
|
* DNS query goes over encrypted HTTPS channel ✅
|
|||
|
|
|
|||
|
|
# unbound 配置 DoT 递归
|
|||
|
|
# /etc/unbound/unbound.conf.d/doth-forwarders.conf
|
|||
|
|
forward-zone:
|
|||
|
|
name: "."
|
|||
|
|
forward-ssl-upstream: yes
|
|||
|
|
forward-addr: 1.1.1.1@853 # Cloudflare DoT
|
|||
|
|
forward-addr: 8.8.8.8@853 # Google DoT
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### DHCP 安全注意事项
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
DHCP DORA 四步本身是无认证的:
|
|||
|
|
Discover → Offer → Request → Acknowledge
|
|||
|
|
|
|||
|
|
风险:
|
|||
|
|
• Rogue DHCP Server:恶意 DHCP 分配错误的网关/DNS
|
|||
|
|
• DHCP Starvation:耗尽 DHCP 地址池
|
|||
|
|
|
|||
|
|
防御:
|
|||
|
|
• Switch 端口安全:DHCP Snooping
|
|||
|
|
• 802.1X 认证:未认证的端口不参与 DHCP
|
|||
|
|
• 静态绑定:关键设备用 static IP + DHCP Reservation
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## NAT 穿透技术补充
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart TD
|
|||
|
|
STUN["STUN<br/>穿越 NAT 发现自己对外暴露的地址/port"] --> ICE
|
|||
|
|
TURN["TURN<br/>中继服务器转发流量<br/>兜底方案"] --> ICE
|
|||
|
|
ICE["ICE 框架<br/>按优先级尝试: direct → STUN → TURN"] --> SUCCESS["✅ 建立 P2P 连接"]
|
|||
|
|
|
|||
|
|
style STUN fill:#98FB98,color:#000
|
|||
|
|
style TURN fill:#FFD700,color:#000
|
|||
|
|
style SUCCESS fill:#DDA0DD,color:#000
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- **STUN**:客户端向 STUN 服务器发请求,拿到自己的公网 IP:Port。但对称型 NAT 下 STUN 失效。
|
|||
|
|
- **TURN**:所有流量经中继服务器中转,兼容性最好但延迟最高。
|
|||
|
|
- **ICE**:WebRTC 的标准框架,同时收集候选地址并按连通性排序。
|
|||
|
|
|
|||
|
|
## 关联笔记
|
|||
|
|
|
|||
|
|
- [[hhs/NETWORK/NAT原理与应用]] — NAT 穿透技术(STUN/TURN/ICE)的详细原理
|
|||
|
|
- [[hhs/NETWORK/DNS与DHCP与WebSocket]] — DNS 递归查询与 DoH/DoT 的完整实现
|
|||
|
|
- [[hhs/NETWORK/04-寻址体系MAC-IPTPort]] — 私有 IP 地址范围用于 SSRF 校验判断
|