Announcement

👇Official Account👇

图片

Welcome to join the group & private message

Article first/tail QR code

Skip to content

10 个 Golang 安全陷阱及真正有效的修复方案

安全漏洞往往源于看似无害的代码。本文揭示 Go 开发中最常见的 10 个安全陷阱。

陷阱 1:SQL 注入

❌ 错误代码

go
func getUser(username string) (*User, error) {
    query := fmt.Sprintf("SELECT * FROM users WHERE username = '%s'", username)
    return db.Query(query)
}

✅ 修复方案

go
func getUser(username string) (*User, error) {
    // 使用参数化查询
    query := "SELECT * FROM users WHERE username = ?"
    return db.Query(query, username)
}

// 或者使用命名参数
func getUserByEmail(email string) (*User, error) {
    query := "SELECT * FROM users WHERE email = :email"
    return db.NamedQuery(query, map[string]interface{}{
        "email": email,
    })
}

陷阱 2:路径遍历

❌ 错误代码

go
func serveFile(w http.ResponseWriter, r *http.Request) {
    filename := r.URL.Query().Get("file")
    data, err := os.ReadFile("/data/" + filename)
    // ...
}

✅ 修复方案

go
func serveFile(w http.ResponseWriter, r *http.Request) {
    filename := r.URL.Query().Get("file")
    
    // 清理路径
    cleanPath := filepath.Clean(filename)
    
    // 确保在允许的目录内
    basePath := "/data"
    fullPath := filepath.Join(basePath, cleanPath)
    
    // 验证路径
    if !strings.HasPrefix(fullPath, basePath) {
        http.Error(w, "invalid path", http.StatusBadRequest)
        return
    }
    
    data, err := os.ReadFile(fullPath)
    // ...
}

陷阱 3:不安全的反序列化

❌ 错误代码

go
func processData(data []byte) {
    var obj interface{}
    json.Unmarshal(data, &obj)
    // 直接使用 obj,没有验证
}

✅ 修复方案

go
type RequestData struct {
    Action string `json:"action" validate:"required,oneof=create update delete"`
    ID     int    `json:"id" validate:"required,min=1"`
    Data   string `json:"data" validate:"required,max=1000"`
}

func processData(data []byte) error {
    var req RequestData
    
    // 使用 Decoder 限制大小
    decoder := json.NewDecoder(bytes.NewReader(data))
    decoder.DisallowUnknownFields()
    
    if err := decoder.Decode(&req); err != nil {
        return fmt.Errorf("invalid json: %w", err)
    }
    
    // 验证数据
    if err := validate.Struct(req); err != nil {
        return fmt.Errorf("validation failed: %w", err)
    }
    
    // 处理数据
    return nil
}

陷阱 4:不安全的随机数

❌ 错误代码

go
func generateToken() string {
    // math/rand 不是加密安全的
    return fmt.Sprintf("%d", rand.Int())
}

✅ 修复方案

go
import "crypto/rand"

func generateToken() (string, error) {
    bytes := make([]byte, 32)
    if _, err := rand.Read(bytes); err != nil {
        return "", err
    }
    return hex.EncodeToString(bytes), nil
}

// 或者使用更简洁的方式
func generateSecureToken() string {
    return uuid.Must(uuid.NewRandom()).String()
}

陷阱 5:敏感信息泄露

❌ 错误代码

go
func handleError(w http.ResponseWriter, err error) {
    // 向客户端暴露内部错误
    http.Error(w, err.Error(), http.StatusInternalServerError)
}

✅ 修复方案

go
func handleError(w http.ResponseWriter, err error, isDev bool) {
    // 记录详细错误到日志
    log.Printf("internal error: %v", err)
    
    // 向客户端返回通用错误
    message := "internal server error"
    
    // 开发环境可以返回更多信息
    if isDev {
        message = err.Error()
    }
    
    http.Error(w, message, http.StatusInternalServerError)
}

陷阱 6:不安全的 HTTP 头

❌ 错误代码

go
func handler(w http.ResponseWriter, r *http.Request) {
    // 没有设置安全头
    w.Write([]byte("Hello"))
}

✅ 修复方案

go
func secureHeaders(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("X-Content-Type-Options", "nosniff")
        w.Header().Set("X-Frame-Options", "DENY")
        w.Header().Set("X-XSS-Protection", "1; mode=block")
        w.Header().Set("Content-Security-Policy", "default-src 'self'")
        w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
        w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
        
        next.ServeHTTP(w, r)
    })
}

陷阱 7:竞态条件

❌ 错误代码

go
type Counter struct {
    count int
}

func (c *Counter) Increment() {
    c.count++ // 非线程安全
}

✅ 修复方案

go
type Counter struct {
    count int64
}

func (c *Counter) Increment() {
    atomic.AddInt64(&c.count, 1)
}

// 或者使用互斥锁
type SafeCounter struct {
    mu    sync.Mutex
    count int
}

func (c *SafeCounter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count++
}

陷阱 8:不安全的文件上传

❌ 错误代码

go
func uploadHandler(w http.ResponseWriter, r *http.Request) {
    file, header, _ := r.FormFile("file")
    defer file.Close()
    
    // 直接使用上传的文件名
    out, _ := os.Create("/uploads/" + header.Filename)
    io.Copy(out, file)
}

✅ 修复方案

go
func uploadHandler(w http.ResponseWriter, r *http.Request) {
    file, header, err := r.FormFile("file")
    if err != nil {
        http.Error(w, "invalid file", http.StatusBadRequest)
        return
    }
    defer file.Close()
    
    // 验证文件类型
    buffer := make([]byte, 512)
    n, _ := file.Read(buffer)
    fileType := http.DetectContentType(buffer[:n])
    
    allowedTypes := map[string]bool{
        "image/jpeg": true,
        "image/png":  true,
        "image/gif":  true,
    }
    
    if !allowedTypes[fileType] {
        http.Error(w, "invalid file type", http.StatusBadRequest)
        return
    }
    
    // 生成安全的文件名
    ext := filepath.Ext(header.Filename)
    safeName := uuid.New().String() + ext
    
    // 限制文件大小
    const maxSize = 10 << 20 // 10MB
    limitedReader := io.LimitReader(file, maxSize)
    
    out, err := os.Create(filepath.Join("/uploads", safeName))
    if err != nil {
        http.Error(w, "upload failed", http.StatusInternalServerError)
        return
    }
    defer out.Close()
    
    if _, err := io.Copy(out, limitedReader); err != nil {
        http.Error(w, "upload failed", http.StatusInternalServerError)
        return
    }
}

陷阱 9:硬编码凭证

❌ 错误代码

go
const (
    DBPassword = "super_secret_password_123"
    APIKey     = "sk-1234567890abcdef"
)

✅ 修复方案

go
// 使用环境变量
type Config struct {
    DBPassword string `env:"DB_PASSWORD,required"`
    APIKey     string `env:"API_KEY,required"`
}

func loadConfig() (*Config, error) {
    var cfg Config
    if err := envconfig.Process("", &cfg); err != nil {
        return nil, err
    }
    return &cfg, nil
}

// 或者使用密钥管理服务
func getSecret(name string) (string, error) {
    // 从 AWS Secrets Manager / HashiCorp Vault 获取
    return secretManager.Get(name)
}

陷阱 10:不安全的重定向

❌ 错误代码

go
func redirectHandler(w http.ResponseWriter, r *http.Request) {
    url := r.URL.Query().Get("url")
    http.Redirect(w, r, url, http.StatusFound)
}

✅ 修复方案

go
func redirectHandler(w http.ResponseWriter, r *http.Request) {
    target := r.URL.Query().Get("url")
    
    // 解析 URL
    u, err := url.Parse(target)
    if err != nil {
        http.Error(w, "invalid url", http.StatusBadRequest)
        return
    }
    
    // 只允许相对路径或白名单域名
    allowedHosts := map[string]bool{
        "example.com": true,
        "www.example.com": true,
    }
    
    if u.IsAbs() && !allowedHosts[u.Host] {
        http.Error(w, "invalid redirect", http.StatusBadRequest)
        return
    }
    
    http.Redirect(w, r, target, http.StatusFound)
}

总结

陷阱风险等级修复要点
SQL 注入参数化查询
路径遍历路径验证
不安全的反序列化输入验证
不安全的随机数crypto/rand
敏感信息泄露错误处理
不安全的 HTTP 头安全头设置
竞态条件同步机制
不安全的文件上传类型检查 + 重命名
硬编码凭证环境变量
不安全的重定向URL 白名单

安全编程需要时刻保持警惕,遵循最佳实践。

上次更新于: