引言Web安全是PHP开发中最关键的环节之一。从输入验证到输出转义从认证授权到加密存储每个环节的疏忽都可能导致安全漏洞。本文将深入PHP安全编程的核心实践。SQL注入防护SQL注入是最危险的Web安全漏洞之一通过参数化查询和正确使用预处理语句可以有效防御。class DatabaseSecurity{private PDO $pdo;public function __construct(){$this-pdo new PDO(mysql:hostlocalhost;dbnametest;charsetutf8mb4,root,,[PDO::ATTR_ERRMODE PDO::ERRMODE_EXCEPTION,PDO::ATTR_DEFAULT_FETCH_MODE PDO::FETCH_ASSOC,PDO::ATTR_EMULATE_PREPARES false,]);}// 安全参数化查询public function findUserByEmail(string $email): ?array{$sql SELECT * FROM users WHERE email :email;$stmt $this-pdo-prepare($sql);$stmt-execute([email $email]);return $stmt-fetch() ?: null;}// 安全IN 子句public function findUsersByIds(array $ids): array{if (empty($ids)) {return [];}$placeholders implode(, , array_fill(0, count($ids), ?));$sql SELECT * FROM users WHERE id IN ($placeholders);$stmt $this-pdo-prepare($sql);$stmt-execute(array_values($ids));return $stmt-fetchAll();}// 安全LIKE 查询public function searchUsers(string $query): array{$sql SELECT * FROM users WHERE name LIKE :query;$stmt $this-pdo-prepare($sql);$stmt-execute([query % . $query . %]);return $stmt-fetchAll();}// 安全ORDER BY 和 LIMIT 白名单验证public function getUsers(string $sortBy, string $sortOrder, int $limit, int $offset): array{$allowedSortColumns [id, name, email, created_at];$sortBy in_array($sortBy, $allowedSortColumns, true) ? $sortBy : id;$sortOrder strtoupper($sortOrder) DESC ? DESC : ASC;$sql sprintf(SELECT * FROM users ORDER BY %s %s LIMIT :limit OFFSET :offset,$sortBy,$sortOrder);$stmt $this-pdo-prepare($sql);$stmt-execute([limit $limit, offset $offset]);return $stmt-fetchAll();}// 批量插入安全public function batchInsertUsers(array $users): int{$sql INSERT INTO users (name, email, password_hash) VALUES (:name, :email, :password_hash);$stmt $this-pdo-prepare($sql);$count 0;foreach ($users as $user) {try {$stmt-execute([name $user[name],email $user[email],password_hash $user[password_hash],]);$count;} catch (PDOException $e) {error_log(Insert failed for user {$user[email]}: . $e-getMessage());}}return $count;}}// 拒绝字符串拼接高危function unsafeQuery(string $id): void{// NEVER DO THIS:$sql SELECT * FROM users WHERE id $id;// 如果 $id 1; DROP TABLE users; --后果灾难性}XSS防护跨站脚本攻击通过注入恶意脚本窃取用户数据。输出转义和内容安全策略是主要防御手段。class XssProtector{// HTML 上下文转义public static function escapeHtml(string $input): string{return htmlspecialchars($input, ENT_QUOTES | ENT_SUBSTITUTE, UTF-8);}// HTML 属性上下文转义public static function escapeAttribute(string $input): string{return htmlspecialchars($input, ENT_QUOTES | ENT_SUBSTITUTE, UTF-8, true);}// JavaScript 上下文转义public static function escapeJs(string $input): string{$json json_encode($input, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);return $json ! false ? $json : ;}// CSS 上下文转义public static function escapeCss(string $input): string{$escaped ;for ($i 0; $i strlen($input); $i) {$char $input[$i];if (!preg_match(/[a-zA-Z0-9]/, $char)) {$escaped . \\ . dechex(ord($char)) . ;} else {$escaped . $char;}}return $escaped;}// URL 上下文转义public static function escapeUrl(string $input): string{$parsed parse_url($input);if ($parsed false) {return ;}$scheme isset($parsed[scheme]) in_array(strtolower($parsed[scheme]), [http, https, ftp], true)? $parsed[scheme] . ://: ;if (empty($scheme)) {return ;}$host $parsed[host] ?? ;$path isset($parsed[path]) ? implode(/, array_map(rawurlencode, explode(/, $parsed[path]))) : ;$query isset($parsed[query]) ? ? . rawurlencode($parsed[query]) : ;return $scheme . $host . $path . $query;}// 富文本过滤使用自定义白名单public static function filterRichText(string $html): string{$allowedTags [p, br, b, i, u, strong, em,ul, ol, li, a, img, blockquote,code, pre, h1, h2, h3, h4, h5, h6,span, div, table, tr, td, th,];$allowedAttributes [a [href],img [src, alt, width, height],* [class, id, style],];return strip_tags($html, . implode(, $allowedTags) . );}}// 安全模板渲染class SafeTemplate{private array $data [];public function assign(string $key, mixed $value): void{$this-data[$key] $value;}public function render(string $template): string{$output $template;foreach ($this-data as $key $value) {if (is_string($value)) {$escaped XssProtector::escapeHtml($value);$output str_replace({{{$key}}}, $escaped, $output);$output str_replace({{{ {$key} }}}, $value, $output);}}return $output;}}$template new SafeTemplate();$template-assign(username, );$template-assign(link, https://evil.com/?q
PHP安全编程与防护
发布时间:2026/5/28 17:13:28
引言Web安全是PHP开发中最关键的环节之一。从输入验证到输出转义从认证授权到加密存储每个环节的疏忽都可能导致安全漏洞。本文将深入PHP安全编程的核心实践。SQL注入防护SQL注入是最危险的Web安全漏洞之一通过参数化查询和正确使用预处理语句可以有效防御。class DatabaseSecurity{private PDO $pdo;public function __construct(){$this-pdo new PDO(mysql:hostlocalhost;dbnametest;charsetutf8mb4,root,,[PDO::ATTR_ERRMODE PDO::ERRMODE_EXCEPTION,PDO::ATTR_DEFAULT_FETCH_MODE PDO::FETCH_ASSOC,PDO::ATTR_EMULATE_PREPARES false,]);}// 安全参数化查询public function findUserByEmail(string $email): ?array{$sql SELECT * FROM users WHERE email :email;$stmt $this-pdo-prepare($sql);$stmt-execute([email $email]);return $stmt-fetch() ?: null;}// 安全IN 子句public function findUsersByIds(array $ids): array{if (empty($ids)) {return [];}$placeholders implode(, , array_fill(0, count($ids), ?));$sql SELECT * FROM users WHERE id IN ($placeholders);$stmt $this-pdo-prepare($sql);$stmt-execute(array_values($ids));return $stmt-fetchAll();}// 安全LIKE 查询public function searchUsers(string $query): array{$sql SELECT * FROM users WHERE name LIKE :query;$stmt $this-pdo-prepare($sql);$stmt-execute([query % . $query . %]);return $stmt-fetchAll();}// 安全ORDER BY 和 LIMIT 白名单验证public function getUsers(string $sortBy, string $sortOrder, int $limit, int $offset): array{$allowedSortColumns [id, name, email, created_at];$sortBy in_array($sortBy, $allowedSortColumns, true) ? $sortBy : id;$sortOrder strtoupper($sortOrder) DESC ? DESC : ASC;$sql sprintf(SELECT * FROM users ORDER BY %s %s LIMIT :limit OFFSET :offset,$sortBy,$sortOrder);$stmt $this-pdo-prepare($sql);$stmt-execute([limit $limit, offset $offset]);return $stmt-fetchAll();}// 批量插入安全public function batchInsertUsers(array $users): int{$sql INSERT INTO users (name, email, password_hash) VALUES (:name, :email, :password_hash);$stmt $this-pdo-prepare($sql);$count 0;foreach ($users as $user) {try {$stmt-execute([name $user[name],email $user[email],password_hash $user[password_hash],]);$count;} catch (PDOException $e) {error_log(Insert failed for user {$user[email]}: . $e-getMessage());}}return $count;}}// 拒绝字符串拼接高危function unsafeQuery(string $id): void{// NEVER DO THIS:$sql SELECT * FROM users WHERE id $id;// 如果 $id 1; DROP TABLE users; --后果灾难性}XSS防护跨站脚本攻击通过注入恶意脚本窃取用户数据。输出转义和内容安全策略是主要防御手段。class XssProtector{// HTML 上下文转义public static function escapeHtml(string $input): string{return htmlspecialchars($input, ENT_QUOTES | ENT_SUBSTITUTE, UTF-8);}// HTML 属性上下文转义public static function escapeAttribute(string $input): string{return htmlspecialchars($input, ENT_QUOTES | ENT_SUBSTITUTE, UTF-8, true);}// JavaScript 上下文转义public static function escapeJs(string $input): string{$json json_encode($input, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);return $json ! false ? $json : ;}// CSS 上下文转义public static function escapeCss(string $input): string{$escaped ;for ($i 0; $i strlen($input); $i) {$char $input[$i];if (!preg_match(/[a-zA-Z0-9]/, $char)) {$escaped . \\ . dechex(ord($char)) . ;} else {$escaped . $char;}}return $escaped;}// URL 上下文转义public static function escapeUrl(string $input): string{$parsed parse_url($input);if ($parsed false) {return ;}$scheme isset($parsed[scheme]) in_array(strtolower($parsed[scheme]), [http, https, ftp], true)? $parsed[scheme] . ://: ;if (empty($scheme)) {return ;}$host $parsed[host] ?? ;$path isset($parsed[path]) ? implode(/, array_map(rawurlencode, explode(/, $parsed[path]))) : ;$query isset($parsed[query]) ? ? . rawurlencode($parsed[query]) : ;return $scheme . $host . $path . $query;}// 富文本过滤使用自定义白名单public static function filterRichText(string $html): string{$allowedTags [p, br, b, i, u, strong, em,ul, ol, li, a, img, blockquote,code, pre, h1, h2, h3, h4, h5, h6,span, div, table, tr, td, th,];$allowedAttributes [a [href],img [src, alt, width, height],* [class, id, style],];return strip_tags($html, . implode(, $allowedTags) . );}}// 安全模板渲染class SafeTemplate{private array $data [];public function assign(string $key, mixed $value): void{$this-data[$key] $value;}public function render(string $template): string{$output $template;foreach ($this-data as $key $value) {if (is_string($value)) {$escaped XssProtector::escapeHtml($value);$output str_replace({{{$key}}}, $escaped, $output);$output str_replace({{{ {$key} }}}, $value, $output);}}return $output;}}$template new SafeTemplate();$template-assign(username, );$template-assign(link, https://evil.com/?q