1、首先我们了解一下什么叫SQL注入问题

SQL注入是一个很古老的系统安全问题,黑客可以通过构造字符串,尝试改变实际执行的SQL语句,从而达到绕过系统认证,或者提取系统中自己没有权限的数据来脱库。出现这个问题的根本原因是开发者在使用SQL的时候,采用的是拼接字符串的方式来实现SQL语句的参数传值,当然这种问题在ORM框架盛行的今天其实出现概率已经很小了,具体例子如下:

  • SQL注入绕过认证原理,如下代码就存在绕过认证的安全问题:

  • 正常情况下,使用正确和错误的用户名和密码都能够限制认证

       数库数据:

# 建立数据库连接 def login(userName, userPwd):     connection = pymysql.connect(host='127.0.0.1', user='root', password="root",                                  database='newtest', port=3306, charset='utf8')     # 获取游标对象     cursor = connection.cursor()     # sql = "select * from t_user where username='%s' and userpwd='%s'" % (userName, userPwd)     sql = "select * from t_user where username='"+ userName +"' and userpwd='" + userPwd +"'"     print(sql)     # 执行查询操作     result = cursor.execute(sql)     connection.commit()     userInfo = cursor.fetchone()     connection.close() #     return userInfo
# 普通登录,正确的用户名和密码登录成功 user = login('xiaojiejie', '123456') if user:     print("登录成功")  # 普通登录,错误的用户名或密码登录失败 user = login('xiaojiejie', '111111') if user:     print("登录成功") else:     print("登录失败")

如下,第一次正确的用户名和密码,提示成功,第二密码是错误的,提示失败

  • SQL注入情况,通过传入特殊构造的参数,从而达到改变实际执行SQL语句绕过认证的目的
# 构造特殊的字符串,达到SQL注入的目的 user = login("xiaojiejie", "1' or '1'='1") if user:     print("绕过认证,登录成功") else:     print("登录失败")

以上在传入参数的时候,密码特殊处理了,1' or '1'='1,从而使得密码和原始SQL在字符串拼接的时候,构成了如下的SQL

select * from t_user where username='xiaojiejie' and userpwd='1' or '1'='1'

引入 or '1'='1'是恒成真的,所以可以绕过认证。

问题的根本原因就在于SQL语句:

 sql = "select * from t_user where username='"+ userName +"' and userpwd='" + userPwd +"'" 

是通过字符串拼接出来的,这样就让一些别有用心的人有机可趁。

 

2、SQL语句参数化是解决这类问题的通用方案

SQL语句参数化是数据库技术里通用的解决SQL注入的最佳解决方案,其实就是在程序向数据库发送SQL执行的时候将SQL语句和参数分开传递,需要补充的动态参数在SQL语句中使用占位符占位,然后在数据库端在填入参数执行,这样既规避了SQL注入的问题,同时也一定程度提高了数据库执行SQL的效率。就像Java中JDBC支持preparedStatement,Python也支持SQL参数化。实现如下:

def loginParams(params=[]):     connection = pymysql.connect(host='127.0.0.1', user='root', password="root",                                  database='newtest', port=3306, charset='utf8')     # 获取游标对象     cursor = connection.cursor()     sql = "select * from t_user where username=%s and userpwd=%s"     print(sql)     # 执行查询操作     result = cursor.execute(sql, params)     connection.commit()     userInfo = cursor.fetchone()     connection.close() #     return userInfo 
  • 再次尝试绕过认证:

# 构造特殊的字符串,参数化后无法达到SQL注入的目的 user = loginParams(["xiaojiejie", "1' or '1'='1"]) print(user) if user:     print("绕过认证,登录成功") else:     print("绕过认证,登录失败")

绕过失败:

通常Web系统都可以使用id来获得用户的个人信息,那么如果要看到别人的个人信息或者系统中所有用户的信息,就是一个拖库方法,SQL具有采用字符串拼接也会引起这样的安全问题,例如:

def profile(userId):     connection = pymysql.connect(host='127.0.0.1', user='root', password="root",                                  database='newtest', port=3306, charset='utf8')     # 获取游标对象     cursor = connection.cursor()     sql = "select * from t_user where userid=" +str(userId)     print(sql)     # 执行查询操作     result = cursor.execute(sql)     connection.commit()     userInfo = cursor.fetchall()     connection.close()  #     return userInfo     pass  userInfo = profile(1) print(userInfo)

根据个人userid能够读取自己的信息

对于一个普通用户来说,他并不具读取其他用户的个人信息的权限,我们改一下参数:

# 越权读取了表中全部用户的信息 userInfo = profile("1 or 1=1") print(userInfo)

以上可以看到执行的通过传入的参数,实际执行的SQL是:

select * from t_user where userid=1 or 1=1

从而实现了拖库,是不是很危险