网站常见安全漏洞之SQL注入 | 青训营

106 阅读2分钟

一个网站的基本构成

image.png

  • 前端:JavaScript/Vue/React
  • 网关:Nginx
  • 后端:Java/Go/Node.js/Python
  • 前后端交互:HTTP/Websocket

一些我们经常听到的安全事件

  • 数据泄露
  • 服务瘫痪
  • 因果失窃
  • 系统劫持

网站攻击者

image.png

漏洞的分类

image.png

SQL注入

SQL注入(SQL Injection)是一种常见的网络安全漏洞,攻击者通过在应用程序的输入字段中插入恶意的SQL(结构化查询语言)代码来执行未经授权的数据库操作。这可能会导致对数据库的未授权访问、数据泄露、数据篡改以及其他安全问题。

而造成 SQL 注入的原因是因为程序没有有效过滤用户的输入,使攻击者成功的向服务器提交恶意的 SQL 查询代码,程序在接收后错误的将攻击者的输入作为查询语句的一部分执行,导致原始的查询逻辑被改变,额外的执行了攻击者精心构造的恶意代码。

下面是一个简单的例子

假设用户输入的用户名和密码是通过构建SQL查询进行验证的:

query := "SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "'"

如果攻击者在用户名字段中输入 ' OR '1'='1,并且不输入密码,那么构建的查询将会是:

SELECT * FROM users WHERE username='' OR '1'='1' AND password=''

由于 '1'='1' 总是成立,查询将返回所有用户的记录,攻击者可以绕过身份验证。

如何解决?

使用参数化查询 参数化查询是一种防止SQL注入的有效方法。它的原理是将用户提供的数据作为参数传递给数据库,而不是将其直接嵌入到SQL查询字符串中。这样,数据库会自动处理参数的转义,从而防止恶意输入影响查询。

在Go中,可以使用数据库驱动程序的预处理语句来实现参数化查询。以下是修复SQL注入问题的示例代码:参数化查询是一种防止SQL注入的有效方法。它的原理是将用户提供的数据作为参数传递给数据库,而不是将其直接嵌入到SQL查询字符串中。这样,数据库会自动处理参数的转义,从而防止恶意输入影响查询。

func loginPage(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodPost {
		username := r.FormValue("username")
		password := r.FormValue("password")

		db, err := sql.Open("mysql", "username:password@tcp(localhost:3306)/mydb")
		if err != nil {
			http.Error(w, "Database error", http.StatusInternalServerError)
			return
		}
		defer db.Close()

		query := "SELECT * FROM users WHERE username=? AND password=?"
		rows, err := db.Query(query, username, password)
		if err != nil {
			http.Error(w, "Query error", http.StatusInternalServerError)
			return
		}
		defer rows.Close()

		if rows.Next() {
			fmt.Fprintf(w, "Welcome, %s!", username)
		} else {
			fmt.Fprintln(w, "Invalid credentials")
		}
	} else {
		//..........
	}
}

在这个修复后的示例中,我们使用了参数 username 和 password 来替代原来的字符串插值。这样,数据库驱动会自动处理这些参数,确保它们被正确转义,防止SQL注入。