什么是SQL注入?如何防止SQL注入?

一、SQL注入

1、什么是SQL注入?

SQL注入是比较常见的网络攻击方式之一,主要攻击对象是数据库,针对程序员编写时的疏忽,通过SQL语句,实现无账号登录,篡改数据库。

SQL注入简单来说就是通过在表单中填写包含SQL关键字的数据来使数据库执行非常规代码的过程。

SQL数据库的操作是通过SQL语句来执行的,这就导致如果我们在代码中加入了某些SQL语句关键字(比如说DELETE、DROP等),这些关键字就很可能在数据库写入或读取数据时得到执行。

2、SQL注入攻击的总体思路 

  1. 寻找到SQL注入的位置;
  2. 判断服务器类型和后台数据库类型;
  3. 针对不同的服务器和数据库特点进行SQL注入攻击。

3、SQL注入案例来看一个SQL注入的案例。正常代码

import sqlite3

# 连接数据库 
conn = sqlite3.connect(‘test.db’)

# 建立新的数据表
conn.executescript(”’DROP TABLE IF EXISTS students;        

CREATE TABLE students        (id INTEGER PRIMARY KEY AUTOINCREMENT,  name TEXT NOT NULL);”’

# 插入学生信息 
students = [‘Paul’,’Tom’,’Tracy’,’Lily’] for name in students:     
query = “INSERT INTO students (name) VALUES (‘%s’)” % (name) 

conn.executescript(query);

# 检视已有的学生信息 
cursor = conn.execute(“SELECT id, name from students”) 
print(‘IDName’) for row in cursor:  print(‘{0}{1}’.format(row[0], row[1]))
conn.close()

SQL注入代码

# 连接数据库
conn = sqlite3.connect(‘test.db’) 

# 插入包含注入代码的信息 name = “Robert’);
DROP TABLE students;–” 
query = “INSERT INTO students (name) VALUES (‘%s’)” % (name)
conn.executescript(query)

# 检视已有的学生信息 
cursor = conn.execute(“SELECT id, name from students”) 
print(‘IDName’) for row in cursor:     
print(‘{0}{1}’.format(row[0], row[1]))     
conn.close()

上述代码执行其后果可想。


二、如何防止SQL注入

但凡有SQL注入漏洞的程序,都是因为程序要接受来自客户端用户输入的变量或URL传递的参数,并且这个变量或参数是组成SQL语句的一部分,对于用户输入的内容或传递的参数,我们应该要时刻保持警惕,这是安全领域里的「外部数据不可信任」的原则。纵观Web安全领域的各种攻击方式,大多数都是因为开发者违反了这个原则而导致的,所以自然能想到的,就是从变量的检测、过滤、验证下手,确保变量是开发者所预想的。


1、检查变量数据类型和格式

如果SQL语句是类似where id={$id}这种形式,数据库里所有的id都是数字,那么就应该在SQL被执行前,检查确保变量id是int类型;如果是其他的类型比如日期、时间等也是一个道理。只要是有固定格式的变量,在SQL语句执行前,应该严格按照固定格式去检查,确保变量是我们预想的格式,这样很大程度上可以避免SQL注入攻击。

2、过滤特殊符号

对于无法确定固定格式的变量,一定要进行特殊符号过滤或转义处理。

3、绑定变量,使用预编译语句

实际上,绑定变量使用预编译语句是预防SQL注入的最佳方式,使用预编译的SQL语句语义不会发生改变,在SQL语句中,变量用问号?表示,黑客即使本事再大,也无法改变SQL语句的结构。

三、为什么SQL预编译能有效防御SQL注入

1、预编译语句是什么?

一条sql在db接收到最终执行完毕返回可以分为下面三个过程:

  1. 词法和语义解析;
  2. 优化sql语句,制定执行计划;
  3. 执行并返回结果。

很多情况,同一类型的sql语句可能会反复执行,如果每次都需要经过上面的词法语义解析、语句优化、制定执行计划等,不但影响执行效率也不安全。

所谓预编译语句就是将这类语句中的值用占位符替代,可以视为将sql语句模板化或者说参数化,一般称这类语句叫Prepared Statements。

预编译语句的优势在于归纳为:一次编译、多次运行,省去了解析优化等过程;此外预编译语句能防止sql注入。

2、为什么Statement会被sql注入?

Statement之所以会被sql注入是因为SQL语句结构发生了变化。
比如:

"select * from tablename where username='"+uesrname+  "'and password='"+password+"'"

在用户输入’or true or’之后sql语句结构改变。

  • select * from tablename where username=”or true or” and password=”

这样本来是判断用户名和密码都匹配时才会计数,但是经过改变后变成了或的逻辑关系,不管用户名和密码是否匹配该式的返回值永远为true;

3、为什么Preparement可以防止SQL注入?

原理是采用了预编译的方法,先将SQL语句中可被客户端控制的参数集进行编译,生成对应的临时变量集,再使用对应的设置方法,为临时变量集里面的元素进行赋值,赋值函数setString(),会对传入的参数进行强制类型检查和安全检查,所以就避免了SQL注入的产生。

Preparement样式为:

  • select * from tablename where username=? and password=?

该SQL语句会在得到用户的输入之前先用数据库进行预编译,这样的话不管用户输入什么用户名和密码的判断始终都是并的逻辑关系,防止了SQL注入。