PHP SQL注入及防护

一、什么是SQL注入

SQL注入是所有漏洞类型中危害最严重的漏洞之一。

SQL注入,主要通过伪造客户端请求,把SQL命令提交到服务端进行非法请求的操作,最终达到欺骗服务器从而执行恶意SQL命令。

二、准备工作

文章最后附表数据

三、SQL注入示例

test.php

<?php
declare(strict_types = 1);

$dsn = "mysql:dbname=test;host=127.0.0.1";
$pdo = new PDO($dsn,'root','123456');
// 接收username
$studentName = $_GET['name'];
$sql = "select * from student where name = {$studentName}";
$stmt = $pdo->query($sql);
$data = $stmt->fetchAll(2);
if (!empty($data)) {
    var_dump($data);
} else {
    echo '未找到记录';
}

1、查询所有数据(程序原来的目的是根据用户输入去查询单条记录)

访问 yourWebsite/test.php?name='a' or 1 = 1,会发现即使没有符合条件的,却仍然将所有数据查询出来了。

注:如果程序中写的是select * from student where name = '{$studentName}'

则构造查询条件为 https://www.haveyb.com/test.php?name= a' or 1 = '1,查询结果则一样。

2、查询的同时删除记录数据

访问 yourWebsite/test.php?name='a' or 1 = 1;delete from student where id = 4,你会发现,id 为 4 的记录被删掉了,如果没有备份,后果可以想象。

同理,也可以通过SQL注入修改数据,比如修改工资、财务等信息。

四、隐式类型注入

还有更简洁的一种方式去实现注入删除数据,直接将搜索条件置为0,后面跟删除语句。这时,执行的结果仍然是查询出所有记录,并将指定记录删除。

yourWebsite/test.php?name=0;delete from student where id = 7

此种情况仅出现在数据类型为string,并且查询条件值为0时,会返回所有记录。

建议程序中进行 if (empty) 判断。

五、SQL注入的防护

SQL注入是最危险的漏洞之一,但也是最好防护的漏洞之一。

通过预编译处理

SQL注入之所以能被注入,主要原因在于它的数据和代码指令是混合的。使用数据库预编译方式进行数据库查询,不仅可以增强系统安全性,而且可以提高系统的执行效率。

这里,以 PDO 方式举例。

<?php
declare(strict_types = 1);
// 定义pdo连接信息
$dsn = "mysql:dbname=test;host=127.0.0.1";
$pdo = new PDO($dsn,'root','123456');

// 预处理方式查询
$name = $_GET['name'];
$pdo->query('set names utf8');
$sql = 'select * from student where name = :name';
$stmt = $pdo->prepare($sql);
$stmt->execute(['name' => $name]);
var_dump($stmt->fetchAll(2));

// 预处理方式插入
$name = $_GET['name'];
$age = intval($_GET['age']);
$pdo->query('set names utf8');
$sql = 'insert into student (`name`, `age`) values (:name, :age)';
$stmt = $pdo->prepare($sql);
$stmt->execute(['name' => $name, 'age' => $age]);
var_dump($stmt->fetchAll(2));

说明:目前,大多数框架在执行查询时,都在底层做了预处理,因此不用开发人员过多去关注,但当我们自己写sql,而不用框架内置的连贯查询时,在SQL注入的防护上就要注意了。

如果代码过于老旧,或者PHP版本较低,数据库版本较老,不支持预编译处理,为了防止SQL注入,我们应该对输入的数据进行有效的校验和过滤。

附:实验学生表

DROP TABLE IF EXISTS `student`;
CREATE TABLE `student`  (
  `id` int(4) UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` varchar(12) NOT NULL DEFAULT '' COMMENT '学生姓名',
  `sex` enum('男','女') NOT NULL DEFAULT '男' COMMENT '学生性别',
  `age` tinyint(4) NOT NULL DEFAULT 24,
  `salary` float(8, 2) UNSIGNED NOT NULL DEFAULT 0.00,
  `bonus` float(6, 2) UNSIGNED NOT NULL DEFAULT 0.00,
  `city` varchar(32) NOT NULL DEFAULT '' COMMENT '家乡',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 51 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `student` VALUES (1, '曹广阳', '男', 34, 9500.00, 1200.00, '山东省');
INSERT INTO `student` VALUES (2, '井柏然', '男', 24, 7000.00, 1400.00, '内蒙古');
INSERT INTO `student` VALUES (3, '陈晨', '男', 24, 2000.00, 1400.00, '云南省');
INSERT INTO `student` VALUES (4, '常江波', '男', 35, 14000.00, 840.00, '湖南省');
INSERT INTO `student` VALUES (5, '王张明', '男', 35, 14000.00, 840.00, '湖南省'); 
INSERT INTO `student` VALUES (6, '赵金生', '男', 37, 18000.00, 1440.00, '湖南省');
INSERT INTO `student` VALUES (7, '史月林', '女', 26, 8900.00, 1280.00, '甘肃省');