| name | PHP Security Patterns |
| user-invocable | false |
| description | Use when essential PHP security patterns including input validation, SQL injection prevention, XSS protection, CSRF tokens, password hashing, secure session management, and defense-in-depth strategies for building secure PHP applications. |
| allowed-tools | [] |
PHP Security Patterns
Introduction
Security is paramount in PHP applications as they often handle sensitive user data,
authentication, and financial transactions. PHP's flexibility and dynamic nature
create opportunities for vulnerabilities if security best practices aren't followed.
Common PHP security vulnerabilities include SQL injection, cross-site scripting
(XSS), cross-site request forgery (CSRF), insecure password storage, session
hijacking, and file inclusion attacks. Each can lead to data breaches, unauthorized
access, or complete system compromise.
This skill covers input validation and sanitization, SQL injection prevention,
XSS protection, CSRF defense, secure password handling, session security, file
upload security, and defense-in-depth strategies.
Input Validation and Sanitization
Input validation ensures data meets expected formats before processing, while
sanitization removes or encodes potentially dangerous content.
<?php
declare(strict_types=1);
function validateEmail(string $email): bool {
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
function validateUrl(string $url): bool {
return filter_var($url, FILTER_VALIDATE_URL) !== false;
}
function validateInt(mixed $value, int $min = PHP_INT_MIN,
int $max = PHP_INT_MAX): ?int {
$int = filter_var($value, FILTER_VALIDATE_INT, [
'options' => [
'min_range' => $min,
'max_range' => $max,
],
]);
return $int !== false ? $int : null;
}
function sanitizeString(string $input): string {
$sanitized = str_replace("\0", '', $input);
$sanitized = preg_replace('/[\x00-\x1F\x7F]/u', '', $sanitized);
return trim($sanitized);
}
function sanitizeHtml(string $input): string {
return htmlspecialchars($input, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
class UserRegistration {
private array $errors = [];
public function validate(array $data): bool {
if (!isset($data['email']) || !validateEmail($data['email'])) {
$this->errors[] = 'Invalid email address';
}
$age = validateInt($data['age'] ?? null, 13, 120);
if ($age === null) {
$this->errors[] = 'Age must be between 13 and 120';
}
$username = sanitizeString($data['username'] ?? '');
if (!preg_match('/^[a-zA-Z0-9_]{3,20}$/', $username)) {
$this->errors[] = 'Username must be 3-20 alphanumeric characters';
}
if (!$this->validatePassword($data['password'] ?? '')) {
$this->errors[] = 'Password must be at least 8 characters ' .
'with mixed case and numbers';
}
return empty($this->errors);
}
private function validatePassword(string $password): bool {
return strlen($password) >= 8
&& preg_match('/[A-Z]/', $password)
&& preg_match('/[a-z]/', $password)
&& preg_match('/[0-9]/', $password);
}
public function getErrors(): array {
return $this->errors;
}
}
function validateStatus(string $status): ?string {
$allowed = ['pending', 'approved', 'rejected'];
return in_array($status, $allowed, true) ? $status : null;
}
function validateUserData(array $data): array {
$validated = [];
$validated['email'] = validateEmail($data['email'] ?? '')
? $data['email']
: throw new InvalidArgumentException('Invalid email');
$validated['age'] = validateInt($data['age'] ?? 0, 0, 150) ?? 18;
$validated['name'] = sanitizeString($data['name'] ?? '');
if (isset($data['address'])) {
$validated['address'] = [
'street' => sanitizeString($data['address']['street'] ?? ''),
'city' => sanitizeString($data['address']['city'] ?? ''),
'zip' => preg_match('/^\d{5}$/', $data['address']['zip'] ?? '')
? $data['address']['zip']
: null,
];
}
return $validated;
}
Always validate input at the application boundary and sanitize before output to
prevent injection attacks.
SQL Injection Prevention
SQL injection occurs when user input is directly interpolated into SQL queries,
allowing attackers to manipulate queries.
<?php
declare(strict_types=1);
function findUserUnsafe(PDO $pdo, string $email): ?array {
$sql = "SELECT * FROM users WHERE email = '$email'";
$result = $pdo->query($sql);
return $result ? $result->fetch(PDO::FETCH_ASSOC) : null;
}
function findUserSafe(PDO $pdo, string $email): ?array {
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $email]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result !== false ? $result : null;
}
{
= ->();
->([]);
= ->(PDO::);
!== ? : ;
}
{
= ->(
);
->(, , PDO::);
->(, , PDO::);
->();
->(PDO::);
}
{
= (, );
(()) {
[];
}
= (, (, (), ));
= ;
= ->();
->();
->(PDO::);
}
{
{}
{
= ->pdo->();
->([]);
= ->(PDO::);
!== ? : ;
}
{
= ->pdo->(
);
->([
[],
[],
[],
]);
() ->pdo->();
}
{
= ->pdo->(
);
->([
[],
[],
,
]);
}
{
= ->pdo->();
->([]);
}
{
= ->pdo->(
);
= ;
->(, , PDO::);
->(, , PDO::);
->(, , PDO::);
->();
->(PDO::);
}
}
{
= [];
= [];
{
= . (->params);
->where[] = ;
->params[] = ;
;
}
{
= ;
(!(->where)) {
.= . (, ->where);
}
= ->();
->(->params);
->(PDO::);
}
}
Always use prepared statements with parameter binding - never concatenate user
input into SQL queries.
Cross-Site Scripting (XSS) Prevention
XSS attacks inject malicious scripts into web pages viewed by other users. Proper
output encoding prevents script execution.
<?php
declare(strict_types=1);
function escapeHtml(string $text): string {
return htmlspecialchars($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
function escapeJs(string $text): string {
return json_encode($text, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
}
function escapeUrl(string $url): string {
return urlencode($url);
}
function escapeAttr(string $text): string {
return htmlspecialchars($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
class SafeTemplate {
private = [];
{
->data[] = ;
}
{
((function() {
(()) {
();
}
;
}, ->data));
();
;
();
}
{
->data[] ?? ;
}
}
{
{
= ([]);
= ([]);
= ([]);
;
}
{
= ([]);
= ([]);
= ([] ?? );
;
}
}
{
= [];
{
->directives[] = ;
;
}
{
->directives[] = ;
;
}
{
->directives[] = ;
;
}
{
->directives[] = ;
;
}
{
= [];
(->directives => ) {
[] = . . (, );
}
(, );
}
{
( . ->());
}
}
= ();
->()
->(, )
->(, )
->(, , )
->();
{
= [, , , , , , , ];
= [ => [, ]];
{
= (, ->allowedTags);
= ();
@->( . ,
LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
= ();
(->() ) {
= ->ownerElement->tagName;
= ->name;
(!(->allowedAttributes[])
|| !(, ->allowedAttributes[])) {
->ownerElement->();
}
( === ) {
= ->value;
(!(, )) {
->ownerElement->();
}
}
}
->();
}
}
Always escape output based on context (HTML, JavaScript, URL, attribute) and
implement Content Security Policy headers.
Cross-Site Request Forgery (CSRF) Prevention
CSRF attacks trick authenticated users into executing unwanted actions. Token
validation prevents unauthorized state-changing requests.
<?php
declare(strict_types=1);
class CsrfProtection {
private const TOKEN_NAME = 'csrf_token';
private const TOKEN_LENGTH = 32;
public function __construct(
private string $sessionKey = '_csrf_tokens'
) {
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
}
public function generateToken(string $action = 'default'): string {
$token = bin2hex(random_bytes(self::TOKEN_LENGTH));
if (!isset($_SESSION[$this->sessionKey])) {
$_SESSION[$this->sessionKey] = [];
}
$_SESSION[$this->sessionKey][$action] = [
'token' => ,
=> () + ,
];
;
}
{
(!([->sessionKey][])) {
;
}
= [->sessionKey][];
([] < ()) {
([->sessionKey][]);
;
}
= ([], );
() {
([->sessionKey][]);
}
;
}
{
= ->();
= (::);
= ();
;
}
{
= [::] ?? ;
->(, );
}
}
{
{}
{
([] === ) {
(!->csrf->()) {
();
();
}
}
();
}
}
{
{}
{
= ->csrf->();
;
}
{
= ->csrf->();
;
}
}
{
{}
{
= ->csrf->();
;
}
{
(!->csrf->(, )) {
();
();
}
->users->();
}
}
{
= ;
{
= (());
(
::,
,
[
=> () + ,
=> ,
=> ,
=> ,
=> ,
=> ,
]
);
;
}
{
= [::] ?? ;
(, );
}
}
Implement CSRF protection for all state-changing operations (POST, PUT, DELETE)
using synchronized tokens or double-submit cookies.
Secure Password Handling
Passwords must be hashed with strong algorithms and never stored in plain text.
<?php
declare(strict_types=1);
class PasswordManager {
private const MIN_LENGTH = 8;
private const MAX_LENGTH = 128;
public function hash(string $password): string {
if (strlen($password) < self::MIN_LENGTH ||
strlen($password) > self::MAX_LENGTH) {
throw new InvalidArgumentException('Invalid password length');
}
return password_hash($password, PASSWORD_DEFAULT);
}
public function verify(string $password, string $hash): bool {
return (, );
}
{
(, PASSWORD_DEFAULT);
}
{
(->()) {
->();
}
;
}
}
{
{
= [];
(() < ) {
[] = ;
}
(() > ) {
[] = ;
}
(!(, )) {
[] = ;
}
(!(, )) {
[] = ;
}
(!(, )) {
[] = ;
}
(!(, )) {
[] = ;
}
(->()) {
[] = ;
}
;
}
{
= [, , , , ];
((), , );
}
{
= ;
(() >= ) += ;
(() >= ) += ;
((, )) += ;
((, )) += ;
((, )) += ;
((, )) += ;
(, );
}
}
{
{}
{
= ->passwordManager->();
->users->([
=> ,
=> ,
]);
}
{
= ->users->();
(!) {
->passwordManager->();
;
}
(!->passwordManager->(, [])) {
;
}
= ->passwordManager->(
,
[]
);
() {
->users->([], );
}
;
}
}
{
= ;
{}
{
= ->users->();
(!) {
;
}
= (());
= (, );
= () + ::;
->users->([], , );
;
}
{
= (, );
= ->users->();
(!) {
;
}
;
}
{
= ->();
(!) {
;
}
= ();
= ->();
->users->(, );
->users->();
;
}
}
Always use password_hash() with PASSWORD_DEFAULT, validate password strength,
and implement secure password reset flows.
Session Security
Session hijacking and fixation attacks compromise user sessions. Proper session
management prevents unauthorized access.
<?php
declare(strict_types=1);
class SessionManager {
public function start(): void {
if (session_status() === PHP_SESSION_ACTIVE) {
return;
}
ini_set('session.cookie_httponly', '1');
ini_set('session.cookie_secure', '1');
ini_set('session.cookie_samesite', 'Strict');
ini_set('session.use_strict_mode', '1');
ini_set('session.use_only_cookies', '1');
ini_set('session.cookie_lifetime', '0');
session_start();
if (!isset($_SESSION['initiated'])) {
session_regenerate_id(true);
$_SESSION['initiated'] = true;
[] = ();
[] = [] ?? ;
[] = [] ?? ;
}
->();
}
{
(([])) {
= () - [];
( > ) {
->();
();
}
}
(([])) {
= [] ?? ;
([] !== ) {
->();
();
}
}
(([])) {
= () - [];
( > ) {
->();
}
}
}
{
();
[] = ();
}
{
= [];
(([()])) {
(
(),
,
[
=> () - ,
=> ,
=> ,
=> ,
=> ,
=> ,
]
);
}
();
}
{
[] = ;
}
{
[] ?? ;
}
{
([]);
}
{
([]);
}
}
{
{}
{
= ->users->();
(! ||
!->password->(, [])) {
((, ));
;
}
->session->();
->session->(, []);
->session->(, ());
->users->([]);
;
}
{
->session->();
}
{
->session->();
}
{
->session->();
}
{
(!->()) {
();
();
;
}
}
}
{
= ;
= ;
{}
{
= ->();
= ->();
[] = ();
(, ());
}
{
= ->();
= (, fn() =>
() - < ::
);
() >= ::;
}
{
(!->()) {
;
}
= ->();
= ((, fn() =>
() - < ::
));
:: - (() - );
}
{
= ->();
(()) {
();
}
}
{
= ->();
(!()) {
[];
}
= ();
(, ) ?: [];
}
{
= (, );
->storePath . . . ;
}
}
Configure sessions with secure flags, regenerate session IDs on privilege changes,
and implement session validation and timeout.
Best Practices
-
Validate all inputs at application boundaries before processing or storage
to prevent injection attacks
-
Use prepared statements exclusively for database queries to eliminate SQL
injection vulnerabilities
-
Escape all outputs based on context (HTML, JavaScript, URL, attribute)
before rendering
-
Implement CSRF protection for all state-changing operations with
synchronized tokens
-
Hash passwords with modern algorithms using password_hash() with
PASSWORD_DEFAULT setting
-
Configure secure session management with httponly, secure, and samesite
cookie flags
-
Apply defense in depth with multiple security layers rather than relying
on single mechanisms
-
Use Content Security Policy headers to restrict resource loading and
prevent XSS attacks
-
Implement rate limiting for authentication endpoints to prevent brute
force attacks
-
Keep dependencies updated and regularly audit for known security
vulnerabilities
Common Pitfalls
-
Trusting user input without validation allows attackers to inject
malicious data
-
Using string concatenation for SQL instead of prepared statements enables
SQL injection
-
Forgetting output encoding in templates allows XSS attacks through
user-generated content
-
Skipping CSRF protection on state-changing operations enables unauthorized
actions
-
Storing passwords in plain text or using weak hashing algorithms
compromises credentials
-
Not regenerating session IDs after login allows session fixation attacks
-
Using inadequate randomness for tokens with rand() instead of
random_bytes()
-
Exposing detailed error messages to users reveals system internals to
attackers
-
Not implementing rate limiting allows brute force and denial of service
attacks
-
Allowing unrestricted file uploads without validation enables remote
code execution
When to Use This Skill
Apply security patterns throughout all PHP application development, not as an
afterthought but as core architectural concerns.
Use input validation at every entry point where external data enters the
application, including forms, APIs, and file uploads.
Implement SQL injection prevention whenever constructing database queries,
preferring ORMs or query builders with parameterization.
Apply XSS protection in all templates and views where user-generated content is
displayed to other users.
Use CSRF protection for all authenticated endpoints that perform state-changing
operations like create, update, or delete.
Implement secure session management for any application requiring user
authentication and authorization.
Resources