为什么即使是Go开发者也会落入黑客的圈套?
\n\nGo以其速度、简洁性和内置安全工具而闻名。但用“我用Go写代码,所以我很安全”这样的话来自我安慰,是一种危险的错觉。SQL注入和XSS攻击并非过去式。它们至今仍在每天搞垮网站、泄露数据库,而Go也不例外。
\n\n问题在于,Go为开发者提供了强大的工具,但并不会替你使用它们。你可能会无意中通过fmt.Sprintf拼接SQL查询,或者输出未经转义的用户评论——于是攻击者就能访问你的数据库或窃取访客的Cookie。
在本文中,我们将剖析两种最危险的攻击类型——SQL注入和跨站脚本攻击(XSS)——并展示如何利用原生Go及流行库来防范它们。你将获得的不是理论,而是可以直接插入到当前项目中的现成代码模板。
\n\nGo中的SQL注入:如何产生以及如何彻底消灭
\n\n典型错误:字符串拼接
\n\n假设你有一个根据ID获取用户的端点。编写查询最幼稚(也最危险)的方式如下:
\n\npackage main\n\nimport (\n "database/sql"\n "fmt"\n "log"\n _ "github.com/lib/pq"\n)\n\nfunc getUserByID(db *sql.DB, userID string) {\n // 危险!字符串拼接\n query := fmt.Sprintf("SELECT id, name, email FROM users WHERE id = '%s'", userID)\n rows, err := db.Query(query)\n if err != nil {\n log.Fatal(err)\n }\n defer rows.Close()\n // ... 处理逻辑\n}\n\n如果攻击者向userID传入字符串1' OR '1'='1,查询将变成:
SELECT id, name, email FROM users WHERE id = '1' OR '1'='1'\n\n瞧——他将获取数据库中的所有用户。而如果他输入1'; DROP TABLE users; --,你将丢失整个表。
解决方案:参数化查询(预处理语句)
\n\nGo的PostgreSQL、MySQL和SQLite驱动程序都支持预处理语句。它们将SQL代码与数据分离,驱动程序会自动转义危险字符。
\n\nfunc getUserByIDSafe(db *sql.DB, userID string) {\n // 安全:参数化查询\n query := "SELECT id, name, email FROM users WHERE id = $1"\n rows, err := db.Query(query, userID)\n if err != nil {\n log.Fatal(err)\n }\n defer rows.Close()\n // ... 处理逻辑\n}\n\n注意$1——这是第一个参数的占位符。在MySQL中使用?符号。现在,即使userID包含1' OR '1'='1,驱动程序也会将其视为字符串值,而不是SQL代码的一部分。
使用sqlx包
\n\ngithub.com/jmoiron/sqlx库让代码更加简洁。它支持命名参数并自动扫描到结构体:
import "github.com/jmoiron/sqlx"\n\ntype User struct {\n ID int `db:"id"`\n Name string `db:"name"`\n Email string `db:"email"`\n}\n\nfunc getUserByIDSqlx(db *sqlx.DB, userID string) (*User, error) {\n user := &User{}\n // 命名参数 :id\n err := db.Get(user, "SELECT * FROM users WHERE id = :id", sqlx.Named("id", userID))\n if err != nil {\n return nil, err\n }\n return user, nil\n}\n\n重要提示:即使你使用GORM或Ent等ORM,也请确保其配置为使用预处理语句。某些旧版本的ORM可能允许“原始”查询。
\n\nGo中的XSS:当你的网站搬起石头砸自己的脚
\n\nXSS攻击是如何发生的
\n\nXSS(跨站脚本攻击)发生在你将用户内容插入页面而未进行转义时。恶