暂未配置
前言
上一章我们学习了PHP+MySQL基础增删改查和简单表单登录,但直接拼接用户输入的SQL语句存在严重的SQL注入漏洞,无法用于正式项目。本章重点学习MySQLi预处理语句(防注入核心方案),彻底解决SQL注入安全问题,同时讲解开发中高频使用的文件上传功能,包含单文件上传、文件类型限制、大小限制、防覆盖、错误处理等实战功能,补齐PHP数据库开发和表单功能的核心短板。
一、深度认识SQL注入漏洞
1. 漏洞原理
程序直接将用户通过表单、URL传入的数据,拼接进SQL语句中执行。用户可输入特殊字符(单引号、注释符、逻辑运算符等),篡改原有SQL逻辑,实现越权查询、盗取、篡改甚至删除全站数据。
2. 注入漏洞实战演示
基于上一章的登录功能,无需正确账号密码,即可实现万能登录。
注入测试账号:' or 1=1 #
密码:任意内容
拼接后的SQL语句会变为:
SELECT * FROM user WHERE username='' or 1=1 #' AND password='123'原理解析:#是SQL注释符,会注释掉后续密码验证代码;1=1永久成立,导致无条件查询出所有用户数据,直接绕过登录验证。
3. 传统防护误区
单纯使用addslashes()转义特殊字符、过滤关键词,无法彻底防御高级注入手段,唯一安全、官方推荐的解决方案是:预处理语句(预处理+参数绑定)。
二、MySQLi预处理语句(防注入核心)
1. 预处理核心优势
- 彻底防注入:SQL结构和用户数据完全分离,用户输入仅作为普通数据执行,无法篡改SQL逻辑
- 执行效率高:一条SQL模板可多次传入不同参数执行,适合批量操作数据
- 代码更规范:告别字符串拼接,避免语法错误
2. 预处理语句执行流程
- 创建预处理SQL模板,使用占位符 ? 代替变量
- 初始化预处理对象
mysqli_prepare() - 绑定参数
mysqli_stmt_bind_param() - 执行SQL语句
mysqli_stmt_execute() - 处理执行结果,关闭预处理、关闭数据库连接
3. 参数绑定类型说明
mysqli_stmt_bind_param() 第二个参数为类型标识,多个类型依次填写:
- s:string 字符串(最常用,文本、账号、密码、昵称均用此类型)
- i:int 整型(ID、页码、数量等数字)
- d:double 浮点型(金额、小数数据)
- b:blob 二进制文件(图片、文件流)
三、预处理语句实战(增删改查)
所有案例均基于 php_demo 数据库、user 用户表,完全防注入,可直接用于项目。
1. 预处理新增数据(INSERT)
<?php
// 连接数据库
$conn = mysqli_connect("127.0.0.1","root","root","php_demo");
mysqli_set_charset($conn,"utf8mb4");
// 1. 编写预处理SQL,?为占位符
$sql = "INSERT INTO user(username,password,nickname) VALUES (?,?,?)";
// 2. 初始化预处理
$stmt = mysqli_prepare($conn,$sql);
// 3. 绑定参数:三个字符串类型参数
$username = "user01";
$password = "123456";
$nickname = "普通用户";
mysqli_stmt_bind_param($stmt,"sss",$username,$password,$nickname);
// 4. 执行语句
if(mysqli_stmt_execute($stmt)){
echo "新增成功!新增ID:".mysqli_stmt_insert_id($stmt);
}else{
echo "新增失败:".mysqli_stmt_error($stmt);
}
// 关闭资源
mysqli_stmt_close($stmt);
mysqli_close($conn);
?>2. 预处理查询数据(单条查询)
<?php
$conn = mysqli_connect("127.0.0.1","root","root","php_demo");
mysqli_set_charset($conn,"utf8mb4");
// 接收URL参数,防注入查询
$id = $_GET['id'];
$sql = "SELECT username,nickname FROM user WHERE id=?";
$stmt = mysqli_prepare($conn,$sql);
// 绑定整型参数
mysqli_stmt_bind_param($stmt,"i",$id);
mysqli_stmt_execute($stmt);
// 绑定结果集
mysqli_stmt_bind_result($stmt,$username,$nickname);
mysqli_stmt_fetch($stmt);
if($username){
echo "账号:".$username."<br>昵称:".$nickname;
}else{
echo "用户不存在";
}
mysqli_stmt_close($stmt);
mysqli_close($conn);
?>3. 预处理修改数据(UPDATE)
<?php
$conn = mysqli_connect("127.0.0.1","root","root","php_demo");
mysqli_set_charset($conn,"utf8mb4");
$nickname = "修改后的用户昵称";
$id = 1;
$sql = "UPDATE user SET nickname=? WHERE id=?";
$stmt = mysqli_prepare($conn,$sql);
mysqli_stmt_bind_param($stmt,"si",$nickname,$id);
if(mysqli_stmt_execute($stmt)){
echo "修改成功";
}else{
echo "修改失败:".mysqli_stmt_error($stmt);
}
mysqli_stmt_close($stmt);
mysqli_close($conn);
?>4. 预处理删除数据(DELETE)
<?php
$conn = mysqli_connect("127.0.0.1","root","root","php_demo");
mysqli_set_charset($conn,"utf8mb4");
$id = 2;
$sql = "DELETE FROM user WHERE id=?";
$stmt = mysqli_prepare($conn,$sql);
mysqli_stmt_bind_param($stmt,"i",$id);
if(mysqli_stmt_execute($stmt)){
echo "删除成功";
}else{
echo "删除失败:".mysqli_stmt_error($stmt);
}
mysqli_stmt_close($stmt);
mysqli_close($conn);
?>四、安全登录实战(预处理防注入版)
重写登录功能,彻底杜绝万能登录注入漏洞,企业级安全标准。
1. 登录表单 login.html
<form action="login_do.php" method="post">
账号:<input type="text" name="username" required><br>
密码:<input type="password" name="password" required><br>
<input type="submit" value="安全登录">
</form>2. 登录处理 login\_do.php
<?php
// 接收并去除首尾空格
$username = trim($_POST['username']);
$password = trim($_POST['password']);
// 空值校验
if(empty($username) || empty($password)){
exit("账号密码不能为空!<a href='javascript:history.back()'>返回重试</a>");
}
// 数据库连接
$conn = mysqli_connect("127.0.0.1","root","root","php_demo");
mysqli_set_charset($conn,"utf8mb4");
// 预处理查询,杜绝注入
$sql = "SELECT nickname FROM user WHERE username=? AND password=?";
$stmt = mysqli_prepare($conn,$sql);
mysqli_stmt_bind_param($stmt,"ss",$username,$password);
mysqli_stmt_execute($stmt);
// 获取结果
mysqli_stmt_bind_result($stmt,$nickname);
mysqli_stmt_fetch($stmt);
if(!empty($nickname)){
echo "登录成功!欢迎你:".$nickname;
}else{
echo "账号或密码错误";
}
// 关闭资源
mysqli_stmt_close($stmt);
mysqli_close($conn);
?>五、PHP文件上传核心原理
文件上传是开发高频功能(头像上传、图片发布、附件上传等),PHP通过超全局变量 $_FILES 接收表单上传的文件,包含文件名、文件类型、临时路径、错误码、文件大小等信息。
1. $\_FILES 常用参数
$_FILES['file']['name']:客户端原始文件名$_FILES['file']['tmp_name']:服务器临时存储路径(上传后临时文件,脚本结束自动删除)$_FILES['file']['type']:文件MIME类型$_FILES['file']['size']:文件大小(单位:字节)$_FILES['file']['error']:上传错误码(0为上传成功)
2. 上传必备表单规则
文件上传表单必须满足两个条件:
- 请求方式必须为
method="post" - 表单添加属性
enctype="multipart/form-data"(二进制传输文件)
六、单文件上传完整实战(安全版)
实现功能:限制文件类型、限制文件大小、生成唯一文件名防覆盖、错误提示完善、目录自动创建。
1. 上传表单 upload.html
<form action="upload_do.php" method="post" enctype="multipart/form-data">
选择上传图片:<input type="file" name="file" accept="image/*"><br>
<input type="submit" value="上传文件">
</form>2. 上传处理脚本 upload\_do.php
<?php
// 1. 接收上传文件
$file = $_FILES['file'];
// 2. 配置上传规则
$allow_type = ['jpg','jpeg','png','gif']; // 允许的文件后缀
$max_size = 2097152; // 最大2M
$upload_path = "./upload/"; // 上传保存目录
// 自动创建目录(不存在则创建)
if(!file_exists($upload_path)){
mkdir($upload_path,0777,true);
}
// 3. 获取文件信息
$file_name = $file['name'];
$file_size = $file['size'];
$file_tmp = $file['tmp_name'];
$file_error = $file['error'];
$file_ext = strtolower(pathinfo($file_name,PATHINFO_EXTENSION)); // 获取后缀并转小写
// 4. 错误判断
if($file_error != 0){
exit("文件上传失败!错误码:".$file_error);
}
// 大小判断
if($file_size > $max_size){
exit("文件过大!仅允许2M以内文件");
}
// 类型判断
if(!in_array($file_ext,$allow_type)){
exit("文件格式不支持!仅允许jpg、png、gif格式图片");
}
// 5. 生成唯一文件名(防止文件覆盖)
$new_name = time().rand(1000,9999).".".$file_ext;
$save_path = $upload_path.$new_name;
// 6. 移动临时文件到永久目录
if(move_uploaded_file($file_tmp,$save_path)){
echo "文件上传成功!<br>";
echo "文件保存路径:".$save_name."<br>";
echo "<img src='".$save_path."' width='200'>";
}else{
echo "文件保存失败!";
}
?>七、本章核心总结
- SQL注入是高危漏洞,禁止直接拼接用户输入,必须使用MySQLi预处理语句防护
- 预处理核心:占位符? + 参数绑定,实现SQL结构与数据分离,从根源防注入
- 参数绑定四种类型:s字符串、i整型、d浮点型、b二进制,日常开发以s、i为主
- 文件上传表单必须添加
enctype="multipart/form-data",POST方式提交 - 安全上传必备校验:错误码校验、文件大小校验、后缀白名单校验、唯一文件名防覆盖
- 正式项目中,上传目录禁止执行PHP脚本,可搭配权限配置进一步提升安全性
八、课后作业
1. 使用预处理语句,完成用户注册功能(账号、密码、昵称入库,防注入)。
2. 改造文件上传功能,支持多文件上传,并限制单文件1M、仅允许png格式。
3. 测试SQL注入漏洞:分别用普通拼接SQL和预处理SQL测试万能登录,对比效果差异。
(注:部分内容可能由 AI 生成)