一、关于SQL注入

sql注入是目前web应用中一种常见的攻击方式,通过恶意构造参数生成不可预期的sql语句,来完成不可告人的秘密。危害极大!它的影响主要有以下两点:

第一:拖库,拖库的意思是直接把整个数据表甚至库中的数据都拖出来了。当今的互联网环境中,数据毫无疑问在任何公司都是最宝贵的财富,一旦数据泄露,轻者造成经济损失,重者可能造成法律责任。

第二:删库,拖库的危害可能只是和他人共享了劳动成果,而删库就不同了,数据被共享了不说,还把数据都删了。这就是典型的——走别人的路,让别人无路可走!

近期闹得沸沸扬扬的“微盟删库”事件,因为运维人员把数据库删了,导致业务接近一周都没有恢复,股价直接下跌10+亿。可见“删库”的危害实在太大!

sql注入示例

数据表user_info保存了用户相关的信息,库中包含了三条数据:

ysql> select * from user_info;
+----+--------+-----+
| id | name   | age |
+----+--------+-----+
|  1 | maqian |  25 |
|  2 | nginx  |  10 |
|  3 | redis  |  15 |
+----+--------+-----+
3 rows in set (0.00 sec)

后台服务中接口get_info可以通过用户的id来获取用户信息,部分代码如下:

func handler(w http.ResponseWriter, r *http.Request) {
    // ...
    id := r.FormValue("id")
    sqlClause := fmt.Sprintf("select name, age from user_info where id = %s", id)
    // ...
    rows, err := db.Query(sqlClause)
    // ...
    data, _ := json.marshal(rs)
    w.write(data)
}

代码的逻辑是:先通过参数中的id构造出sql语句,然后到数据库中查询数据,最后再返回json到前端。

例如请求返回id等于1的用户信息:

可以看到是能正常获取到用户信息的,符合功能预期。但是如果把id的值改成1 or 1,此时构造出来的sql语句就是:

select name, age from user_info where id = 1 or 1

执行就会把库中所有用户的信息都打印出来,这就完成了一次拖库:

更有甚者,如果把id改成1; drop table user_info;,构造出来的sql语句就是:

select name, age from user_info where id = 1; drop table user_info;

查询完数据之后直接调用了drop表了,成功删库!

二、避免sql注入

2.1 安全检查

安全检查要求程序员在写代码时不要相信任何外部输入,所有来自外部输入的数据都进行关键字检查,过滤掉可能存在注入的字符。对不符合要求的数据直接不处理。

这也是常用的方式,但是不很稳妥,因为攻击的方式太多,各种奇怪的组合都有可能,很容易忽略掉一些场景。

2.2 参数化查询

参数化查询是数据库层面提供的避免sql注入的方式,是目前最有效的避免sql注入的方法。它通过提前预编译sql语句,所有的参数通过占位符(@/?等,mysql是?,mssql是@)来表示,避免执行输入的参数。

例如,在golang中,执行sql语句之前可以先通过prepare来编译语句,然后再执行语句:

stmt, err := db.Prepare(sqlClause)
if err != nil {
    return nil, err
}
defer stmt.Close()

rs, err := stmt.Exec(val...)
// ...
最后修改:2020 年 08 月 23 日
喜欢就给我点赞吧