0xd00 随笔小记

Back

1. SQL注入#

Sink点#

  • database/sql.DB.Query()
  • database/sql.DB.QueryRow()
  • database/sql.DB.Exec()
  • database/sql.Stmt.Query()
  • database/sql.Stmt.QueryRow()
  • database/sql.Stmt.Exec()
  • GORM Where()
  • GORM Raw()
  • GORM Order()

审计检查项#

  • 是否使用?占位符进行参数化
  • SQL语句中是否存在字符串拼接
  • 用户输入是否直接进入SQL
  • 是否使用Prepare()进行预处理
  • GORM Where()是否参数化形式
  • GORM Raw()是否与用户拼接
  • GORM Order()是否有白名单
  • 无法参数化部分是否验证

风险代码模式#

// 模式1:字符串拼接
username := r.FormValue("username")
query := "SELECT * FROM users WHERE username = '" + username + "'"
rows, err := db.Query(query)
go
// 模式2:GORM Raw拼接
filter := r.FormValue("filter")
var user User
db.Raw("SELECT * FROM users WHERE username = '" + filter + "'").Scan(&user)
go
// 模式3:GORM Where拼接
db.Where("username = '" + username + "'").First(&user)
go
// 模式4:ORDER BY未验证
orderBy := r.FormValue("orderBy")
db.Order(orderBy).Find(&users)
go

安全实现#

// 方案1:参数化查询
username := r.FormValue("username")
var user User
err := db.QueryRow(
    "SELECT id, username FROM users WHERE username = ?",
    username,
).Scan(&user.ID, &user.Username)
go
// 方案2:GORM参数化
db.Where("username = ?", username).First(&user)
go
// 方案3:GORM Order白名单
orderBy := r.FormValue("orderBy")
allowedFields := map[string]bool{
    "id": true, "username": true, "email": true, "created_at": true,
}
if !allowedFields[orderBy] {
    http.Error(w, "Invalid field", 400)
    return
}
db.Order(orderBy).Find(&users)
go
// 方案4:Prepare预处理
stmt, err := db.Prepare("SELECT * FROM users WHERE username = ?")
if err != nil {
    log.Fatal(err)
}
defer stmt.Close()

rows, err := stmt.Query(username)
go

2. 命令执行#

Sink点#

  • os/exec.Command()
  • os/exec.CommandContext()
  • os/exec.LookPath()

审计检查项#

  • 第二个参数(args)是否包含用户输入
  • 第一个参数(程序名)是否来自用户
  • 是否使用了绝对路径
  • 是否实现了命令白名单
  • 参数是否进行了验证

风险代码模式#

// 模式1:用户输入直接作为命令
cmd := r.FormValue("cmd")
out, err := exec.Command(cmd).CombinedOutput()
go
// 模式2:通过shell执行用户输入
cmd := r.FormValue("cmd")
out, err := exec.Command("sh", "-c", cmd).CombinedOutput()
go
// 模式3:从用户输入确定可执行程序
program := r.FormValue("program")
out, err := exec.Command(program, "arg").CombinedOutput()
go
// 模式4:参数包含用户输入但未验证
hostname := r.FormValue("host")
out, err := exec.Command("ping", "-c", "4", hostname).CombinedOutput()
go

安全实现#

// 方案1:命令白名单
cmd := r.FormValue("cmd")
allowedCommands := map[string]bool{
    "whoami": true,
    "date":   true,
    "pwd":    true,
}

if !allowedCommands[cmd] {
    http.Error(w, "Command not allowed", 400)
    return
}

out, err := exec.Command(cmd).CombinedOutput()
fmt.Fprint(w, string(out))
go

3. 文件上传和任意文件写入#

Sink点#

  • os.Create()
  • os.OpenFile()
  • ioutil.WriteFile()
  • os.WriteFile()
  • io.Copy()

审计检查项#

  • 扩展名是否进行白名单验证
  • 是否防止了.go.exe等文件
  • MIME类型是否经过验证
  • 文件名是否包含路径分隔符
  • 是否生成了新文件名
  • 最终路径是否在预期目录内

风险代码模式#

// 模式1:直接使用上传文件名
file, header, _ := r.FormFile("upload")
defer file.Close()

dst, _ := os.Create("uploads/" + header.Filename)
io.Copy(dst, file)
go
// 模式2:仅检查扩展名
if !strings.HasSuffix(header.Filename, ".jpg") {
    return
}
// 但shell.jpg.go可绕过
go

安全实现#


4. 任意文件读取#

Sink点#

  • os.Open()
  • ioutil.ReadFile()
  • os.ReadFile()
  • io.ReadAll()
  • bufio.Scanner

审计检查项#

  • 用户参数是否直接作为文件路径
  • 是否使用filepath.Base()
  • 是否使用filepath.Abs()
  • 最终路径是否在允许目录内

安全实现#


5. 路径遍历#

Sink点#

  • 文件操作

审计检查项#

  • 路径是否包含用户输入
  • 是否使用filepath.Clean()
  • 规范化后路径是否在基础目录内

安全实现#

func validatePath(userPath string) (string, error) {
    baseDir := "downloads"
    filepath := filepath.Join(baseDir, userPath)
    filepath = filepath.Clean(filepath)
    
    absPath, _ := filepath.Abs(filepath)
    absBaseDir, _ := filepath.Abs(baseDir)
    
    if !strings.HasPrefix(absPath, absBaseDir) {
        return "", fmt.Errorf("path traversal detected")
    }
    
    return absPath, nil
}
go

6. XXE (XML External Entity)#

Sink点#

  • encoding/xml.Unmarshal()
  • encoding/xml.NewDecoder()
  • encoding/xml.Decoder.Decode()

审计检查项#

  • 是否对不可信XML进行解析
  • 是否禁用了外部实体
  • DOCTYPE声明是否被检查

说明:Go的encoding/xml默认不解析外部实体,相对安全。

安全实现#

xmlData := r.FormValue("xml")

// 可选:检查并拒绝DOCTYPE
if strings.Contains(xmlData, "<!DOCTYPE") {
    http.Error(w, "DOCTYPE not allowed", 400)
    return
}

decoder := xml.NewDecoder(strings.NewReader(xmlData))
var data MyStruct
err := decoder.Decode(&data)
go

7. SSRF (Server-Side Request Forgery)#

Sink点#

  • net.Dial()
  • http.Get()
  • http.Post()
  • http.Client.Do()
  • net.LookupIP()

审计检查项#

  • 是否允许用户指定URL
  • 是否检测内部地址(127.0.0.1)
  • 是否限制了协议(仅HTTP/HTTPS)
  • 是否防止了DNS重绑定
  • 域名是否在白名单内

安全实现#


8. 模板注入#

Sink点#

  • text/template.Parse()
  • html/template.Parse()
  • text/template.Execute()
  • html/template.Execute()

审计检查项#

  • 是否允许用户编辑模板
  • 是否使用text/template
  • 是否使用html/template
  • 是否将用户输入作为模板

风险代码模式#

// 模式1:用户可控制模板
tplContent := r.FormValue("template")
tpl, _ := template.New("user").Parse(tplContent)
tpl.Execute(w, data)
go
// 模式2:使用text/template(无XSS防护)
import "text/template"
// <script> 会直接输出
go

安全实现#

import "html/template"

// 方案1:从文件加载
tpl, _ := html.template.ParseFiles("templates/index.html")
tpl.Execute(w, data)

// 方案2:使用html/template(自动转义)
tpl, _ := html.template.New("safe").Parse("<div>{{.UserInput}}</div>")
// UserInput中的<script>会被转义

// 方案3:显式转义
import "html"
safeTxt := html.EscapeString(userInput)
fmt.Fprintf(w, "<div>%s</div>", safeTxt)
go

9. 其他漏洞#

XSS#

Sink点fmt.Fprint()

检查项:使用html/template自动转义

开放重定向#

Sink点http.Redirect()

检查项:验证重定向URL


10. Go语言安全特性#

特性说明
强类型编译编译期类型检查
默认XML安全encoding/xml不解析外部实体
反序列化安全不自动调用方法
内存安全垃圾回收机制

注意事项#

  • Windows下os/exec的PATH搜索问题
  • text/template缺乏XSS防护,应使用html/template
  • 第三方库可能有漏洞

11. 审计工具#

  • 静态分析:golangci-lint、gosec、SonarQube、Semgrep
  • 动态检测:OWASP ZAP、Burp Suite
  • 依赖检查:go mod audit(Go 1.21+)、nancy、Snyk
  • 官方工具:govulncheck
Go 代码审计 - 漏洞Sink点
https://blog.0xd00.com/blog/go_audit
Author 0xd00
Published at 2025年12月18日
Comment seems to stuck. Try to refresh?✨