PHP 怎么PHP 最小特权

wen PHP项目 2

本文目录导读:

PHP 怎么PHP 最小特权

  1. 数据库连接:使用权限最小的数据库用户
  2. 文件系统权限:限制 PHP 进程的读写范围
  3. 会话与临时文件:隔离临时目录
  4. 系统命令执行:白名单 + 禁用危险函数
  5. 权限检查:每次操作前验证
  6. 错误信息最小化
  7. Composer 依赖的权限隔离
  8. PHP-FPM 进程池隔离(多站点)
  9. 总结检查清单

在 PHP 中实现 最小特权原则(Principle of Least Privilege,PoLP) 的核心思想是:只给代码、用户、进程恰好够用的权限,既不更多,也不更少

以下是针对 PHP 开发中贯彻最小特权的具体策略和代码实践:

数据库连接:使用权限最小的数据库用户

常见错误:整个项目用一个 root 用户连接数据库。 正确做法:为不同模块创建不同权限的数据库用户。

// 只读模块(如首页展示)使用只读账号
$readonly_db = new PDO('mysql:host=localhost;dbname=blog', 'blog_reader', 'password1', [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
// 写入模块(如发布文章)使用增删改账号
$write_db = new PDO('mysql:host=localhost;dbname=blog', 'blog_writer', 'password2', [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);

SQL 层面配置

-- 只读权限
GRANT SELECT ON blog.* TO 'blog_reader'@'localhost';
-- 增删改权限(不给 DROP、ALTER)
GRANT SELECT, INSERT, UPDATE, DELETE ON blog.* TO 'blog_writer'@'localhost';

文件系统权限:限制 PHP 进程的读写范围

原则:PHP 进程(通常是 www-datanobody)只能访问它必须访问的目录。

目录权限配置(Linux/Unix)

# 网站根目录(PHP 可读,不可写)
chmod 755 /var/www/html
chown root:www-data /var/www/html
# 上传目录(PHP 可读写,但禁止执行PHP)
chmod 755 /var/www/html/uploads
chown www-data:www-data /var/www/html/uploads
# 配置文件(PHP 可读,绝对不可写)
chmod 640 /var/www/html/config.php
chown root:www-data /var/www/html/config.php
# 日志目录(PHP 只写,不可读已存在的日志)
chmod 730 /var/log/myapp/
chown www-data:www-data /var/log/myapp/

禁用文件执行权限(防止上传 PHP 马): 在 Nginx/Apache 配置中对上传目录取消 PHP 执行:

location /uploads/ {
    location ~ \.php$ {
        deny all;
    }
}

会话与临时文件:隔离临时目录

常见错误:PHP 默认临时目录 /tmp 可能被其他用户读取。 改进:使用项目私有临时目录,并设置严格权限。

// 在入口文件设置
session_save_path('/var/www/project/tmp/sessions');
ini_set('upload_tmp_dir', '/var/www/project/tmp/uploads');
// 确保目录权限
// chmod 700 /var/www/project/tmp/sessions
// chown www-data:www-data /var/www/project/tmp/sessions

系统命令执行:白名单 + 禁用危险函数

原则:除非必须,不使用 exec()shell_exec()system(),如果必须使用,限制输入范围。

// ❌ 糟糕:直接拼接用户输入
exec("convert {$_GET['img']} output.png");
// ✅ 较好:白名单 + 参数化
$allowed_commands = [
    'resize' => 'convert %s -resize 200x200 output.png',
    'compress' => 'convert %s -quality 80 output.jpg'
];
$action = $_GET['action'] ?? '';
if (!array_key_exists($action, $allowed_commands)) {
    die('Invalid command');
}
$file = basename($_GET['file']); // 去掉路径
$command = sprintf($allowed_commands[$action], escapeshellarg($file));
exec($command, $output, $return_code);

禁用高危函数php.inidisable_functions):

disable_functions = exec, shell_exec, system, passthru, popen, proc_open, pcntl_exec

权限检查:每次操作前验证

最小特权不仅体现在系统层面,也体现在业务逻辑

// 用户只能修改自己的文章
class ArticleController {
    public function update(int $articleId, array $data) {
        $article = Article::find($articleId);
        // ⭐ 权限检查:不是用户自己的文章,也不是管理员,则拒绝
        if ($article->user_id !== Auth::id() && !Auth::user()->isAdmin()) {
            http_response_code(403);
            die('Insufficient permissions');
        }
        $article->update($data);
    }
}

错误信息最小化

原则:生产环境不暴露任何敏感信息。

// php.ini 配置
display_errors = Off
log_errors = On
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
// 使用自定义错误处理
set_error_handler(function($severity, $message, $file, $line) {
    // 记录到日志,但返回通用错误
    error_log("Error: $message in $file:$line");
    http_response_code(500);
    echo 'An internal error occurred.';
});

Composer 依赖的权限隔离

问题:Composer 的 vendor 目录如果可写,可能被攻击者修改依赖。 解决方案

# 部署后锁定 vendor 目录
chmod -R 755 vendor
# 或者使用 composer 的 --no-dev 并禁用自动更新
composer install --no-dev --optimize-autoloader
composer config --global disable-tls false

PHP-FPM 进程池隔离(多站点)

如果使用 PHP-FPM,可以为不同站点创建独立的进程池,并以不同用户运行:

; /etc/php/8.x/fpm/pool.d/site1.conf
[site1]
user = site1_user
group = site1_user
listen = /run/php/php8.x-fpm-site1.sock
; /etc/php/8.x/fpm/pool.d/site2.conf
[site2]
user = site2_user
group = site2_user
listen = /run/php/php8.x-fpm-site2.sock

总结检查清单

场景 最小特权做法 风险点
数据库 使用专用只读/写入账号 使用root账户
文件系统 目录755/750,文件644/640 使用777
临时文件 私有temp目录,session独立 使用系统/tmp
命令执行 白名单+escapeshellarg 直接拼接用户输入
错误处理 日志详细,输出模糊 生产环境显示错误详情
PHP扩展 只加载需要的扩展 加载不必要扩展增加攻击面

核心思想:每次授予一个权限时,问自己:“如果这个权限被滥用,最坏的结果是什么?” 只给那些即使被滥用也不会造成严重后果的权限。

抱歉,评论功能暂时关闭!