使用 database/sql 预处理语句可防 SQL 注入,关键在于用参数化查询(如 WHERE name = ?)替代字符串拼接,且需注意 IN 子句和动态列名等场景仍存在风险。

用 database/sql 配合预处理语句防 SQL 注入
Echo 本身不处理数据库,但你用它写 API 时大概率会搭配 database/sql + mysql 或 postgres 驱动。关键点是:**永远别用字符串拼接构造 SQL 查询**。比如这样写就是危险的:
username := c.Param("user")
query := "SELECT * FROM users WHERE name = '" + username + "'"
rows, _ := db.Query(query) // ❌ 直接拼接,SQL注入入口
正确做法是使用 Query / Exec 的参数化版本,让驱动自动处理转义:
username := c.Param("user")
rows, err := db.Query("SELECT * FROM users WHERE name = ?", username) // ✅ 参数占位符
if err != nil { /* 处理错误 */ }
注意:mysql 驱动默认启用预处理(interpolateParams=true 时例外,应禁用),而 postgres 驱动强制走二进制协议,天然支持参数化。别碰 fmt.Sprintf 拼 SQL,也别信“我只接收数字就安全”——类型转换必须在绑定前完成,而不是靠 SQL 字符串里硬塞 INT。
输出 HTML 前必须用 html.EscapeString 或模板自动转义
如果你用 html/template 渲染页面(比如返回商品详情页),它默认会对 .Name 这类变量做上下文敏感转义,这是安全的:
{{.ProductName}} → 自动转义 <script> → <script>
</script>
但一旦你手动拼接 HTML 字符串再 c.String 返回,就完全绕过保护:
c.String(200, "<div>"+userInput+"</div>") // ❌ userInput 含 <script> 就执行了 </script>
此时必须显式转义:
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
import "html" c.String(200, "<div>"+html.EscapeString(userInput)+"</div>") // ✅
补充提醒:text/template 不自动转义 HTML,url.PathEscape 用于路径段,url.QueryEscape 用于 URL 查询参数——别混用。
CSRF 和 XSS 防护要靠中间件 + 响应头,不是 Echo 自带的
Echo 没有内置 CSRF Token 管理或 XSS 过滤中间件。你要自己加:
- 对表单提交接口,用
github.com/gorilla/csrf中间件生成并校验 token,配合SetCookie写入HttpOnly+SameSite=Strict的 cookie - 所有响应加
X-XSS-Protection: 1; mode=block和Content-Security-Policy,例如:c.Response().Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline'")(注意:生产环境慎用unsafe-inline) - 敏感 Cookie 必须设
HttpOnly和Secure(HTTPS 下)
别依赖前端 JS 去过滤输入——XSS 是服务端渲染阶段的事,前端验证只是辅助。
别忽略错误信息泄露和日志记录方式
开发时 c.Logger 可能打印完整 SQL 错误,比如 ERROR: relation "users" does not exist,这直接暴露表结构。上线前必须:
- 关闭详细错误回显:
c.Logger.SetLevel(log.OFF)或自定义错误处理器,只返回通用提示 - 数据库错误统一映射为 500,并记录到独立审计日志(含时间、IP、请求路径、参数摘要),**不记录原始 SQL 或用户数据**
- 用
log.Printf记录异常时,避免把err.Error()直接写进日志——有些驱动会把 SQL 语句塞进 error message 里
最易被忽略的是:你以为用了预处理就万事大吉,但若在 WHERE IN (?) 场景下动态拼接多个问号,或者用反射把 struct 字段名当列名拼 SQL,照样中招。参数化只保“值”,不保“结构”。










