php-security

Never trust user input. Validate everything from $_GET , $_POST , $_COOKIE , $_FILES , $_SERVER , and $_REQUEST . Defense in depth — layer multiple protections.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "php-security" with this command: npx skills add peixotorms/odinlayer-skills/peixotorms-odinlayer-skills-php-security

PHP Security

Core Principle

Never trust user input. Validate everything from $_GET , $_POST , $_COOKIE , $_FILES , $_SERVER , and $_REQUEST . Defense in depth — layer multiple protections.

SQL Injection Prevention

Use prepared statements with bound parameters for all queries. Never interpolate user input into SQL strings.

Rule Detail

Prepared statements only WHERE , VALUES , SET — parameterize all data values

Table/column names Validate against whitelist, never user input directly

Least-privilege DB accounts Don't use root; separate read/write accounts

Use PDO with exceptions $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)

Disable emulated prepares $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false)

See resources/validation-patterns.md for prepared statement and dynamic column name code examples.

XSS Prevention

Escape all user data on output. Use htmlspecialchars($val, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') (or a short e() helper) for HTML contexts.

Context Escaping

HTML body htmlspecialchars() with ENT_QUOTES

HTML attribute htmlspecialchars() with ENT_QUOTES

JavaScript json_encode() with JSON_HEX_TAG

URL parameter urlencode()

CSS Avoid user input in CSS; whitelist if needed

Set CSP headers: Content-Security-Policy: default-src 'self'; script-src 'self'

See resources/validation-patterns.md for output escaping and CSP code examples.

CSRF Protection

// Generate token session_start(); if (empty($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); }

// In form <input type="hidden" name="csrf_token" value="<?= e($_SESSION['csrf_token']) ?>">

// Validate on submit if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) { http_response_code(403); exit('Invalid CSRF token'); }

Rule Detail

Token per session Generate once, validate on every state-changing request

hash_equals()

Timing-safe comparison — prevents timing attacks

SameSite=Lax cookies Additional protection layer

Not needed for GET GET should never modify state

Password Security

// Hash with bcrypt (default, recommended) $hash = password_hash($password, PASSWORD_DEFAULT);

// Verify if (password_verify($inputPassword, $storedHash)) { // Authenticated }

// Check if rehash needed (algorithm/cost changes) if (password_needs_rehash($storedHash, PASSWORD_DEFAULT)) { $newHash = password_hash($inputPassword, PASSWORD_DEFAULT); // Update stored hash }

Rule Detail

Never store plaintext Always password_hash()

PASSWORD_DEFAULT

Auto-uses best available algorithm

PASSWORD_ARGON2ID

Stronger than bcrypt if available (PHP 7.3+)

Never use md5() / sha1()

Not designed for passwords — too fast

password_verify() only Don't compare hashes manually

Rehash on login password_needs_rehash() keeps hashes current

Input Validation & Sanitization

Use filter_var() / filter_input() for type validation. Key filters: FILTER_VALIDATE_EMAIL , FILTER_VALIDATE_INT (with min_range /max_range ), FILTER_VALIDATE_FLOAT , FILTER_VALIDATE_IP , FILTER_VALIDATE_URL , FILTER_VALIDATE_BOOL (with FILTER_NULL_ON_FAILURE ), FILTER_VALIDATE_DOMAIN , FILTER_VALIDATE_REGEXP .

Sanitization Filters (FILTER_SANITIZE_*)

Filter Effect Notes

FILTER_SANITIZE_FULL_SPECIAL_CHARS

Encodes <>"'& (like htmlspecialchars ) Preferred over deprecated FILTER_SANITIZE_STRING

FILTER_SANITIZE_EMAIL

Removes all except [a-zA-Z0-9!#$%&'*+-=?^_
{|}~@.[]]`

FILTER_SANITIZE_URL

Removes chars not valid in URLs

FILTER_SANITIZE_NUMBER_INT

Keeps only [0-9+-]

FILTER_SANITIZE_NUMBER_FLOAT

Keeps only [0-9+-]

  • optional . , eE

Use FILTER_FLAG_ALLOW_FRACTION

FILTER_SANITIZE_ADD_SLASHES

Applies addslashes() (PHP 7.3+)

FILTER_SANITIZE_ENCODED

URL-encodes string

FILTER_SANITIZE_STRING

DEPRECATED PHP 8.1 — use htmlspecialchars()

Output Escaping by Context

Context Function Example

HTML body/attribute htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')

<p><?= e($name) ?></p>

JavaScript json_encode($s, JSON_HEX_TAG | JSON_HEX_AMP)

var x = <?= json_encode($val) ?>

URL parameter urlencode($s) (space=+ ) or rawurlencode($s) (space=%20 ) ?q=<?= urlencode($q) ?>

Shell argument escapeshellarg($s)

Single arg only

Shell command escapeshellcmd($s)

Less safe — prefer escapeshellarg

CSS Avoid user input; whitelist if needed

Key Rules

Rule Detail

Validate type, format, range, length Before any processing

Whitelist over blacklist Accept known-good, reject everything else

Don't use $_REQUEST

Ambiguous source — specify $_GET , $_POST , $_COOKIE

$SERVER values can be spoofed HTTP* headers are user-controlled

filter_input() over $_POST

Reads from superglobal directly

FILTER_NULL_ON_FAILURE

Returns null instead of false — useful for booleans

FILTER_VALIDATE_EMAIL is basic Only validates syntax, not existence

FILTER_VALIDATE_URL ASCII only IDN domains always fail

Never unserialize() user data Use JSON; verify HMAC for stored serialized data

Array format in proc_open()

Bypasses shell entirely — safest option

See resources/validation-patterns.md for filter_var code examples, serialization security, and process execution patterns.

File Upload Security

$file = $_FILES['upload'];

// Validate if ($file['error'] !== UPLOAD_ERR_OK) { throw new UploadException('Upload failed'); }

// Check MIME type (don't trust $_FILES['type'] — user-controlled) $finfo = new finfo(FILEINFO_MIME_TYPE); $mime = $finfo->file($file['tmp_name']); $allowed = ['image/jpeg', 'image/png', 'image/gif']; if (!in_array($mime, $allowed, true)) { throw new ValidationException('Invalid file type'); }

// Check size if ($file['size'] > 5 * 1024 * 1024) { // 5MB throw new ValidationException('File too large'); }

// Generate safe filename — never use original $ext = match($mime) { 'image/jpeg' => '.jpg', 'image/png' => '.png', 'image/gif' => '.gif', }; $safeName = bin2hex(random_bytes(16)) . $ext;

// Move to non-web-accessible directory move_uploaded_file($file['tmp_name'], '/var/uploads/' . $safeName);

Rule Detail

Never trust $_FILES['name']

Generate random filename

Never trust $_FILES['type']

Check with finfo on actual file

Store outside web root Serve through PHP controller

Validate file content MIME check, size limits, dimension limits for images

No executable uploads Block .php , .phtml , .phar , etc.

Session Security

// Secure session configuration ini_set('session.cookie_httponly', '1'); // No JavaScript access ini_set('session.cookie_secure', '1'); // HTTPS only ini_set('session.cookie_samesite', 'Lax'); // CSRF protection ini_set('session.use_strict_mode', '1'); // Reject uninitialized IDs ini_set('session.use_only_cookies', '1'); // No session ID in URL

session_start();

// Regenerate ID on privilege change (login) session_regenerate_id(true);

// Destroy session properly session_unset(); session_destroy(); setcookie(session_name(), '', time() - 3600, '/');

Rule Detail

httponly cookies Prevents XSS session hijacking

secure flag Only send over HTTPS

Regenerate on login Prevents session fixation

Regenerate on privilege change Elevation of privilege attacks

Absolute timeout Expire sessions after N hours regardless of activity

Idle timeout Expire after N minutes of inactivity

Filesystem Security

// BAD: Path traversal attack $file = $_GET['file']; include("/templates/$file"); // ../../etc/passwd

// GOOD: Validate and constrain $allowed = ['header', 'footer', 'sidebar']; $file = $_GET['file']; if (!in_array($file, $allowed, true)) { throw new NotFoundException(); } include("/templates/{$file}.php");

// GOOD: realpath validation $basePath = realpath('/var/uploads'); $fullPath = realpath("/var/uploads/{$filename}"); if ($fullPath === false || !str_starts_with($fullPath, $basePath)) { throw new SecurityException('Path traversal detected'); }

Rule Detail

Whitelist allowed paths Never build paths from user input directly

realpath()

  • prefix check Resolves symlinks, detects .. traversal

basename() strips directories But don't rely on it alone

Restrict PHP user permissions Principle of least privilege

Log file operations Audit trail for sensitive access

Cryptography

// Random bytes for tokens, keys, nonces $token = bin2hex(random_bytes(32)); // 64-char hex string $apiKey = base64_encode(random_bytes(32));

// HMAC for data integrity $signature = hash_hmac('sha256', $data, $secretKey); if (!hash_equals($expected, $signature)) { throw new SecurityException('Invalid signature'); }

// Encryption with sodium (PHP 7.2+) $key = sodium_crypto_secretbox_keygen(); $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); $encrypted = sodium_crypto_secretbox($plaintext, $nonce, $key); $decrypted = sodium_crypto_secretbox_open($encrypted, $nonce, $key);

Rule Detail

random_bytes() / random_int()

For all security-sensitive random — never rand() or mt_rand()

hash_equals()

Timing-safe comparison for tokens, signatures

Sodium over OpenSSL Simpler API, harder to misuse

Never roll your own crypto Use well-tested libraries

Store keys outside code Environment variables or secret management

HTTP Security Headers

// Essential headers header('Strict-Transport-Security: max-age=31536000; includeSubDomains'); header("Content-Security-Policy: default-src 'self'"); header('X-Content-Type-Options: nosniff'); header('X-Frame-Options: DENY'); header('Referrer-Policy: strict-origin-when-cross-origin'); header('Permissions-Policy: camera=(), microphone=(), geolocation=()');

// Hide PHP version ini_set('expose_php', '0');

php.ini Hardening (OWASP)

Error and Information Disclosure

Directive Production Value Purpose

expose_php

Off

Hide PHP version from headers

display_errors

Off

Never show errors to users

display_startup_errors

Off

Hide startup errors

log_errors

On

Log to file, not stdout

error_reporting

E_ALL

Report everything, log everything

error_log

/var/log/php/error.log

Secure log location

Filesystem and Includes

Directive Production Value Purpose

open_basedir

/var/www/app/

Restrict file access to app directory

allow_url_fopen

Off

Prevent remote file inclusion

allow_url_include

Off

Blocks remote code inclusion

doc_root

Set appropriately Limit accessible filesystem

Disable Unused Functions

Use disable_functions in php.ini to disable functions your application does not need. Audit which functions are actually used. Common candidates: phpinfo , show_source , pcntl_fork , pcntl_exec , and any shell-related functions not required by the app.

File Uploads (php.ini)

Directive Value Purpose

file_uploads

Off (if unused) Disable entirely if not needed

upload_max_filesize

2M (or app minimum) Limit upload size

max_file_uploads

2 (or app minimum) Limit concurrent uploads

upload_tmp_dir

Dedicated directory Isolate temp uploads

Session Hardening (php.ini)

Directive Value Purpose

session.use_strict_mode

1

Reject uninitialized session IDs

session.use_only_cookies

1

Never pass session ID in URL

session.cookie_httponly

1

Block JavaScript access to session cookie

session.cookie_secure

1

HTTPS-only session cookies

session.cookie_samesite

Strict

Strongest CSRF protection

session.sid_length

128

Longer session IDs harder to brute-force

session.gc_maxlifetime

600

Expire idle sessions (10 min)

session.name

Custom value Avoid default PHPSESSID — reduces fingerprinting

Resource Limits

Directive Value Purpose

max_execution_time

30

Prevent runaway scripts

max_input_time

30

Limit input parsing time

memory_limit

128M

Prevent memory exhaustion

post_max_size

8M

Limit POST body size

Error Exposure

Rule Detail

display_errors = Off in production Errors reveal paths, DB structure, code

log_errors = On

Log to file or syslog, not stdout

Custom error pages Show generic message, log details

Never expose DB errors Attackers use them for reconnaissance

Hide X-Powered-By

expose_php = Off in php.ini

Common Vulnerability Patterns

Vulnerability Prevention

SQL injection Prepared statements with bound parameters

XSS (stored/reflected) htmlspecialchars()

  • CSP headers

CSRF Token per session + SameSite cookies

Session fixation session_regenerate_id(true) on login

Path traversal realpath()

  • prefix validation

File upload attacks MIME validation + random names + store outside webroot

Timing attacks hash_equals() for all comparisons

Insecure deserialization Never unserialize() user data; use JSON

PHP Object Injection unserialize() triggers __wakeup() , __destruct() magic methods — attacker-crafted objects can delete files, inject SQL, run code

Remote File Inclusion allow_url_include = Off , allow_url_fopen = Off in php.ini

Open redirect Whitelist redirect targets

Mass assignment Explicit field lists, never extract() on user data

Header injection Validate/strip \r\n from header values

Command injection escapeshellarg()

  • avoid shell functions when possible

Information disclosure Disable display_errors , expose_php , phpinfo()

Automated Taint Analysis

Psalm can trace user input from source (e.g., $_GET ) to dangerous sinks (e.g., SQL query, echo ) and flag injection paths automatically:

vendor/bin/psalm --taint-analysis

Catches SQL injection, XSS, and other data-flow vulnerabilities that manual review misses. Use alongside manual code review, not as replacement.

Security Checklist

  • declare(strict_types=1) in every file

  • === for all comparisons (especially auth checks)

  • Prepared statements for all database queries

  • htmlspecialchars() for all HTML output

  • CSRF tokens on all state-changing forms

  • password_hash() / password_verify() for passwords

  • random_bytes() for tokens and keys

  • Session cookies: httponly , secure , samesite

  • File uploads validated by content, not name/extension

  • display_errors = Off in production

  • CSP and security headers set

  • expose_php = Off

  • open_basedir restricts file access

  • allow_url_include = Off and allow_url_fopen = Off

  • disable_functions configured for unused dangerous functions

  • session.sid_length >= 128 , custom session.name

  • No $_REQUEST usage — specify input source

  • No unserialize() on user data — use JSON

  • escapeshellarg() or array format for process execution

  • Regex patterns tested for catastrophic backtracking (ReDoS)

  • cURL: CURLOPT_SSL_VERIFYPEER enabled in production

  • filter_var() / filter_input() for input validation

  • Psalm taint analysis in CI (--taint-analysis )

Resources

  • resources/validation-patterns.md — Detailed code examples for SQL injection prevention, XSS output escaping, input validation filters, serialization security, and process execution security

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Security

wp-security

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

elementor-development

No summary provided by upstream source.

Repository SourceNeeds Review
General

elementor-hooks

No summary provided by upstream source.

Repository SourceNeeds Review