PHP Form Security
Why PHP Form Security is Critical
Every web form is a potential attack vector. Without proper security measures, your PHP forms can be exploited for:
- Cross-Site Scripting (XSS) attacks
- SQL Injection attacks
- CSRF (Cross-Site Request Forgery)
- Spam submissions
- Data corruption
This guide covers professional-grade PHP form security techniques to bulletproof your web applications.
Essential PHP Form Security Layers
1. Input Sanitization
function secure_input($data) {
$data = trim($data); // Remove whitespace
$data = stripslashes($data); // Remove backslashes
$data = htmlspecialchars($data, ENT_QUOTES | ENT_HTML5, 'UTF-8');
return $data;
}
// Usage:
$username = secure_input($_POST['username']);
Why this matters:
- Prevents XSS attacks by converting <script> to <script>
- Standardizes input format
- Protects against malformed data
2. Validation Rules with Regular Expressions
// Validate email format
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors['email'] = "Invalid email format";
}
// Validate username (alphanumeric + underscore, 3-20 chars)
if (!preg_match("/^[a-zA-Z0-9_]{3,20}$/", $username)) {
$errors['username'] = "Only letters, numbers and underscore (3-20 chars)";
}
// Validate URL
if (!empty($website) && !filter_var($website, FILTER_VALIDATE_URL)) {
$errors['website'] = "Invalid website URL";
}
Output Examples:
✅ Valid: user_123, test@example.com
❌ Invalid: user<script>, invalid.email
3. CSRF Protection
// Generate token
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// Add to form
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
// Validate on submission
if ($_POST['csrf_token'] !== $_SESSION['csrf_token']) {
die("CSRF token validation failed");
}
Why this matters:
- Prevents form hijacking
- Stops unauthorized submissions
- Essential for state-changing operations (logins, payments)
4. Secure File Uploads
$allowed_types = ['image/jpeg', 'image/png'];
$max_size = 2 * 1024 * 1024; // 2MB
if ($_FILES['upload']['error'] === UPLOAD_ERR_OK) {
// Verify file type
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($_FILES['upload']['tmp_name']);
if (!in_array($mime, $allowed_types)) {
die("Invalid file type");
}
// Verify file size
if ($_FILES['upload']['size'] > $max_size) {
die("File too large");
}
// Move to secure location
$new_name = uniqid('img_', true) . '.jpg';
move_uploaded_file(
$_FILES['upload']['tmp_name'],
'/var/www/uploads/' . $new_name
);
}
Security Checklist:
- ✔ Verify MIME type (not just extension)
- ✔ Set strict size limits
- ✔ Rename files to prevent directory traversal
- ✔ Store outside web root when possible
5. Password Security
// Hashing passwords
$hashed_password = password_hash($password, PASSWORD_BCRYPT);
// Verification
if (password_verify($input_password, $stored_hash)) {
// Login successful
}
// Rehash if needed
if (password_needs_rehash($stored_hash, PASSWORD_BCRYPT)) {
$new_hash = password_hash($input_password, PASSWORD_BCRYPT);
// Update in database
}
Best Practices:
- Always use password_hash() (never md5/sha1)
- BCRYPT is currently the best algorithm
- Consider peppering (additional secret salt)
Complete Secure Form Example
<?php
session_start();
$errors = [];
// CSRF Protection
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Validate CSRF token
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
die("Security violation detected");
}
// Sanitize inputs
$email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
$username = preg_replace('/[^a-zA-Z0-9_]/', '', $_POST['username']);
// Validate
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors['email'] = "Invalid email address";
}
if (strlen($username) < 3) {
$errors['username'] = "Username too short";
}
// Process if valid
if (empty($errors)) {
// Database insertion with prepared statements
$stmt = $pdo->prepare("INSERT INTO users (email, username) VALUES (?, ?)");
$stmt->execute([$email, $username]);
header("Location: /success.php");
exit;
}
}
?>
<form method="post">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<label>Email:
<input type="email" name="email" required
value="<?= htmlspecialchars($_POST['email'] ?? '') ?>">
<?= $errors['email'] ?? '' ?>
</label>
<label>Username:
<input type="text" name="username" required
pattern="[a-zA-Z0-9_]{3,20}"
value="<?= htmlspecialchars($_POST['username'] ?? '') ?>">
<?= $errors['username'] ?? '' ?>
</label>
<button type="submit">Register</button>
</form>
Advanced Security Measures
1. Rate Limiting
// Using Redis for rate limiting
$redis = new Redis();
$redis->connect('127.0.0.1');
$ip = $_SERVER['REMOTE_ADDR'];
$key = "form_submit:$ip";
if ($redis->get($key) > 5) {
die("Too many submissions. Try again later.");
}
$redis->incr($key);
$redis->expire($key, 3600); // 1 hour limit
2. Content Security Policy (CSP)
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'");
3. Database Security
// Using PDO with prepared statements
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute([$email]);
$user = $stmt->fetch();
Security Checklist for PHP Forms
- Always sanitize and validate all inputs
- Implement CSRF protection
- Use prepared statements for databases
- Secure file uploads with MIME verification
- Hash passwords with bcrypt
- Set secure session configurations
- Add rate limiting to prevent brute force
- Implement CSP headers
- Keep PHP updated to patch vulnerabilities