PHP 安全编码完全指南
输入验证与过滤
1 输入验证原则
// ❌ 不安全的做法
$id = $_GET['id'];
$query = "SELECT * FROM users WHERE id = $id";
// ✅ 安全的做法
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if ($id === false || $id === null) {
throw new InvalidArgumentException('Invalid ID');
}
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
2 常用过滤函数
// 验证邮箱
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
// 验证URL
$url = filter_input(INPUT_POST, 'url', FILTER_VALIDATE_URL);
// 验证IP地址
$ip = filter_input(INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP);
// 自定义验证
function validateInput($data, $type) {
switch ($type) {
case 'name':
return preg_match('/^[a-zA-Z\s\-]{2,50}$/', $data);
case 'phone':
return preg_match('/^\+?[0-9\-]{8,15}$/', $data);
// 更多自定义规则...
}
}
SQL注入防护
1 使用PDO预处理语句
// ✅ PDO预处理(推荐)
$pdo = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 使用命名参数
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email AND status = :status");
$stmt->execute([
':email' => $_POST['email'],
':status' => 1
]);
$user = $stmt->fetch();
// 使用位置参数
$stmt = $pdo->prepare("INSERT INTO users (name, email) VALUES (?, ?)");
$stmt->execute([$_POST['name'], $_POST['email']]);
2 使用MySQLi
// ✅ MySQLi预处理
$mysqli = new mysqli('localhost', 'user', 'pass', 'db');
$stmt = $mysqli->prepare("SELECT * FROM users WHERE id = ?");
$stmt->bind_param('i', $id);
$stmt->execute();
$result = $stmt->get_result();
XSS攻击防护
1 输出编码
// ❌ 不安全:直接输出用户输入
echo $_POST['username'];
// ✅ 安全:HTML实体编码
echo htmlspecialchars($_POST['username'], ENT_QUOTES, 'UTF-8');
// ✅ 更安全的输出函数
function escape_output($data) {
return htmlspecialchars($data ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
2 使用模板引擎的转义功能
// Twig模板引擎
$twig->render('user.html', [
'name' => $_POST['name'] // Twig自动转义 {{ name }}
]);
// Blade模板引擎 (Laravel)
echo $name; // Blade {{ $name }} 自动转义
echo {!! $content !!}; // 不转义(需确保$content安全)
CSRF防护
1 生成和验证Token
class CSRF {
public static function generateToken() {
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
public static function validateToken($token) {
return isset($_SESSION['csrf_token'])
&& hash_equals($_SESSION['csrf_token'], $token);
}
}
// 在表单中使用
$token = CSRF::generateToken();
?>
<form method="POST">
<input type="hidden" name="csrf_token" value="<?= $token ?>">
<!-- 其他表单字段 -->
</form>
<?php
// 验证POST请求
if (!CSRF::validateToken($_POST['csrf_token'] ?? '')) {
http_response_code(403);
die('CSRF token validation failed');
}
文件上传安全
1 安全文件上传处理
class FileUploadHandler {
private $allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
private $maxSize = 2 * 1024 * 1024; // 2MB
public function upload($file) {
// 验证文件大小
if ($file['size'] > $this->maxSize) {
throw new Exception('File too large');
}
// 验证MIME类型(使用finfo,不信任客户端)
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($file['tmp_name']);
if (!in_array($mimeType, $this->allowedTypes)) {
throw new Exception('Invalid file type');
}
// 生成随机文件名
$extension = pathinfo($file['name'], PATHINFO_EXTENSION);
$newName = bin2hex(random_bytes(16)) . '.' . $extension;
// 存储到安全目录(非web根目录)
$uploadDir = '/var/www/uploads/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0750, true);
}
// 移动文件
if (!move_uploaded_file($file['tmp_name'], $uploadDir . $newName)) {
throw new Exception('File upload failed');
}
return $newName;
}
}
会话安全
1 安全会话配置
// 会话安全配置
ini_set('session.use_only_cookies', 1);
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1); // HTTPS环境下才启用
ini_set('session.use_strict_mode', 1);
session_start();
// 会话固定防护
if (!isset($_SESSION['initiated'])) {
session_regenerate_id(true);
$_SESSION['initiated'] = true;
}
// 设置会话超时
$_SESSION['last_activity'] = time();
if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity'] > 1800)) {
session_unset();
session_destroy();
header('Location: login.php');
exit;
}
密码安全存储
1 密码哈希
// ✅ 使用password_hash(推荐)
$hashedPassword = password_hash($_POST['password'], PASSWORD_DEFAULT);
// 验证密码
if (password_verify($_POST['password'], $user['password'])) {
// 密码正确
}
// 检查是否需要重新哈希
if (password_needs_rehash($user['password'], PASSWORD_DEFAULT)) {
// 更新密码哈希
}
安全响应头
1 设置安全HTTP头
// 统一设置安全响应头
header('X-Frame-Options: SAMEORIGIN');
header('X-Content-Type-Options: nosniff');
header('X-XSS-Protection: 1; mode=block');
header('Referrer-Policy: strict-origin-when-cross-origin');
header('Content-Security-Policy: default-src \'self\'; script-src \'self\'');
header('Strict-Transport-Security: max-age=31536000; includeSubDomains'); // HTTPS
// 或使用框架中间件
$app->add(new \Middlewares\SecureHeaders());
错误处理与日志
1 安全的错误处理
// 生产环境隐藏错误详情
if (ENVIRONMENT === 'production') {
ini_set('display_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', '/var/log/php_errors.log');
} else {
ini_set('display_errors', 1);
}
// 自定义错误处理函数
set_exception_handler(function ($exception) {
log_error("Exception: " . $exception->getMessage());
// 生产环境返回通用错误信息
echo '发生错误,请稍后重试';
});
依赖安全
1 使用Composer管理依赖
{
"require": {
"php": ">=7.4",
"ext-pdo": "*",
"ext-filter": "*"
}
}
# 定期更新依赖 composer update --no-dev # 检查已知漏洞 composer audit
十一、安全配置清单
1 PHP配置建议
; php.ini 安全配置 display_errors = Off log_errors = On error_reporting = E_ALL ; 限制上传 upload_max_filesize = 2M max_file_uploads = 20 ; 禁用危险函数 disable_functions = exec,shell_exec,system,passthru,proc_open,show_source ; 会话安全 session.cookie_httponly = 1 session.use_strict_mode = 1
十二、框架安全最佳实践
1 Laravel安全实践
// 使用Eloquent ORM(自动防SQL注入)
$user = User::where('email', $request->email)->first();
// 使用环境变量
$apiKey = env('API_KEY');
// 使用artisan进行安全检查
php artisan security:check
十三、输入数据流程图
用户输入
↓
1. 数据验证(类型、格式、长度)
↓
2. 数据过滤/净化
↓
3. 数据库操作(预处理语句)
↓
4. 输出编码(HTML转义)
↓
5. 安全存储/显示
十四、安全编码检查清单
- [ ] 所有数据库操作使用预处理语句
- [ ] 所有输出进行HTML实体编码
- [ ] 所有表单包含CSRF token
- [ ] 密码使用password_hash()存储
- [ ] 文件上传验证MIME类型和大小
- [ ] 错误信息不泄露敏感信息
- [ ] 会话使用安全配置
- [ ] 输入数据验证和过滤
- [ ] 使用HTTPS加密传输
- [ ] 设置安全响应头
- [ ] 依赖包定期更新和审计
安全编码是一个持续的过程,需要遵循以下核心原则:

- 永远不要信任用户输入
- 使用参数化查询防SQL注入
- 对输出进行编码防XSS
- 使用CSRF token防护
- 最小权限原则
- 安全配置和更新是最重要的防线
安全不是一次性的工作,而是需要在开发的全过程中持续关注和维护。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播