内容简介:本文风哥教程参考MariaDB官方文档MariaDB Server、Security等章节,详细讲解MariaDB SQL注入防范与安全开发,包括SQL注入攻击原理、防范技术和安全编码实践。
Part01-基础概念与理论知识
1.1 SQL注入基本概念
SQL注入是一种常见的网络攻击方式,攻击者通过在用户输入中插入恶意SQL代码,使数据库执行非预期的SQL语句。SQL注入攻击可以导致数据泄露、数据篡改、数据库服务器被控制等严重安全问题。
1.2 SQL注入攻击原理
- 攻击者通过用户输入界面(如登录表单、搜索框等)输入恶意SQL代码
- 应用程序将用户输入直接拼接到SQL语句中
- 数据库执行包含恶意代码的SQL语句
- 攻击者获取敏感信息或执行恶意操作
1.3 SQL注入危害
- 数据泄露:获取敏感信息,如用户密码、信用卡信息等
- 数据篡改:修改数据库中的数据
- 数据库服务器被控制:执行系统命令,获取服务器控制权
- 拒绝服务:使数据库服务不可用
- 绕过认证:无需用户名和密码即可登录系统
更多视频教程www.fgedu.net.cn
Part02-生产环境规划与建议
2.1 安全开发原则
风哥提示:安全开发是防止SQL注入的第一道防线。
- 输入验证:对所有用户输入进行严格验证
- 参数化查询:使用预处理语句和参数绑定
- 最小权限原则:为数据库用户分配最小必要的权限
- 数据加密:对敏感数据进行加密存储
- 安全审计:定期进行安全审计和漏洞扫描
2.2 数据库安全配置
- 用户权限管理:
- 为不同应用分配不同的数据库用户
- 限制用户的权限范围
- 定期更新用户密码
- 网络安全:
- 限制数据库访问IP
- 使用SSL加密连接
- 关闭不必要的服务和端口
- 配置安全:
- 禁用不必要的功能
- 启用审计日志
- 设置合理的密码策略
2.3 生产环境最佳实践
- 定期备份数据库
- 应用安全补丁
- 使用防火墙保护数据库服务器
- 定期进行安全扫描
- 建立安全事件响应机制
学习交流加群风哥微信: itpux-com
Part03-生产环境项目实施方案
3.1 SQL注入防范技术
更多学习教程公众号风哥教程itpux_com
# 1. 使用参数化查询
# 不安全的代码
# $username = $_POST[‘username’];
# $password = $_POST[‘password’];
# $sql = “SELECT * FROM fgedu_users WHERE username = ‘$username’ AND password = ‘$password'”;
# 安全的代码
# 使用预处理语句
$stmt = $pdo->prepare(‘SELECT * FROM fgedu_users WHERE username = ? AND password = ?’);
$stmt->execute([$username, $password]);
# 2. 输入验证
# 验证用户名
if (!preg_match(‘/^[a-zA-Z0-9_]{3,20}$/’, $username)) {
die(‘Invalid username’);
}
# 验证密码
if (strlen($password) < 6) {
die(‘Password too short’);
}
# 3. 转义特殊字符
# 使用mysqli_real_escape_string
$username = mysqli_real_escape_string($conn, $username);
$password = mysqli_real_escape_string($conn, $password);
$sql = “SELECT * FROM fgedu_users WHERE username = ‘$username’ AND password = ‘$password'”;
# 4. 使用ORM框架
# 使用Eloquent ORM
$user = User::where(‘username’, $username)->where(‘password’, $password)->first();
# 5. 最小权限原则
# 创建只读用户
MariaDB [(none)]> CREATE USER ‘fgedu_read’@’%’ IDENTIFIED BY ‘password’;
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> GRANT SELECT ON fgedudb.* TO ‘fgedu_read’@’%’;
Query OK, 0 rows affected (0.00 sec)
# 创建读写用户
MariaDB [(none)]> CREATE USER ‘fgedu_write’@’%’ IDENTIFIED BY ‘password’;
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> GRANT SELECT, INSERT, UPDATE, DELETE ON fgedudb.* TO ‘fgedu_write’@’%’;
Query OK, 0 rows affected (0.00 sec)
# 不安全的代码
# $username = $_POST[‘username’];
# $password = $_POST[‘password’];
# $sql = “SELECT * FROM fgedu_users WHERE username = ‘$username’ AND password = ‘$password'”;
# 安全的代码
# 使用预处理语句
$stmt = $pdo->prepare(‘SELECT * FROM fgedu_users WHERE username = ? AND password = ?’);
$stmt->execute([$username, $password]);
# 2. 输入验证
# 验证用户名
if (!preg_match(‘/^[a-zA-Z0-9_]{3,20}$/’, $username)) {
die(‘Invalid username’);
}
# 验证密码
if (strlen($password) < 6) {
die(‘Password too short’);
}
# 3. 转义特殊字符
# 使用mysqli_real_escape_string
$username = mysqli_real_escape_string($conn, $username);
$password = mysqli_real_escape_string($conn, $password);
$sql = “SELECT * FROM fgedu_users WHERE username = ‘$username’ AND password = ‘$password'”;
# 4. 使用ORM框架
# 使用Eloquent ORM
$user = User::where(‘username’, $username)->where(‘password’, $password)->first();
# 5. 最小权限原则
# 创建只读用户
MariaDB [(none)]> CREATE USER ‘fgedu_read’@’%’ IDENTIFIED BY ‘password’;
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> GRANT SELECT ON fgedudb.* TO ‘fgedu_read’@’%’;
Query OK, 0 rows affected (0.00 sec)
# 创建读写用户
MariaDB [(none)]> CREATE USER ‘fgedu_write’@’%’ IDENTIFIED BY ‘password’;
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> GRANT SELECT, INSERT, UPDATE, DELETE ON fgedudb.* TO ‘fgedu_write’@’%’;
Query OK, 0 rows affected (0.00 sec)
3.2 安全编码实践
# 1. 安全的登录验证
function login($username, $password) {
global $pdo;
// 输入验证
if (!preg_match(‘/^[a-zA-Z0-9_]{3,20}$/’, $username)) {
return false;
}
// 使用预处理语句
$stmt = $pdo->prepare(‘SELECT * FROM fgedu_users WHERE username = ?’);
$stmt->execute([$username]);
$user = $stmt->fetch();
if (!$user) {
return false;
}
// 验证密码(使用密码哈希)
if (password_verify($password, $user[‘password_hash’])) {
return $user;
}
return false;
}
# 2. 安全的搜索功能
function search($keyword) {
global $pdo;
// 输入验证
if (strlen($keyword) > 100) {
$keyword = substr($keyword, 0, 100);
}
// 使用预处理语句
$stmt = $pdo->prepare(‘SELECT * FROM fgedu_products WHERE name LIKE ? OR description LIKE ?’);
$searchTerm = ‘%’ . $keyword . ‘%’;
$stmt->execute([$searchTerm, $searchTerm]);
return $stmt->fetchAll();
}
# 3. 安全的用户注册
function register($username, $email, $password) {
global $pdo;
// 输入验证
if (!preg_match(‘/^[a-zA-Z0-9_]{3,20}$/’, $username)) {
return ‘Invalid username’;
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return ‘Invalid email’;
}
if (strlen($password) < 6) {
return ‘Password too short’;
}
// 检查用户名是否已存在
$stmt = $pdo->prepare(‘SELECT id FROM fgedu_users WHERE username = ?’);
$stmt->execute([$username]);
if ($stmt->fetch()) {
return ‘Username already exists’;
}
// 检查邮箱是否已存在
$stmt = $pdo->prepare(‘SELECT id FROM fgedu_users WHERE email = ?’);
$stmt->execute([$email]);
if ($stmt->fetch()) {
return ‘Email already exists’;
}
// 密码哈希
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
// 插入用户
$stmt = $pdo->prepare(‘INSERT INTO fgedu_users (username, email, password_hash, created_at) VALUES (?, ?, ?, NOW())’);
if ($stmt->execute([$username, $email, $passwordHash])) {
return ‘Registration successful’;
}
return ‘Registration failed’;
}
# 4. 安全的文件上传
function uploadFile($file) {
// 检查文件类型
$allowedTypes = [‘image/jpeg’, ‘image/png’, ‘image/gif’];
if (!in_array($file[‘type’], $allowedTypes)) {
return ‘Invalid file type’;
}
// 检查文件大小
if ($file[‘size’] > 1024 * 1024 * 5) { // 5MB
return ‘File too large’;
}
// 生成安全的文件名
$filename = uniqid() . ‘.’ . pathinfo($file[‘name’], PATHINFO_EXTENSION);
$uploadPath = ‘/mariadb/uploads/’ . $filename;
// 移动文件
if (move_uploaded_file($file[‘tmp_name’], $uploadPath)) {
return $filename;
}
return ‘Upload failed’;
}
function login($username, $password) {
global $pdo;
// 输入验证
if (!preg_match(‘/^[a-zA-Z0-9_]{3,20}$/’, $username)) {
return false;
}
// 使用预处理语句
$stmt = $pdo->prepare(‘SELECT * FROM fgedu_users WHERE username = ?’);
$stmt->execute([$username]);
$user = $stmt->fetch();
if (!$user) {
return false;
}
// 验证密码(使用密码哈希)
if (password_verify($password, $user[‘password_hash’])) {
return $user;
}
return false;
}
# 2. 安全的搜索功能
function search($keyword) {
global $pdo;
// 输入验证
if (strlen($keyword) > 100) {
$keyword = substr($keyword, 0, 100);
}
// 使用预处理语句
$stmt = $pdo->prepare(‘SELECT * FROM fgedu_products WHERE name LIKE ? OR description LIKE ?’);
$searchTerm = ‘%’ . $keyword . ‘%’;
$stmt->execute([$searchTerm, $searchTerm]);
return $stmt->fetchAll();
}
# 3. 安全的用户注册
function register($username, $email, $password) {
global $pdo;
// 输入验证
if (!preg_match(‘/^[a-zA-Z0-9_]{3,20}$/’, $username)) {
return ‘Invalid username’;
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return ‘Invalid email’;
}
if (strlen($password) < 6) {
return ‘Password too short’;
}
// 检查用户名是否已存在
$stmt = $pdo->prepare(‘SELECT id FROM fgedu_users WHERE username = ?’);
$stmt->execute([$username]);
if ($stmt->fetch()) {
return ‘Username already exists’;
}
// 检查邮箱是否已存在
$stmt = $pdo->prepare(‘SELECT id FROM fgedu_users WHERE email = ?’);
$stmt->execute([$email]);
if ($stmt->fetch()) {
return ‘Email already exists’;
}
// 密码哈希
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
// 插入用户
$stmt = $pdo->prepare(‘INSERT INTO fgedu_users (username, email, password_hash, created_at) VALUES (?, ?, ?, NOW())’);
if ($stmt->execute([$username, $email, $passwordHash])) {
return ‘Registration successful’;
}
return ‘Registration failed’;
}
# 4. 安全的文件上传
function uploadFile($file) {
// 检查文件类型
$allowedTypes = [‘image/jpeg’, ‘image/png’, ‘image/gif’];
if (!in_array($file[‘type’], $allowedTypes)) {
return ‘Invalid file type’;
}
// 检查文件大小
if ($file[‘size’] > 1024 * 1024 * 5) { // 5MB
return ‘File too large’;
}
// 生成安全的文件名
$filename = uniqid() . ‘.’ . pathinfo($file[‘name’], PATHINFO_EXTENSION);
$uploadPath = ‘/mariadb/uploads/’ . $filename;
// 移动文件
if (move_uploaded_file($file[‘tmp_name’], $uploadPath)) {
return $filename;
}
return ‘Upload failed’;
}
3.3 安全审计与监控
# 1. 启用审计日志
MariaDB [(none)]> SET GLOBAL audit_log=ON;
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> SET GLOBAL audit_log_file=’/mariadb/fgdata/audit.log’;
Query OK, 0 rows affected (0.00 sec)
# 2. 监控异常登录
MariaDB [fgedudb]> CREATE TABLE fgedu_login_attempts (
-> id INT PRIMARY KEY AUTO_INCREMENT,
-> username VARCHAR(50),
-> ip VARCHAR(20),
-> timestamp DATETIME,
-> success BOOLEAN
-> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.01 sec)
# 记录登录尝试
function logLoginAttempt($username, $ip, $success) {
global $pdo;
$stmt = $pdo->prepare(‘INSERT INTO fgedu_login_attempts (username, ip, timestamp, success) VALUES (?, ?, NOW(), ?)’);
$stmt->execute([$username, $ip, $success]);
}
# 检查登录尝试次数
function checkLoginAttempts($ip) {
global $pdo;
$stmt = $pdo->prepare(‘SELECT COUNT(*) FROM fgedu_login_attempts WHERE ip = ? AND timestamp > DATE_SUB(NOW(), INTERVAL 1 HOUR) AND success = 0’);
$stmt->execute([$ip]);
$count = $stmt->fetchColumn();
return $count >= 5;
// 5次失败尝试后锁定
}
# 3. 定期安全扫描
# 使用sqlmap进行SQL注入扫描
$ sqlmap -u “http://fgedu.net.cn/login.php” –data=”username=test&password=test” –dbs
# 4. 监控数据库活动
MariaDB [(none)]> SHOW PROCESSLIST;
+—-+——+———–+——+———+——+————————+——————+
| Id | User | Host | db | Command | Time | State | Info |
+—-+——+———–+——+———+——+————————+——————+
| 1 | root | fgedu.localhost | NULL | Query | 0 | executing | SHOW PROCESSLIST |
+—-+——+———–+——+———+——+————————+——————+
# 5. 定期备份
# 创建备份脚本
#!/bin/bash
# backup.sh
# from:www.itpux.com.qq113257174.wx:itpux-com
# web: `http://www.fgedu.net.cn`
date=$(date +%Y%m%d)
mysqldump -u fgedu -pfgedu fgedudb > /mariadb/backup/fgedudb_$date.sql
gzip /mariadb/backup/fgedudb_$date.sql
# 执行备份
$ bash backup.sh
MariaDB [(none)]> SET GLOBAL audit_log=ON;
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> SET GLOBAL audit_log_file=’/mariadb/fgdata/audit.log’;
Query OK, 0 rows affected (0.00 sec)
# 2. 监控异常登录
MariaDB [fgedudb]> CREATE TABLE fgedu_login_attempts (
-> id INT PRIMARY KEY AUTO_INCREMENT,
-> username VARCHAR(50),
-> ip VARCHAR(20),
-> timestamp DATETIME,
-> success BOOLEAN
-> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.01 sec)
# 记录登录尝试
function logLoginAttempt($username, $ip, $success) {
global $pdo;
$stmt = $pdo->prepare(‘INSERT INTO fgedu_login_attempts (username, ip, timestamp, success) VALUES (?, ?, NOW(), ?)’);
$stmt->execute([$username, $ip, $success]);
}
# 检查登录尝试次数
function checkLoginAttempts($ip) {
global $pdo;
$stmt = $pdo->prepare(‘SELECT COUNT(*) FROM fgedu_login_attempts WHERE ip = ? AND timestamp > DATE_SUB(NOW(), INTERVAL 1 HOUR) AND success = 0’);
$stmt->execute([$ip]);
$count = $stmt->fetchColumn();
return $count >= 5;
// 5次失败尝试后锁定
}
# 3. 定期安全扫描
# 使用sqlmap进行SQL注入扫描
$ sqlmap -u “http://fgedu.net.cn/login.php” –data=”username=test&password=test” –dbs
# 4. 监控数据库活动
MariaDB [(none)]> SHOW PROCESSLIST;
+—-+——+———–+——+———+——+————————+——————+
| Id | User | Host | db | Command | Time | State | Info |
+—-+——+———–+——+———+——+————————+——————+
| 1 | root | fgedu.localhost | NULL | Query | 0 | executing | SHOW PROCESSLIST |
+—-+——+———–+——+———+——+————————+——————+
# 5. 定期备份
# 创建备份脚本
#!/bin/bash
# backup.sh
# from:www.itpux.com.qq113257174.wx:itpux-com
# web: `http://www.fgedu.net.cn`
date=$(date +%Y%m%d)
mysqldump -u fgedu -pfgedu fgedudb > /mariadb/backup/fgedudb_$date.sql
gzip /mariadb/backup/fgedudb_$date.sql
# 执行备份
$ bash backup.sh
学习交流加群风哥QQ113257174
Part04-生产案例与实战讲解
4.1 SQL注入攻击演示
# 场景1:登录绕过
# 不安全的登录代码
# $username = $_POST[‘username’];
# $password = $_POST[‘password’];
# $sql = “SELECT * FROM fgedu_users WHERE username = ‘$username’ AND password = ‘$password'”;
# 攻击输入
# username: admin’ —
# password: anything
# 生成的SQL
# SELECT * FROM fgedu_users WHERE username = ‘admin’ –‘ AND password = ‘anything’
# 场景2:数据泄露
# 不安全的搜索代码
# $keyword = $_GET[‘keyword’];
# $sql = “SELECT * FROM fgedu_products WHERE name LIKE ‘%$keyword%'”;
# 攻击输入
# keyword: ‘ UNION SELECT username, password, NULL, NULL FROM fgedu_users —
# 生成的SQL
# SELECT * FROM fgedu_products WHERE name LIKE ‘%’ UNION SELECT username, password, NULL, NULL FROM fgedu_users –%’
# 场景3:数据库操作
# 不安全的删除代码
# $id = $_GET[‘id’];
# $sql = “DELETE FROM fgedu_products WHERE id = $id”;
# 攻击输入
# id: 1 OR 1=1
# 生成的SQL
# DELETE FROM fgedu_products WHERE id = 1 OR 1=1
# 场景4:系统命令执行
# 不安全的代码(使用了xp_cmdshell等危险函数)
# $command = $_GET[‘command’];
# $sql = “EXEC xp_cmdshell ‘$command'”;
# 攻击输入
# command: dir
# 生成的SQL
# EXEC xp_cmdshell ‘dir’
# 不安全的登录代码
# $username = $_POST[‘username’];
# $password = $_POST[‘password’];
# $sql = “SELECT * FROM fgedu_users WHERE username = ‘$username’ AND password = ‘$password'”;
# 攻击输入
# username: admin’ —
# password: anything
# 生成的SQL
# SELECT * FROM fgedu_users WHERE username = ‘admin’ –‘ AND password = ‘anything’
# 场景2:数据泄露
# 不安全的搜索代码
# $keyword = $_GET[‘keyword’];
# $sql = “SELECT * FROM fgedu_products WHERE name LIKE ‘%$keyword%'”;
# 攻击输入
# keyword: ‘ UNION SELECT username, password, NULL, NULL FROM fgedu_users —
# 生成的SQL
# SELECT * FROM fgedu_products WHERE name LIKE ‘%’ UNION SELECT username, password, NULL, NULL FROM fgedu_users –%’
# 场景3:数据库操作
# 不安全的删除代码
# $id = $_GET[‘id’];
# $sql = “DELETE FROM fgedu_products WHERE id = $id”;
# 攻击输入
# id: 1 OR 1=1
# 生成的SQL
# DELETE FROM fgedu_products WHERE id = 1 OR 1=1
# 场景4:系统命令执行
# 不安全的代码(使用了xp_cmdshell等危险函数)
# $command = $_GET[‘command’];
# $sql = “EXEC xp_cmdshell ‘$command'”;
# 攻击输入
# command: dir
# 生成的SQL
# EXEC xp_cmdshell ‘dir’
4.2 SQL注入防范实战
# 1. 使用参数化查询
# 安全的登录代码
function secureLogin($username, $password) {
global $pdo;
// 使用预处理语句
$stmt = $pdo->prepare(‘SELECT * FROM fgedu_users WHERE username = ? AND password = ?’);
$stmt->execute([$username, $password]);
return $stmt->fetch();
}
# 测试安全登录
$user = secureLogin(“admin’ –“, “anything”);
// 不会成功登录,因为参数会被正确处理
# 2. 输入验证
# 安全的搜索代码
function secureSearch($keyword) {
global $pdo;
// 输入验证
if (strlen($keyword) > 100) {
$keyword = substr($keyword, 0, 100);
}
// 移除危险字符
$keyword = preg_replace(‘/[\'”\\;
]/’, ”, $keyword);
// 使用预处理语句
$stmt = $pdo->prepare(‘SELECT * FROM fgedu_products WHERE name LIKE ?’);
$searchTerm = ‘%’ . $keyword . ‘%’;
$stmt->execute([$searchTerm]);
return $stmt->fetchAll();
}
# 测试安全搜索
$results = secureSearch(“‘ UNION SELECT username, password, NULL, NULL FROM fgedu_users –“);
// 不会泄露用户密码,因为输入会被过滤
# 3. 最小权限原则
# 创建应用专用用户
MariaDB [(none)]> CREATE USER ‘fgedu_app’@’fgedu.localhost’ IDENTIFIED BY ‘secure_password’;
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> GRANT SELECT, INSERT, UPDATE, DELETE ON fgedudb.* TO ‘fgedu_app’@’fgedu.localhost’;
Query OK, 0 rows affected (0.00 sec)
# 移除危险权限
MariaDB [(none)]> REVOKE FILE ON *.* FROM ‘fgedu_app’@’fgedu.localhost’;
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> REVOKE SUPER ON *.* FROM ‘fgedu_app’@’fgedu.localhost’;
Query OK, 0 rows affected (0.00 sec)
# 4. 使用ORM框架
# 使用Laravel Eloquent ORM
// 安全的查询
$users = User::where(‘username’, $username)->where(‘password’, $password)->get();
// 安全的插入
$user = new User;
$user->username = $username;
$user->password = $password;
$user->save();
// 安全的更新
User::where(‘id’, $id)->update([‘username’ => $newUsername]);
// 安全的删除
User::where(‘id’, $id)->delete();
# 安全的登录代码
function secureLogin($username, $password) {
global $pdo;
// 使用预处理语句
$stmt = $pdo->prepare(‘SELECT * FROM fgedu_users WHERE username = ? AND password = ?’);
$stmt->execute([$username, $password]);
return $stmt->fetch();
}
# 测试安全登录
$user = secureLogin(“admin’ –“, “anything”);
// 不会成功登录,因为参数会被正确处理
# 2. 输入验证
# 安全的搜索代码
function secureSearch($keyword) {
global $pdo;
// 输入验证
if (strlen($keyword) > 100) {
$keyword = substr($keyword, 0, 100);
}
// 移除危险字符
$keyword = preg_replace(‘/[\'”\\;
]/’, ”, $keyword);
// 使用预处理语句
$stmt = $pdo->prepare(‘SELECT * FROM fgedu_products WHERE name LIKE ?’);
$searchTerm = ‘%’ . $keyword . ‘%’;
$stmt->execute([$searchTerm]);
return $stmt->fetchAll();
}
# 测试安全搜索
$results = secureSearch(“‘ UNION SELECT username, password, NULL, NULL FROM fgedu_users –“);
// 不会泄露用户密码,因为输入会被过滤
# 3. 最小权限原则
# 创建应用专用用户
MariaDB [(none)]> CREATE USER ‘fgedu_app’@’fgedu.localhost’ IDENTIFIED BY ‘secure_password’;
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> GRANT SELECT, INSERT, UPDATE, DELETE ON fgedudb.* TO ‘fgedu_app’@’fgedu.localhost’;
Query OK, 0 rows affected (0.00 sec)
# 移除危险权限
MariaDB [(none)]> REVOKE FILE ON *.* FROM ‘fgedu_app’@’fgedu.localhost’;
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> REVOKE SUPER ON *.* FROM ‘fgedu_app’@’fgedu.localhost’;
Query OK, 0 rows affected (0.00 sec)
# 4. 使用ORM框架
# 使用Laravel Eloquent ORM
// 安全的查询
$users = User::where(‘username’, $username)->where(‘password’, $password)->get();
// 安全的插入
$user = new User;
$user->username = $username;
$user->password = $password;
$user->save();
// 安全的更新
User::where(‘id’, $id)->update([‘username’ => $newUsername]);
// 安全的删除
User::where(‘id’, $id)->delete();
4.3 安全开发实战
# 1. 安全的用户管理系统
# 用户登录
function login($username, $password) {
global $pdo;
// 输入验证
if (!preg_match(‘/^[a-zA-Z0-9_]{3,20}$/’, $username)) {
logLoginAttempt($username, $_SERVER[‘REMOTE_ADDR’], false);
return [‘success’ => false, ‘message’ => ‘Invalid username’];
}
// 检查登录尝试次数
if (checkLoginAttempts($_SERVER[‘REMOTE_ADDR’])) {
return [‘success’ => false, ‘message’ => ‘Too many login attempts. Please try again later.’];
}
// 使用预处理语句
$stmt = $pdo->prepare(‘SELECT * FROM fgedu_users WHERE username = ?’);
$stmt->execute([$username]);
$user = $stmt->fetch();
if (!$user) {
logLoginAttempt($username, $_SERVER[‘REMOTE_ADDR’], false);
return [‘success’ => false, ‘message’ => ‘Invalid username or password’];
}
// 验证密码
if (!password_verify($password, $user[‘password_hash’])) {
logLoginAttempt($username, $_SERVER[‘REMOTE_ADDR’], false);
return [‘success’ => false, ‘message’ => ‘Invalid username or password’];
}
// 记录成功登录
logLoginAttempt($username, $_SERVER[‘REMOTE_ADDR’], true);
// 生成会话
$_SESSION[‘user_id’] = $user[‘id’];
$_SESSION[‘username’] = $user[‘username’];
return [‘success’ => true, ‘message’ => ‘Login successful’];
}
# 用户注册
function register($username, $email, $password) {
global $pdo;
// 输入验证
if (!preg_match(‘/^[a-zA-Z0-9_]{3,20}$/’, $username)) {
return [‘success’ => false, ‘message’ => ‘Invalid username. Only letters, numbers, and underscores allowed.’];
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return [‘success’ => false, ‘message’ => ‘Invalid email address’];
}
if (strlen($password) < 6) {
return [‘success’ => false, ‘message’ => ‘Password must be at least 6 characters long’];
}
// 检查用户名是否已存在
$stmt = $pdo->prepare(‘SELECT id FROM fgedu_users WHERE username = ?’);
$stmt->execute([$username]);
if ($stmt->fetch()) {
return [‘success’ => false, ‘message’ => ‘Username already exists’];
}
// 检查邮箱是否已存在
$stmt = $pdo->prepare(‘SELECT id FROM fgedu_users WHERE email = ?’);
$stmt->execute([$email]);
if ($stmt->fetch()) {
return [‘success’ => false, ‘message’ => ‘Email already exists’];
}
// 密码哈希
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
// 插入用户
$stmt = $pdo->prepare(‘INSERT INTO fgedu_users (username, email, password_hash, created_at) VALUES (?, ?, ?, NOW())’);
if ($stmt->execute([$username, $email, $passwordHash])) {
return [‘success’ => true, ‘message’ => ‘Registration successful. You can now login.’];
}
return [‘success’ => false, ‘message’ => ‘Registration failed. Please try again later.’];
}
# 2. 安全的产品管理系统
# 添加产品
function addProduct($name, $description, $price, $stock) {
global $pdo;
// 输入验证
if (strlen($name) > 100) {
return [‘success’ => false, ‘message’ => ‘Product name too long’];
}
if (strlen($description) > 1000) {
return [‘success’ => false, ‘message’ => ‘Product description too long’];
}
if (!is_numeric($price) || $price < 0) {
return [‘success’ => false, ‘message’ => ‘Invalid price’];
}
if (!is_numeric($stock) || $stock < 0) {
return [‘success’ => false, ‘message’ => ‘Invalid stock’];
}
// 使用预处理语句
$stmt = $pdo->prepare(‘INSERT INTO fgedu_products (name, description, price, stock, created_at) VALUES (?, ?, ?, ?, NOW())’);
if ($stmt->execute([$name, $description, $price, $stock])) {
return [‘success’ => true, ‘message’ => ‘Product added successfully’];
}
return [‘success’ => false, ‘message’ => ‘Failed to add product’];
}
# 更新产品
function updateProduct($id, $name, $description, $price, $stock) {
global $pdo;
// 输入验证
if (!is_numeric($id) || $id <= 0) {
return [‘success’ => false, ‘message’ => ‘Invalid product ID’];
}
if (strlen($name) > 100) {
return [‘success’ => false, ‘message’ => ‘Product name too long’];
}
if (strlen($description) > 1000) {
return [‘success’ => false, ‘message’ => ‘Product description too long’];
}
if (!is_numeric($price) || $price < 0) {
return [‘success’ => false, ‘message’ => ‘Invalid price’];
}
if (!is_numeric($stock) || $stock < 0) {
return [‘success’ => false, ‘message’ => ‘Invalid stock’];
}
// 使用预处理语句
$stmt = $pdo->prepare(‘UPDATE fgedu_products SET name = ?, description = ?, price = ?, stock = ? WHERE id = ?’);
if ($stmt->execute([$name, $description, $price, $stock, $id])) {
return [‘success’ => true, ‘message’ => ‘Product updated successfully’];
}
return [‘success’ => false, ‘message’ => ‘Failed to update product’];
}
# 删除产品
function deleteProduct($id) {
global $pdo;
// 输入验证
if (!is_numeric($id) || $id <= 0) {
return [‘success’ => false, ‘message’ => ‘Invalid product ID’];
}
// 使用预处理语句
$stmt = $pdo->prepare(‘DELETE FROM fgedu_products WHERE id = ?’);
if ($stmt->execute([$id])) {
return [‘success’ => true, ‘message’ => ‘Product deleted successfully’];
}
return [‘success’ => false, ‘message’ => ‘Failed to delete product’];
}
# 用户登录
function login($username, $password) {
global $pdo;
// 输入验证
if (!preg_match(‘/^[a-zA-Z0-9_]{3,20}$/’, $username)) {
logLoginAttempt($username, $_SERVER[‘REMOTE_ADDR’], false);
return [‘success’ => false, ‘message’ => ‘Invalid username’];
}
// 检查登录尝试次数
if (checkLoginAttempts($_SERVER[‘REMOTE_ADDR’])) {
return [‘success’ => false, ‘message’ => ‘Too many login attempts. Please try again later.’];
}
// 使用预处理语句
$stmt = $pdo->prepare(‘SELECT * FROM fgedu_users WHERE username = ?’);
$stmt->execute([$username]);
$user = $stmt->fetch();
if (!$user) {
logLoginAttempt($username, $_SERVER[‘REMOTE_ADDR’], false);
return [‘success’ => false, ‘message’ => ‘Invalid username or password’];
}
// 验证密码
if (!password_verify($password, $user[‘password_hash’])) {
logLoginAttempt($username, $_SERVER[‘REMOTE_ADDR’], false);
return [‘success’ => false, ‘message’ => ‘Invalid username or password’];
}
// 记录成功登录
logLoginAttempt($username, $_SERVER[‘REMOTE_ADDR’], true);
// 生成会话
$_SESSION[‘user_id’] = $user[‘id’];
$_SESSION[‘username’] = $user[‘username’];
return [‘success’ => true, ‘message’ => ‘Login successful’];
}
# 用户注册
function register($username, $email, $password) {
global $pdo;
// 输入验证
if (!preg_match(‘/^[a-zA-Z0-9_]{3,20}$/’, $username)) {
return [‘success’ => false, ‘message’ => ‘Invalid username. Only letters, numbers, and underscores allowed.’];
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return [‘success’ => false, ‘message’ => ‘Invalid email address’];
}
if (strlen($password) < 6) {
return [‘success’ => false, ‘message’ => ‘Password must be at least 6 characters long’];
}
// 检查用户名是否已存在
$stmt = $pdo->prepare(‘SELECT id FROM fgedu_users WHERE username = ?’);
$stmt->execute([$username]);
if ($stmt->fetch()) {
return [‘success’ => false, ‘message’ => ‘Username already exists’];
}
// 检查邮箱是否已存在
$stmt = $pdo->prepare(‘SELECT id FROM fgedu_users WHERE email = ?’);
$stmt->execute([$email]);
if ($stmt->fetch()) {
return [‘success’ => false, ‘message’ => ‘Email already exists’];
}
// 密码哈希
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
// 插入用户
$stmt = $pdo->prepare(‘INSERT INTO fgedu_users (username, email, password_hash, created_at) VALUES (?, ?, ?, NOW())’);
if ($stmt->execute([$username, $email, $passwordHash])) {
return [‘success’ => true, ‘message’ => ‘Registration successful. You can now login.’];
}
return [‘success’ => false, ‘message’ => ‘Registration failed. Please try again later.’];
}
# 2. 安全的产品管理系统
# 添加产品
function addProduct($name, $description, $price, $stock) {
global $pdo;
// 输入验证
if (strlen($name) > 100) {
return [‘success’ => false, ‘message’ => ‘Product name too long’];
}
if (strlen($description) > 1000) {
return [‘success’ => false, ‘message’ => ‘Product description too long’];
}
if (!is_numeric($price) || $price < 0) {
return [‘success’ => false, ‘message’ => ‘Invalid price’];
}
if (!is_numeric($stock) || $stock < 0) {
return [‘success’ => false, ‘message’ => ‘Invalid stock’];
}
// 使用预处理语句
$stmt = $pdo->prepare(‘INSERT INTO fgedu_products (name, description, price, stock, created_at) VALUES (?, ?, ?, ?, NOW())’);
if ($stmt->execute([$name, $description, $price, $stock])) {
return [‘success’ => true, ‘message’ => ‘Product added successfully’];
}
return [‘success’ => false, ‘message’ => ‘Failed to add product’];
}
# 更新产品
function updateProduct($id, $name, $description, $price, $stock) {
global $pdo;
// 输入验证
if (!is_numeric($id) || $id <= 0) {
return [‘success’ => false, ‘message’ => ‘Invalid product ID’];
}
if (strlen($name) > 100) {
return [‘success’ => false, ‘message’ => ‘Product name too long’];
}
if (strlen($description) > 1000) {
return [‘success’ => false, ‘message’ => ‘Product description too long’];
}
if (!is_numeric($price) || $price < 0) {
return [‘success’ => false, ‘message’ => ‘Invalid price’];
}
if (!is_numeric($stock) || $stock < 0) {
return [‘success’ => false, ‘message’ => ‘Invalid stock’];
}
// 使用预处理语句
$stmt = $pdo->prepare(‘UPDATE fgedu_products SET name = ?, description = ?, price = ?, stock = ? WHERE id = ?’);
if ($stmt->execute([$name, $description, $price, $stock, $id])) {
return [‘success’ => true, ‘message’ => ‘Product updated successfully’];
}
return [‘success’ => false, ‘message’ => ‘Failed to update product’];
}
# 删除产品
function deleteProduct($id) {
global $pdo;
// 输入验证
if (!is_numeric($id) || $id <= 0) {
return [‘success’ => false, ‘message’ => ‘Invalid product ID’];
}
// 使用预处理语句
$stmt = $pdo->prepare(‘DELETE FROM fgedu_products WHERE id = ?’);
if ($stmt->execute([$id])) {
return [‘success’ => true, ‘message’ => ‘Product deleted successfully’];
}
return [‘success’ => false, ‘message’ => ‘Failed to delete product’];
}
风哥提示:安全开发是防止SQL注入的第一道防线
Part05-风哥经验总结与分享
5.1 SQL注入防范经验
在实际生产环境中,SQL注入防范需要注意以下几点:
- 始终使用参数化查询:这是防止SQL注入的最有效方法
- 进行严格的输入验证:对所有用户输入进行验证和过滤
- 使用最小权限原则:为数据库用户分配最小必要的权限
- 定期安全审计:检查代码中的安全漏洞
- 使用ORM框架:减少直接SQL语句的使用
- 保持数据库和应用程序的更新:及时应用安全补丁
5.2 常见安全问题与解决方案
- 密码存储:
- 问题:明文存储密码
- 解决方案:使用密码哈希函数(如bcrypt)存储密码
- 会话管理:
- 问题:会话固定攻击
- 解决方案:使用安全的会话ID生成机制,定期更新会话ID
- 跨站脚本(XSS):
- 问题:用户输入包含恶意脚本
- 解决方案:对输出进行HTML转义
- 跨站请求伪造(CSRF):
- 问题:攻击者诱导用户执行非预期操作
- 解决方案:使用CSRF令牌
5.3 生产安全故障案例分析
某电商系统发生SQL注入攻击,导致用户数据泄露,经过分析发现:
- 问题原因:
- 使用了不安全的SQL拼接
- 没有进行输入验证
- 数据库用户权限过大
- 没有启用审计日志
- 解决方案:
- 将所有SQL语句改为参数化查询
- 添加输入验证和过滤
- 重新配置数据库用户权限
- 启用审计日志和监控
- 对用户密码进行哈希处理
- 通知受影响的用户更改密码
- 预防措施:
- 定期进行安全代码审计
- 使用自动化安全扫描工具
- 对开发人员进行安全培训
- 建立安全事件响应机制
from MariaDB视频:www.itpux.com
本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html
