浅谈PHP安全规范
字数 1187 2025-08-18 11:37:45

PHP安全规范与最佳实践

1. PHP基础安全配置

1.1 敏感配置项

; 不在请求头中泄露PHP信息
expose_php=Off

; 错误处理配置
display_errors=Off
display_startup_errors=off
log_errors=On
error_log=/var/log/httpd/php_scripts_error.log

; 文件上传配置
file_uploads=On
upload_max_filesize=1M
post_max_size=1M  ; 必须大于upload_max_filesize

; 远程代码执行防护
allow_url_fopen=Off
allow_url_include=Off

; 资源限制
max_execution_time = 30
max_input_time = 30
memory_limit = 40M

; 会话配置
session.save_path="/var/lib/php/session"

1.2 禁用危险函数

disable_functions = phpinfo,eval,passthru,assert,exec,system,ini_set,ini_get,get_included_files,get_defined_functions,get_defined_constants,get_defined_vars,glob,``,chroot,scandir,chgrp,chown,shell_exec,proc_open,proc_get_status,ini_alter,ini_restore,dl,pfsockopen,openlog,syslog,readlink,symlink,popepassthru,stream_socket_server,fsocket,fsockopen

1.3 文件系统访问限制

open_basedir='/var/www/html/'

2. PHP弱类型安全问题

2.1 常见弱类型比较问题

0=='0' //true
0 == 'abcdefg' //true
1 == '1abcdef' //true
null==false //true
123=='123' //true

// 哈希比较
"0e132456789"=="0e7124511451155" //true
"0x1e240"=="123456" //true
"0x1e240"==123456 //true

// 类型转换
var_dump(intval('2')) //2
var_dump(intval('3abcd')) //3
var_dump(intval('abcd')) //0

// 数组比较
var_dump(md5($array1)==var_dump($array2)); //true

// switch比较
$i ="2abc";
switch ($i) {
    case 0:case 1:case 2: 
        echo "i is less than 3 but not negative"; 
        break;
    case 3: echo "i is 3";
}

// in_array问题
$array=[0,1,2,'3'];
var_dump(in_array('abc', $array)); //true
var_dump(in_array('1bc', $array)); //true

3. 常见漏洞防护

3.1 SQL注入防护

不安全示例(Low level):

$id = $_REQUEST['id'];
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysql_query($query);

安全示例(Impossible level):

// 检查Anti-CSRF token
checkToken($_REQUEST['user_token'], $_SESSION['session_token'], 'index.php');

// 获取并验证输入
$id = $_GET['id'];
if(is_numeric($id)) {
    // 使用预处理语句
    $data = $db->prepare('SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;');
    $data->bindParam(':id', $id, PDO::PARAM_INT);
    $data->execute();
    
    if($data->rowCount() == 1) {
        $row = $data->fetch();
        // 安全输出
        echo "<pre>ID: {$id}<br />First name: {$row['first_name']}<br />Surname: {$row['last_name']}</pre>";
    }
}

3.2 CSRF防护

不安全示例(Low level):

if(isset($_GET['Change'])) {
    $pass_new = $_GET['password_new'];
    $pass_conf = $_GET['password_conf'];
    if($pass_new == $pass_conf) {
        // 直接修改密码
        $insert = "UPDATE `users` SET password = '".md5($pass_new)."' WHERE user = '".dvwaCurrentUser()."'";
        mysql_query($insert);
    }
}

安全示例(Impossible level):

if(isset($_POST['Change'])) {
    // 检查Anti-CSRF token
    checkToken($_REQUEST['user_token'], $_SESSION['session_token'], 'index.php');
    
    // 获取输入
    $pass_curr = $_GET['password_current'];
    $pass_new = $_GET['password_new'];
    $pass_conf = $_GET['password_conf'];
    
    // 清理和验证当前密码
    $pass_curr = stripslashes($pass_curr);
    $pass_curr = mysql_real_escape_string($pass_curr);
    $pass_curr = md5($pass_curr);
    
    // 验证当前密码是否正确
    $data = $db->prepare('SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;');
    $data->bindParam(':user', dvwaCurrentUser(), PDO::PARAM_STR);
    $data->bindParam(':password', $pass_curr, PDO::PARAM_STR);
    $data->execute();
    
    // 验证新密码和确认密码是否匹配
    if(($pass_new == $pass_conf) && ($data->rowCount() == 1)) {
        // 更新密码
        $pass_new = stripslashes($pass_new);
        $pass_new = mysql_real_escape_string($pass_new);
        $pass_new = md5($pass_new);
        
        $data = $db->prepare('UPDATE users SET password = (:password) WHERE user = (:user);');
        $data->bindParam(':password', $pass_new, PDO::PARAM_STR);
        $data->bindParam(':user', dvwaCurrentUser(), PDO::PARAM_STR);
        $data->execute();
    }
}

3.3 命令注入防护

不安全示例(Low level):

$target = $_REQUEST['ip'];
if(stristr(php_uname('s'), 'Windows NT')) {
    $cmd = shell_exec('ping '.$target);
} else {
    $cmd = shell_exec('ping -c 4 '.$target);
}
echo "<pre>{$cmd}</pre>";

安全示例(Impossible level):

// 检查Anti-CSRF token
checkToken($_REQUEST['user_token'], $_SESSION['session_token'], 'index.php');

// 获取并验证IP地址
$target = $_REQUEST['ip'];
$target = stripslashes($target);
$octet = explode(".", $target);

if((is_numeric($octet[0])) && (is_numeric($octet[1])) && 
   (is_numeric($octet[2])) && (is_numeric($octet[3])) && 
   (sizeof($octet) == 4)) {
    // 重建合法IP
    $target = $octet[0].'.'.$octet[1].'.'.$octet[2].'.'.$octet[3];
    
    // 执行ping命令
    if(stristr(php_uname('s'), 'Windows NT')) {
        $cmd = shell_exec('ping '.$target);
    } else {
        $cmd = shell_exec('ping -c 4 '.$target);
    }
    echo "<pre>{$cmd}</pre>";
} else {
    echo '<pre>ERROR: You have entered an invalid IP.</pre>';
}

3.4 暴力破解防护

不安全示例(Low level):

$user = $_GET['username'];
$pass = md5($_GET['password']);
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysql_query($query);
if($result && mysql_num_rows($result) == 1) {
    // 登录成功
}

安全示例(Impossible level):

// 默认值
$total_failed_login = 3;
$lockout_time = 15;
$account_locked = false;

// 检查数据库中的失败登录次数
$data = $db->prepare('SELECT failed_login, last_login FROM users WHERE user = (:user) LIMIT 1;');
$data->bindParam(':user', $user, PDO::PARAM_STR);
$data->execute();
$row = $data->fetch();

// 检查用户是否被锁定
if(($data->rowCount() == 1) && ($row['failed_login'] >= $total_failed_login)) {
    $last_login = strtotime($row['last_login']);
    $timeout = strtotime("{$last_login} +{$lockout_time} minutes");
    $timenow = strtotime("now");
    
    if($timenow < $timeout) {
        $account_locked = true;
    }
}

// 验证用户名和密码
$data = $db->prepare('SELECT * FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;');
$data->bindParam(':user', $user, PDO::PARAM_STR);
$data->bindParam(':password', $pass, PDO::PARAM_STR);
$data->execute();
$row = $data->fetch();

if(($data->rowCount() == 1) && ($account_locked == false)) {
    // 登录成功,重置失败计数
    $data = $db->prepare('UPDATE users SET failed_login = "0" WHERE user = (:user) LIMIT 1;');
    $data->bindParam(':user', $user, PDO::PARAM_STR);
    $data->execute();
} else {
    // 登录失败,增加失败计数
    sleep(rand(2, 4)); // 随机延迟
    $data = $db->prepare('UPDATE users SET failed_login = (failed_login + 1) WHERE user = (:user) LIMIT 1;');
    $data->bindParam(':user', $user, PDO::PARAM_STR);
    $data->execute();
    
    // 更新最后登录时间
    $data = $db->prepare('UPDATE users SET last_login = now() WHERE user = (:user) LIMIT 1;');
    $data->bindParam(':user', $user, PDO::PARAM_STR);
    $data->execute();
}

3.5 文件包含漏洞防护

不安全示例(Low level):

$file = $_GET['page'];
include($file);

安全示例(Impossible level):

$file = $_GET['page'];
// 只允许包含特定文件
if($file != "include.php" && $file != "file1.php" && $file != "file2.php" && $file != "file3.php") {
    echo "ERROR: File not found!";
    exit;
}
include($file);

3.6 文件上传漏洞防护

不安全示例(Low level):

$target_path = "uploads/";
$target_path .= basename($_FILES['uploaded']['name']);
move_uploaded_file($_FILES['uploaded']['tmp_name'], $target_path);

安全示例(Impossible level):

// 检查Anti-CSRF token
checkToken($_REQUEST['user_token'], $_SESSION['session_token'], 'index.php');

// 文件信息
$uploaded_name = $_FILES['uploaded']['name'];
$uploaded_ext = substr($uploaded_name, strrpos($uploaded_name, '.') + 1);
$uploaded_size = $_FILES['uploaded']['size'];
$uploaded_type = $_FILES['uploaded']['type'];
$uploaded_tmp = $_FILES['uploaded']['tmp_name'];

// 目标路径
$target_path = 'uploads/';
$target_file = md5(uniqid() . $uploaded_name) . '.' . $uploaded_ext;

// 验证文件类型和大小
if((strtolower($uploaded_ext) == 'jpg' || strtolower($uploaded_ext) == 'jpeg' || strtolower($uploaded_ext) == 'png') &&
   ($uploaded_size < 100000) &&
   ($uploaded_type == 'image/jpeg' || $uploaded_type == 'image/png') &&
   getimagesize($uploaded_tmp)) {
    
    // 重新编码图像以去除元数据
    if($uploaded_type == 'image/jpeg') {
        $img = imagecreatefromjpeg($uploaded_tmp);
        imagejpeg($img, $temp_file, 100);
    } else {
        $img = imagecreatefrompng($uploaded_tmp);
        imagepng($img, $temp_file, 9);
    }
    imagedestroy($img);
    
    // 移动文件
    if(rename($temp_file, $target_path . $target_file)) {
        echo "<pre><a href='{$target_path}{$target_file}'>{$target_file}</a> succesfully uploaded!</pre>";
    }
}

3.7 XSS防护

反射型XSS不安全示例(Low level):

echo '<pre>Hello ' . $_GET['name'] . '</pre>';

存储型XSS安全示例(Impossible level):

// 检查Anti-CSRF token
checkToken($_REQUEST['user_token'], $_SESSION['session_token'], 'index.php');

// 获取并清理输入
$message = trim($_POST['mtxMessage']);
$name = trim($_POST['txtName']);

// 清理消息输入
$message = stripslashes($message);
$message = mysql_real_escape_string($message);
$message = htmlspecialchars($message);

// 清理名称输入
$name = stripslashes($name);
$name = mysql_real_escape_string($name);
$name = htmlspecialchars($name);

// 使用预处理语句插入数据库
$data = $db->prepare('INSERT INTO guestbook (comment, name) VALUES (:message, :name);');
$data->bindParam(':message', $message, PDO::PARAM_STR);
$data->bindParam(':name', $name, PDO::PARAM_STR);
$data->execute();

4. PHP伪协议

PHP支持多种伪协议,可能被用于攻击:

  • file:///var/www/html - 访问本地文件系统
  • ftp://<login>:<password>@<ftpserveraddress> - 访问FTP(s) URLs
  • data:// - 数据流
  • http:// - 访问HTTP(s) URLs
  • php:// - 访问各个输入/输出流
  • zlib:// - 压缩流
  • data:// - Data (RFC 2397)
  • glob:// - 查找匹配的文件路径模式
  • phar:// - PHP Archives
  • sh2:// - Secure Shell 2
  • rar:// - RAR
  • ogg:// - Audio streams
  • expect:// - 处理交互式的流

5. 安全开发建议

  1. 永远不要信任用户输入:所有用户输入都应被视为不可信的,必须进行验证和过滤
  2. 使用预处理语句:防止SQL注入的最有效方法
  3. 实施CSRF防护:为所有敏感操作使用Anti-CSRF token
  4. 安全的会话管理:使用安全的会话配置和适当的会话超时
  5. 安全的文件处理:限制文件系统访问,安全处理文件上传
  6. 安全的错误处理:生产环境中不应显示详细错误信息
  7. 使用最新版本的PHP:保持PHP版本更新以获取安全修复
  8. 禁用危险函数:通过php.ini禁用不必要的危险函数
  9. 实施适当的访问控制:确保用户只能访问他们被授权的资源
  10. 安全的密码存储:使用强哈希算法如bcrypt存储密码

通过遵循这些安全规范和最佳实践,可以显著提高PHP应用程序的安全性,减少常见漏洞的风险。

PHP安全规范与最佳实践 1. PHP基础安全配置 1.1 敏感配置项 1.2 禁用危险函数 1.3 文件系统访问限制 2. PHP弱类型安全问题 2.1 常见弱类型比较问题 3. 常见漏洞防护 3.1 SQL注入防护 不安全示例(Low level): 安全示例(Impossible level): 3.2 CSRF防护 不安全示例(Low level): 安全示例(Impossible level): 3.3 命令注入防护 不安全示例(Low level): 安全示例(Impossible level): 3.4 暴力破解防护 不安全示例(Low level): 安全示例(Impossible level): 3.5 文件包含漏洞防护 不安全示例(Low level): 安全示例(Impossible level): 3.6 文件上传漏洞防护 不安全示例(Low level): 安全示例(Impossible level): 3.7 XSS防护 反射型XSS不安全示例(Low level): 存储型XSS安全示例(Impossible level): 4. PHP伪协议 PHP支持多种伪协议,可能被用于攻击: file:///var/www/html - 访问本地文件系统 ftp://<login>:<password>@<ftpserveraddress> - 访问FTP(s) URLs data:// - 数据流 http:// - 访问HTTP(s) URLs php:// - 访问各个输入/输出流 zlib:// - 压缩流 data:// - Data (RFC 2397) glob:// - 查找匹配的文件路径模式 phar:// - PHP Archives sh2:// - Secure Shell 2 rar:// - RAR ogg:// - Audio streams expect:// - 处理交互式的流 5. 安全开发建议 永远不要信任用户输入 :所有用户输入都应被视为不可信的,必须进行验证和过滤 使用预处理语句 :防止SQL注入的最有效方法 实施CSRF防护 :为所有敏感操作使用Anti-CSRF token 安全的会话管理 :使用安全的会话配置和适当的会话超时 安全的文件处理 :限制文件系统访问,安全处理文件上传 安全的错误处理 :生产环境中不应显示详细错误信息 使用最新版本的PHP :保持PHP版本更新以获取安全修复 禁用危险函数 :通过php.ini禁用不必要的危险函数 实施适当的访问控制 :确保用户只能访问他们被授权的资源 安全的密码存储 :使用强哈希算法如bcrypt存储密码 通过遵循这些安全规范和最佳实践,可以显著提高PHP应用程序的安全性,减少常见漏洞的风险。