PHP 安全基线加固指南
PHP 核心配置安全
基础安全配置(php.ini)
;========== 基本信息隐藏 ========== expose_php = Off ; 隐藏PHP版本号 display_errors = Off ; 不显示错误信息 log_errors = On ; 记录错误日志 error_reporting = E_ALL ; 记录所有错误 ;========== 资源限制 ========== max_execution_time = 30 ; 最大执行时间(秒) max_input_time = 60 ; 输入解析时间 memory_limit = 128M ; 内存限制 post_max_size = 8M ; POST数据限制 upload_max_filesize = 2M ; 上传文件大小 ;========== 危险函数禁用 ========== disable_functions = exec,system,passthru,shell_exec,proc_open,popen,curl_exec,curl_multi_exec,show_source,highlight_file,dl,proc_get_status,proc_close,proc_terminate,proc_nice ;========== 文件系统安全 ========== allow_url_fopen = Off ; 禁用远程文件打开 allow_url_include = Off ; 禁止远程文件包含 open_basedir = /var/www/html:/tmp ; 限定访问目录
会话安全配置
;========== Session安全 ========== session.cookie_httponly = 1 ; 禁止JavaScript访问Cookie session.cookie_secure = 1 ; 仅通过HTTPS传输 session.use_only_cookies = 1 ; 仅使用Cookie session.name = PHPSESSID ; 会话名称自定义 session.gc_maxlifetime = 1800 ; 会话过期时间(30分钟) session.cookie_lifetime = 0 ; Cookie生命周期(浏览器关闭即失效) session.sid_length = 48 ; 会话ID长度 session.sid_bits_per_character = 6 ; 会话ID复杂度 session.use_strict_mode = 1 ; 严格模式,拒绝未初始化的会话ID
文件上传安全
;========== 文件上传 ========== file_uploads = On upload_tmp_dir = /tmp/php_uploads ; 上传临时目录(需要正确权限) upload_max_filesize = 2M max_file_uploads = 20
代码层面安全
输入验证与过滤
<?php
// 使用filter_var进行输入验证
$email = filter_var($_POST['email'], FILTER_VALIDATE_EMAIL);
if (!$email) {
throw new Exception('非法邮箱格式');
}
// 使用PHP 8+的string类型约束
function processInput(string $input): string {
// 参数类型强制转换
return trim(htmlspecialchars($input, ENT_QUOTES, 'UTF-8'));
}
// 验证整数
$userId = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if ($userId === false || $userId === null) {
throw new Exception('非法用户ID');
}
?>
SQL注入防护(重点)
<?php
// 使用PDO预处理语句(推荐)
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', $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' => $email,
':status' => 'active'
]);
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
XSS防护
<?php
// 输出编码函数
function escapeOutput($data) {
return htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
}
// 使用模板引擎自动转义(如Twig)
// 输出示例
echo escapeOutput($userInput);
// JSON输出时
header('Content-Type: application/json');
echo json_encode($data, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
?>
CSRF防护
<?php
// 生成CSRF Token
function generateCsrfToken() {
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
// 验证CSRF Token
function validateCsrfToken($token) {
return hash_equals($_SESSION['csrf_token'], $token);
}
// 在表单中加入Token
$csrfToken = generateCsrfToken();
echo '<input type="hidden" name="csrf_token" value="' . $csrfToken . '">';
// 验证请求
if (!validateCsrfToken($_POST['csrf_token'] ?? '')) {
http_response_code(403);
exit('CSRF Token验证失败');
}
?>
文件上传安全
<?php
// 文件上传安全处理
function secureUpload() {
$allowedMimeTypes = ['image/jpeg', 'image/png', 'image/gif'];
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif'];
$maxSize = 2 * 1024 * 1024; // 2MB
if ($_FILES['file']['error'] !== UPLOAD_ERR_OK) {
throw new Exception('上传失败');
}
// 检查文件大小
if ($_FILES['file']['size'] > $maxSize) {
throw new Exception('文件过大');
}
// 检查MIME类型(仅作为辅助验证)
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $_FILES['file']['tmp_name']);
if (!in_array($mimeType, $allowedMimeTypes)) {
throw new Exception('文件类型不允许');
}
// 检查文件内容真实性
$imageInfo = getimagesize($_FILES['file']['tmp_name']);
if ($imageInfo === false) {
throw new Exception('文件不是有效的图片');
}
// 检查扩展名
$extension = strtolower(pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION));
if (!in_array($extension, $allowedExtensions)) {
throw new Exception('文件扩展名不允许');
}
// 重命名文件为随机名称
$newFileName = bin2hex(random_bytes(16)) . '.' . $extension;
$uploadPath = '/path/to/upload/' . $newFileName;
if (!move_uploaded_file($_FILES['file']['tmp_name'], $uploadPath)) {
throw new Exception('文件保存失败');
}
return $uploadPath;
}
?>
敏感信息保护
<?php
// 密码加密
$hashedPassword = password_hash($password, PASSWORD_DEFAULT); // 使用bcrypt
// 验证密码
if (password_verify($inputPassword, $hashedPassword)) {
// 密码正确
}
// Token生成
function generateToken() {
return bin2hex(random_bytes(32)); // 64位字符的随机Token
}
// 安全响应头
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: SAMEORIGIN');
header('X-XSS-Protection: 1; mode=block');
header('Strict-Transport-Security: max-age=31536000');
header('Content-Security-Policy: default-src \'self\'');
?>
日志记录
<?php
// 安全审计日志配置
class SecurityLogger {
private $logFile;
public function __construct($logFile) {
$this->logFile = $logFile;
}
public function logSecurityEvent($event, $data = []) {
$logEntry = [
'timestamp' => date('Y-m-d H:i:s'),
'event' => $event,
'data' => $data,
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown'
];
$json = json_encode($logEntry);
file_put_contents($this->logFile, $json . PHP_EOL, FILE_APPEND);
}
}
?>
部署安全
Web服务器配置(Apache/Nginx)
# Nginx安全配置示例
server {
# 禁止访问隐藏文件
location ~ /\.(?!well-known).* {
deny all;
}
# PHP处理配置
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# 限制请求大小
client_max_body_size 8M;
}
# 缓存配置(可选)
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
}
# 安全头部
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}
目录权限配置
# 安全文件权限
find /var/www/html -type f -exec chmod 644 {} \;
find /var/www/html -type d -exec chmod 755 {} \;
# 配置文件权限(重要配置文件)
chmod 600 /var/www/html/config/database.php
# 上传目录权限
mkdir -p /var/www/html/uploads
chmod 755 /var/www/html/uploads
chown www-data:www-data /var/www/html/uploads
# 日志目录权限
chmod 640 /var/www/html/logs/security.log
chown www-data:www-data /var/www/html/logs
运行时监控
安全监控脚本
<?php
// 异常行为检测
class SecurityMonitor {
private $logFile = '/var/log/php_security.log';
public function __construct() {
$this->logFile = ini_get('error_log') ?: $this->logFile;
}
public function monitor() {
// 检测异常请求
$requestRate = $this->getRequestRate($_SERVER['REMOTE_ADDR'] ?? '');
if ($requestRate > 100) {
$this->logSecurityAlert('HIGH_REQUEST_RATE', ['ip' => $_SERVER['REMOTE_ADDR']]);
}
// 检测XSS攻击模式
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
if (preg_match('/<script|javascript:|onerror|onload/i', $userAgent)) {
$this->logSecurityAlert('XSS_ATTEMPT', ['ip' => $_SERVER['REMOTE_ADDR']]);
}
}
private function getRequestRate($ip) {
// 示例:从Redis/DB获取请求计数
// return $rate;
return 0;
}
private function logSecurityAlert($type, $data) {
$entry = [
'timestamp' => date('Y-m-d H:i:s'),
'type' => $type,
'data' => $data
];
file_put_contents($this->logFile, json_encode($entry) . PHP_EOL, FILE_APPEND);
}
}
?>
安全基线检查清单
| 检查项 | 要求 | 检查命令/方法 |
|---|---|---|
| PHP版本 | >= 8.0,及时更新补丁 | php -v |
| 错误显示 | display_errors = Off | php -i | grep display_errors |
| 危险函数 | 已禁用 | php -i | grep disable_functions |
| 目录访问 | 配置open_basedir | php -i | grep open_basedir |
| 会话安全 | session.cookie_httponly = 1 | php -i | grep session.cookie |
| 上传安全 | 限制大小和类型 | php -i | grep upload_max |
| 远程文件 | allow_url_include = Off | php -i | grep allow_url |
| 文件权限 | 目录755,文件644 | ls -la |
| Web容器 | 隐藏版本信息 | 检查响应头 |
| 依赖库 | 及时更新 | composer audit |
持续安全措施
- 定期安全审计:使用 WeDetect 等扫描工具进行代码审计
- 依赖更新:使用
composer audit检查依赖漏洞 - 日志分析:定期分析安全日志,发现异常行为
- 安全测试:使用 OWASP ZAP 进行渗透测试
- 备份策略:定期备份代码和数据库
- Web Application Firewall (WAF):部署WAF进行应用层防护
这个安全基线提供了完整的PHP应用加固方案,建议根据实际环境调整参数,并定期更新安全策略。
