An Auditor’s Checklist for Securing PHP Backends on Linode
sudo apt update && sudo apt upgrade -y
5. Access Control for Sensitive Files: Ensure that sensitive files (e.g., .env files, private keys, configuration files) have strict file permissions and are not readable by the web server user.
# Example: Set permissions for a .env file chmod 600 .env chown root:root .env # Or a dedicated deployment user, NOT www-data
# Example: Configure fail2ban to monitor Nginx access/error logs for 4xx/5xx errors # In /etc/fail2ban/jail.local [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/*error.log maxretry = 6 findtime = 600 # 10 minutes bantime = 3600 # 1 hour # Ensure you have the corresponding filter in /etc/fail2ban/filter.d/nginx-http-auth.conf # Example filter content: # [Definition] # failregex = ^-.*"(GET|POST).*HTTP/1\.[01]"\s[45]\d{2} # ignoreregex =
4. Regular Updates: Keep the operating system, web server, PHP, and all installed packages up-to-date with security patches.
sudo apt update && sudo apt upgrade -y
5. Access Control for Sensitive Files: Ensure that sensitive files (e.g., .env files, private keys, configuration files) have strict file permissions and are not readable by the web server user.
# Example: Set permissions for a .env file chmod 600 .env chown root:root .env # Or a dedicated deployment user, NOT www-data
sudo systemctl restart sshd
3. Log Monitoring: Centralize and monitor logs from PHP, the web server, and the system. Tools like fail2ban can automatically block IPs exhibiting malicious behavior.
# Example: Configure fail2ban to monitor Nginx access/error logs for 4xx/5xx errors # In /etc/fail2ban/jail.local [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/*error.log maxretry = 6 findtime = 600 # 10 minutes bantime = 3600 # 1 hour # Ensure you have the corresponding filter in /etc/fail2ban/filter.d/nginx-http-auth.conf # Example filter content: # [Definition] # failregex = ^-.*"(GET|POST).*HTTP/1\.[01]"\s[45]\d{2} # ignoreregex =
4. Regular Updates: Keep the operating system, web server, PHP, and all installed packages up-to-date with security patches.
sudo apt update && sudo apt upgrade -y
5. Access Control for Sensitive Files: Ensure that sensitive files (e.g., .env files, private keys, configuration files) have strict file permissions and are not readable by the web server user.
# Example: Set permissions for a .env file chmod 600 .env chown root:root .env # Or a dedicated deployment user, NOT www-data
# In /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes # Port 2222 # Optional: change port
Restart SSH service after changes:
sudo systemctl restart sshd
3. Log Monitoring: Centralize and monitor logs from PHP, the web server, and the system. Tools like fail2ban can automatically block IPs exhibiting malicious behavior.
# Example: Configure fail2ban to monitor Nginx access/error logs for 4xx/5xx errors # In /etc/fail2ban/jail.local [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/*error.log maxretry = 6 findtime = 600 # 10 minutes bantime = 3600 # 1 hour # Ensure you have the corresponding filter in /etc/fail2ban/filter.d/nginx-http-auth.conf # Example filter content: # [Definition] # failregex = ^-.*"(GET|POST).*HTTP/1\.[01]"\s[45]\d{2} # ignoreregex =
4. Regular Updates: Keep the operating system, web server, PHP, and all installed packages up-to-date with security patches.
sudo apt update && sudo apt upgrade -y
5. Access Control for Sensitive Files: Ensure that sensitive files (e.g., .env files, private keys, configuration files) have strict file permissions and are not readable by the web server user.
# Example: Set permissions for a .env file chmod 600 .env chown root:root .env # Or a dedicated deployment user, NOT www-data
sudo ufw status verbose sudo ufw allow ssh sudo ufw allow http sudo ufw allow https sudo ufw enable
2. SSH Security: Disable root login, use key-based authentication, and consider changing the default SSH port (though this is more obscurity than true security). Regularly review SSH logs.
# In /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes # Port 2222 # Optional: change port
Restart SSH service after changes:
sudo systemctl restart sshd
3. Log Monitoring: Centralize and monitor logs from PHP, the web server, and the system. Tools like fail2ban can automatically block IPs exhibiting malicious behavior.
# Example: Configure fail2ban to monitor Nginx access/error logs for 4xx/5xx errors # In /etc/fail2ban/jail.local [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/*error.log maxretry = 6 findtime = 600 # 10 minutes bantime = 3600 # 1 hour # Ensure you have the corresponding filter in /etc/fail2ban/filter.d/nginx-http-auth.conf # Example filter content: # [Definition] # failregex = ^-.*"(GET|POST).*HTTP/1\.[01]"\s[45]\d{2} # ignoreregex =
4. Regular Updates: Keep the operating system, web server, PHP, and all installed packages up-to-date with security patches.
sudo apt update && sudo apt upgrade -y
5. Access Control for Sensitive Files: Ensure that sensitive files (e.g., .env files, private keys, configuration files) have strict file permissions and are not readable by the web server user.
# Example: Set permissions for a .env file chmod 600 .env chown root:root .env # Or a dedicated deployment user, NOT www-data
composer update composer audit
System-Level Security and Monitoring
Auditing extends to the underlying Linode infrastructure and operational practices.
1. Firewall Configuration: Ensure a firewall (e.g., ufw) is active and configured to allow only necessary ports (e.g., 22 for SSH, 80 for HTTP, 443 for HTTPS).
sudo ufw status verbose sudo ufw allow ssh sudo ufw allow http sudo ufw allow https sudo ufw enable
2. SSH Security: Disable root login, use key-based authentication, and consider changing the default SSH port (though this is more obscurity than true security). Regularly review SSH logs.
# In /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes # Port 2222 # Optional: change port
Restart SSH service after changes:
sudo systemctl restart sshd
3. Log Monitoring: Centralize and monitor logs from PHP, the web server, and the system. Tools like fail2ban can automatically block IPs exhibiting malicious behavior.
# Example: Configure fail2ban to monitor Nginx access/error logs for 4xx/5xx errors # In /etc/fail2ban/jail.local [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/*error.log maxretry = 6 findtime = 600 # 10 minutes bantime = 3600 # 1 hour # Ensure you have the corresponding filter in /etc/fail2ban/filter.d/nginx-http-auth.conf # Example filter content: # [Definition] # failregex = ^-.*"(GET|POST).*HTTP/1\.[01]"\s[45]\d{2} # ignoreregex =
4. Regular Updates: Keep the operating system, web server, PHP, and all installed packages up-to-date with security patches.
sudo apt update && sudo apt upgrade -y
5. Access Control for Sensitive Files: Ensure that sensitive files (e.g., .env files, private keys, configuration files) have strict file permissions and are not readable by the web server user.
# Example: Set permissions for a .env file chmod 600 .env chown root:root .env # Or a dedicated deployment user, NOT www-data
// Basic example, real-world requires more checks
$allowed_types = ['image/jpeg', 'image/png'];
$max_size = 5 * 1024 * 1024; // 5MB
if (isset($_FILES['user_file']) && $_FILES['user_file']['error'] === UPLOAD_ERR_OK) {
$file_tmp_path = $_FILES['user_file']['tmp_name'];
$file_name = $_FILES['user_file']['name'];
$file_type = $_FILES['user_file']['type'];
$file_size = $_FILES['user_file']['size'];
// Validate type and size
if (!in_array($file_type, $allowed_types)) {
// Error: Invalid file type
}
if ($file_size > $max_size) {
// Error: File too large
}
// Generate a secure, unique filename
$new_file_name = uniqid('upload_', true) . '.' . pathinfo($file_name, PATHINFO_EXTENSION);
$upload_dir = '/var/www/uploads/'; // Ensure this directory is NOT web-accessible
if (move_uploaded_file($file_tmp_path, $upload_dir . $new_file_name)) {
// File uploaded successfully
} else {
// Error moving file
}
}
5. Dependency Management: Use tools like Composer to manage third-party libraries. Regularly update dependencies and use tools like composer audit to check for known vulnerabilities.
composer update composer audit
System-Level Security and Monitoring
Auditing extends to the underlying Linode infrastructure and operational practices.
1. Firewall Configuration: Ensure a firewall (e.g., ufw) is active and configured to allow only necessary ports (e.g., 22 for SSH, 80 for HTTP, 443 for HTTPS).
sudo ufw status verbose sudo ufw allow ssh sudo ufw allow http sudo ufw allow https sudo ufw enable
2. SSH Security: Disable root login, use key-based authentication, and consider changing the default SSH port (though this is more obscurity than true security). Regularly review SSH logs.
# In /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes # Port 2222 # Optional: change port
Restart SSH service after changes:
sudo systemctl restart sshd
3. Log Monitoring: Centralize and monitor logs from PHP, the web server, and the system. Tools like fail2ban can automatically block IPs exhibiting malicious behavior.
# Example: Configure fail2ban to monitor Nginx access/error logs for 4xx/5xx errors # In /etc/fail2ban/jail.local [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/*error.log maxretry = 6 findtime = 600 # 10 minutes bantime = 3600 # 1 hour # Ensure you have the corresponding filter in /etc/fail2ban/filter.d/nginx-http-auth.conf # Example filter content: # [Definition] # failregex = ^-.*"(GET|POST).*HTTP/1\.[01]"\s[45]\d{2} # ignoreregex =
4. Regular Updates: Keep the operating system, web server, PHP, and all installed packages up-to-date with security patches.
sudo apt update && sudo apt upgrade -y
5. Access Control for Sensitive Files: Ensure that sensitive files (e.g., .env files, private keys, configuration files) have strict file permissions and are not readable by the web server user.
# Example: Set permissions for a .env file chmod 600 .env chown root:root .env # Or a dedicated deployment user, NOT www-data
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Regenerate session ID on login
if (!isset($_SESSION['user_id'])) {
session_regenerate_id(true); // true deletes the old session file
$_SESSION['user_id'] = $user_id;
$_SESSION['logged_in'] = time();
}
// Check for session timeout
$timeout_duration = 1800; // 30 minutes
if (isset($_SESSION['logged_in']) && (time() - $_SESSION['logged_in'] > $timeout_duration)) {
session_unset();
session_destroy();
// Redirect to login page
}
4. File Upload Security: Validate file types, sizes, and scan for malware. Store uploaded files outside the webroot and use non-executable file names.
// Basic example, real-world requires more checks
$allowed_types = ['image/jpeg', 'image/png'];
$max_size = 5 * 1024 * 1024; // 5MB
if (isset($_FILES['user_file']) && $_FILES['user_file']['error'] === UPLOAD_ERR_OK) {
$file_tmp_path = $_FILES['user_file']['tmp_name'];
$file_name = $_FILES['user_file']['name'];
$file_type = $_FILES['user_file']['type'];
$file_size = $_FILES['user_file']['size'];
// Validate type and size
if (!in_array($file_type, $allowed_types)) {
// Error: Invalid file type
}
if ($file_size > $max_size) {
// Error: File too large
}
// Generate a secure, unique filename
$new_file_name = uniqid('upload_', true) . '.' . pathinfo($file_name, PATHINFO_EXTENSION);
$upload_dir = '/var/www/uploads/'; // Ensure this directory is NOT web-accessible
if (move_uploaded_file($file_tmp_path, $upload_dir . $new_file_name)) {
// File uploaded successfully
} else {
// Error moving file
}
}
5. Dependency Management: Use tools like Composer to manage third-party libraries. Regularly update dependencies and use tools like composer audit to check for known vulnerabilities.
composer update composer audit
System-Level Security and Monitoring
Auditing extends to the underlying Linode infrastructure and operational practices.
1. Firewall Configuration: Ensure a firewall (e.g., ufw) is active and configured to allow only necessary ports (e.g., 22 for SSH, 80 for HTTP, 443 for HTTPS).
sudo ufw status verbose sudo ufw allow ssh sudo ufw allow http sudo ufw allow https sudo ufw enable
2. SSH Security: Disable root login, use key-based authentication, and consider changing the default SSH port (though this is more obscurity than true security). Regularly review SSH logs.
# In /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes # Port 2222 # Optional: change port
Restart SSH service after changes:
sudo systemctl restart sshd
3. Log Monitoring: Centralize and monitor logs from PHP, the web server, and the system. Tools like fail2ban can automatically block IPs exhibiting malicious behavior.
# Example: Configure fail2ban to monitor Nginx access/error logs for 4xx/5xx errors # In /etc/fail2ban/jail.local [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/*error.log maxretry = 6 findtime = 600 # 10 minutes bantime = 3600 # 1 hour # Ensure you have the corresponding filter in /etc/fail2ban/filter.d/nginx-http-auth.conf # Example filter content: # [Definition] # failregex = ^-.*"(GET|POST).*HTTP/1\.[01]"\s[45]\d{2} # ignoreregex =
4. Regular Updates: Keep the operating system, web server, PHP, and all installed packages up-to-date with security patches.
sudo apt update && sudo apt upgrade -y
5. Access Control for Sensitive Files: Ensure that sensitive files (e.g., .env files, private keys, configuration files) have strict file permissions and are not readable by the web server user.
# Example: Set permissions for a .env file chmod 600 .env chown root:root .env # Or a dedicated deployment user, NOT www-data
// Example: Displaying user-generated content echo htmlspecialchars($user_comment, ENT_QUOTES, 'UTF-8');
3. Secure Session Management: Use strong, random session IDs and regenerate them upon login or privilege change. Store session data securely.
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Regenerate session ID on login
if (!isset($_SESSION['user_id'])) {
session_regenerate_id(true); // true deletes the old session file
$_SESSION['user_id'] = $user_id;
$_SESSION['logged_in'] = time();
}
// Check for session timeout
$timeout_duration = 1800; // 30 minutes
if (isset($_SESSION['logged_in']) && (time() - $_SESSION['logged_in'] > $timeout_duration)) {
session_unset();
session_destroy();
// Redirect to login page
}
4. File Upload Security: Validate file types, sizes, and scan for malware. Store uploaded files outside the webroot and use non-executable file names.
// Basic example, real-world requires more checks
$allowed_types = ['image/jpeg', 'image/png'];
$max_size = 5 * 1024 * 1024; // 5MB
if (isset($_FILES['user_file']) && $_FILES['user_file']['error'] === UPLOAD_ERR_OK) {
$file_tmp_path = $_FILES['user_file']['tmp_name'];
$file_name = $_FILES['user_file']['name'];
$file_type = $_FILES['user_file']['type'];
$file_size = $_FILES['user_file']['size'];
// Validate type and size
if (!in_array($file_type, $allowed_types)) {
// Error: Invalid file type
}
if ($file_size > $max_size) {
// Error: File too large
}
// Generate a secure, unique filename
$new_file_name = uniqid('upload_', true) . '.' . pathinfo($file_name, PATHINFO_EXTENSION);
$upload_dir = '/var/www/uploads/'; // Ensure this directory is NOT web-accessible
if (move_uploaded_file($file_tmp_path, $upload_dir . $new_file_name)) {
// File uploaded successfully
} else {
// Error moving file
}
}
5. Dependency Management: Use tools like Composer to manage third-party libraries. Regularly update dependencies and use tools like composer audit to check for known vulnerabilities.
composer update composer audit
System-Level Security and Monitoring
Auditing extends to the underlying Linode infrastructure and operational practices.
1. Firewall Configuration: Ensure a firewall (e.g., ufw) is active and configured to allow only necessary ports (e.g., 22 for SSH, 80 for HTTP, 443 for HTTPS).
sudo ufw status verbose sudo ufw allow ssh sudo ufw allow http sudo ufw allow https sudo ufw enable
2. SSH Security: Disable root login, use key-based authentication, and consider changing the default SSH port (though this is more obscurity than true security). Regularly review SSH logs.
# In /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes # Port 2222 # Optional: change port
Restart SSH service after changes:
sudo systemctl restart sshd
3. Log Monitoring: Centralize and monitor logs from PHP, the web server, and the system. Tools like fail2ban can automatically block IPs exhibiting malicious behavior.
# Example: Configure fail2ban to monitor Nginx access/error logs for 4xx/5xx errors # In /etc/fail2ban/jail.local [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/*error.log maxretry = 6 findtime = 600 # 10 minutes bantime = 3600 # 1 hour # Ensure you have the corresponding filter in /etc/fail2ban/filter.d/nginx-http-auth.conf # Example filter content: # [Definition] # failregex = ^-.*"(GET|POST).*HTTP/1\.[01]"\s[45]\d{2} # ignoreregex =
4. Regular Updates: Keep the operating system, web server, PHP, and all installed packages up-to-date with security patches.
sudo apt update && sudo apt upgrade -y
5. Access Control for Sensitive Files: Ensure that sensitive files (e.g., .env files, private keys, configuration files) have strict file permissions and are not readable by the web server user.
# Example: Set permissions for a .env file chmod 600 .env chown root:root .env # Or a dedicated deployment user, NOT www-data
// Example: Basic validation for a username
function sanitize_username($input) {
// Remove characters that are not alphanumeric, underscore, or hyphen
$sanitized = preg_replace('/[^a-zA-Z0-9_-]/', '', $input);
// Trim whitespace
return trim($sanitized);
}
$username = sanitize_username($_POST['username']);
// Example: Using prepared statements for SQL queries (essential!)
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->bindParam(':username', $username);
$stmt->execute();
$user = $stmt->fetch();
2. Output Encoding: When displaying user-provided data in HTML, ensure it is properly encoded to prevent XSS.
// Example: Displaying user-generated content echo htmlspecialchars($user_comment, ENT_QUOTES, 'UTF-8');
3. Secure Session Management: Use strong, random session IDs and regenerate them upon login or privilege change. Store session data securely.
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Regenerate session ID on login
if (!isset($_SESSION['user_id'])) {
session_regenerate_id(true); // true deletes the old session file
$_SESSION['user_id'] = $user_id;
$_SESSION['logged_in'] = time();
}
// Check for session timeout
$timeout_duration = 1800; // 30 minutes
if (isset($_SESSION['logged_in']) && (time() - $_SESSION['logged_in'] > $timeout_duration)) {
session_unset();
session_destroy();
// Redirect to login page
}
4. File Upload Security: Validate file types, sizes, and scan for malware. Store uploaded files outside the webroot and use non-executable file names.
// Basic example, real-world requires more checks
$allowed_types = ['image/jpeg', 'image/png'];
$max_size = 5 * 1024 * 1024; // 5MB
if (isset($_FILES['user_file']) && $_FILES['user_file']['error'] === UPLOAD_ERR_OK) {
$file_tmp_path = $_FILES['user_file']['tmp_name'];
$file_name = $_FILES['user_file']['name'];
$file_type = $_FILES['user_file']['type'];
$file_size = $_FILES['user_file']['size'];
// Validate type and size
if (!in_array($file_type, $allowed_types)) {
// Error: Invalid file type
}
if ($file_size > $max_size) {
// Error: File too large
}
// Generate a secure, unique filename
$new_file_name = uniqid('upload_', true) . '.' . pathinfo($file_name, PATHINFO_EXTENSION);
$upload_dir = '/var/www/uploads/'; // Ensure this directory is NOT web-accessible
if (move_uploaded_file($file_tmp_path, $upload_dir . $new_file_name)) {
// File uploaded successfully
} else {
// Error moving file
}
}
5. Dependency Management: Use tools like Composer to manage third-party libraries. Regularly update dependencies and use tools like composer audit to check for known vulnerabilities.
composer update composer audit
System-Level Security and Monitoring
Auditing extends to the underlying Linode infrastructure and operational practices.
1. Firewall Configuration: Ensure a firewall (e.g., ufw) is active and configured to allow only necessary ports (e.g., 22 for SSH, 80 for HTTP, 443 for HTTPS).
sudo ufw status verbose sudo ufw allow ssh sudo ufw allow http sudo ufw allow https sudo ufw enable
2. SSH Security: Disable root login, use key-based authentication, and consider changing the default SSH port (though this is more obscurity than true security). Regularly review SSH logs.
# In /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes # Port 2222 # Optional: change port
Restart SSH service after changes:
sudo systemctl restart sshd
3. Log Monitoring: Centralize and monitor logs from PHP, the web server, and the system. Tools like fail2ban can automatically block IPs exhibiting malicious behavior.
# Example: Configure fail2ban to monitor Nginx access/error logs for 4xx/5xx errors # In /etc/fail2ban/jail.local [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/*error.log maxretry = 6 findtime = 600 # 10 minutes bantime = 3600 # 1 hour # Ensure you have the corresponding filter in /etc/fail2ban/filter.d/nginx-http-auth.conf # Example filter content: # [Definition] # failregex = ^-.*"(GET|POST).*HTTP/1\.[01]"\s[45]\d{2} # ignoreregex =
4. Regular Updates: Keep the operating system, web server, PHP, and all installed packages up-to-date with security patches.
sudo apt update && sudo apt upgrade -y
5. Access Control for Sensitive Files: Ensure that sensitive files (e.g., .env files, private keys, configuration files) have strict file permissions and are not readable by the web server user.
# Example: Set permissions for a .env file chmod 600 .env chown root:root .env # Or a dedicated deployment user, NOT www-data
sudo systemctl restart mysql # Or mariadb
3. Secure Connection (SSL/TLS): Ensure PHP applications connect to the database using SSL/TLS encryption. This requires configuring the database server for SSL and updating the PHP database connection parameters.
4. Regular Backups and Auditing: Implement a robust backup strategy and regularly audit database logs for suspicious activity.
Application-Level Security Checks
Beyond server and PHP configuration, the application code itself must be secure.
1. Input Validation and Sanitization: All user-supplied input (GET, POST, COOKIE, headers, file uploads) must be rigorously validated and sanitized to prevent injection attacks (SQLi, XSS, command injection).
// Example: Basic validation for a username
function sanitize_username($input) {
// Remove characters that are not alphanumeric, underscore, or hyphen
$sanitized = preg_replace('/[^a-zA-Z0-9_-]/', '', $input);
// Trim whitespace
return trim($sanitized);
}
$username = sanitize_username($_POST['username']);
// Example: Using prepared statements for SQL queries (essential!)
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->bindParam(':username', $username);
$stmt->execute();
$user = $stmt->fetch();
2. Output Encoding: When displaying user-provided data in HTML, ensure it is properly encoded to prevent XSS.
// Example: Displaying user-generated content echo htmlspecialchars($user_comment, ENT_QUOTES, 'UTF-8');
3. Secure Session Management: Use strong, random session IDs and regenerate them upon login or privilege change. Store session data securely.
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Regenerate session ID on login
if (!isset($_SESSION['user_id'])) {
session_regenerate_id(true); // true deletes the old session file
$_SESSION['user_id'] = $user_id;
$_SESSION['logged_in'] = time();
}
// Check for session timeout
$timeout_duration = 1800; // 30 minutes
if (isset($_SESSION['logged_in']) && (time() - $_SESSION['logged_in'] > $timeout_duration)) {
session_unset();
session_destroy();
// Redirect to login page
}
4. File Upload Security: Validate file types, sizes, and scan for malware. Store uploaded files outside the webroot and use non-executable file names.
// Basic example, real-world requires more checks
$allowed_types = ['image/jpeg', 'image/png'];
$max_size = 5 * 1024 * 1024; // 5MB
if (isset($_FILES['user_file']) && $_FILES['user_file']['error'] === UPLOAD_ERR_OK) {
$file_tmp_path = $_FILES['user_file']['tmp_name'];
$file_name = $_FILES['user_file']['name'];
$file_type = $_FILES['user_file']['type'];
$file_size = $_FILES['user_file']['size'];
// Validate type and size
if (!in_array($file_type, $allowed_types)) {
// Error: Invalid file type
}
if ($file_size > $max_size) {
// Error: File too large
}
// Generate a secure, unique filename
$new_file_name = uniqid('upload_', true) . '.' . pathinfo($file_name, PATHINFO_EXTENSION);
$upload_dir = '/var/www/uploads/'; // Ensure this directory is NOT web-accessible
if (move_uploaded_file($file_tmp_path, $upload_dir . $new_file_name)) {
// File uploaded successfully
} else {
// Error moving file
}
}
5. Dependency Management: Use tools like Composer to manage third-party libraries. Regularly update dependencies and use tools like composer audit to check for known vulnerabilities.
composer update composer audit
System-Level Security and Monitoring
Auditing extends to the underlying Linode infrastructure and operational practices.
1. Firewall Configuration: Ensure a firewall (e.g., ufw) is active and configured to allow only necessary ports (e.g., 22 for SSH, 80 for HTTP, 443 for HTTPS).
sudo ufw status verbose sudo ufw allow ssh sudo ufw allow http sudo ufw allow https sudo ufw enable
2. SSH Security: Disable root login, use key-based authentication, and consider changing the default SSH port (though this is more obscurity than true security). Regularly review SSH logs.
# In /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes # Port 2222 # Optional: change port
Restart SSH service after changes:
sudo systemctl restart sshd
3. Log Monitoring: Centralize and monitor logs from PHP, the web server, and the system. Tools like fail2ban can automatically block IPs exhibiting malicious behavior.
# Example: Configure fail2ban to monitor Nginx access/error logs for 4xx/5xx errors # In /etc/fail2ban/jail.local [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/*error.log maxretry = 6 findtime = 600 # 10 minutes bantime = 3600 # 1 hour # Ensure you have the corresponding filter in /etc/fail2ban/filter.d/nginx-http-auth.conf # Example filter content: # [Definition] # failregex = ^-.*"(GET|POST).*HTTP/1\.[01]"\s[45]\d{2} # ignoreregex =
4. Regular Updates: Keep the operating system, web server, PHP, and all installed packages up-to-date with security patches.
sudo apt update && sudo apt upgrade -y
5. Access Control for Sensitive Files: Ensure that sensitive files (e.g., .env files, private keys, configuration files) have strict file permissions and are not readable by the web server user.
# Example: Set permissions for a .env file chmod 600 .env chown root:root .env # Or a dedicated deployment user, NOT www-data
# In MySQL/MariaDB configuration file (e.g., /etc/mysql/my.cnf or /etc/mysql/mariadb.conf.d/50-server.cnf) [mysqld] bind-address = 127.0.0.1 # Or the specific IP of the web server if remote
After changing the configuration, restart the MySQL/MariaDB service:
sudo systemctl restart mysql # Or mariadb
3. Secure Connection (SSL/TLS): Ensure PHP applications connect to the database using SSL/TLS encryption. This requires configuring the database server for SSL and updating the PHP database connection parameters.
4. Regular Backups and Auditing: Implement a robust backup strategy and regularly audit database logs for suspicious activity.
Application-Level Security Checks
Beyond server and PHP configuration, the application code itself must be secure.
1. Input Validation and Sanitization: All user-supplied input (GET, POST, COOKIE, headers, file uploads) must be rigorously validated and sanitized to prevent injection attacks (SQLi, XSS, command injection).
// Example: Basic validation for a username
function sanitize_username($input) {
// Remove characters that are not alphanumeric, underscore, or hyphen
$sanitized = preg_replace('/[^a-zA-Z0-9_-]/', '', $input);
// Trim whitespace
return trim($sanitized);
}
$username = sanitize_username($_POST['username']);
// Example: Using prepared statements for SQL queries (essential!)
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->bindParam(':username', $username);
$stmt->execute();
$user = $stmt->fetch();
2. Output Encoding: When displaying user-provided data in HTML, ensure it is properly encoded to prevent XSS.
// Example: Displaying user-generated content echo htmlspecialchars($user_comment, ENT_QUOTES, 'UTF-8');
3. Secure Session Management: Use strong, random session IDs and regenerate them upon login or privilege change. Store session data securely.
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Regenerate session ID on login
if (!isset($_SESSION['user_id'])) {
session_regenerate_id(true); // true deletes the old session file
$_SESSION['user_id'] = $user_id;
$_SESSION['logged_in'] = time();
}
// Check for session timeout
$timeout_duration = 1800; // 30 minutes
if (isset($_SESSION['logged_in']) && (time() - $_SESSION['logged_in'] > $timeout_duration)) {
session_unset();
session_destroy();
// Redirect to login page
}
4. File Upload Security: Validate file types, sizes, and scan for malware. Store uploaded files outside the webroot and use non-executable file names.
// Basic example, real-world requires more checks
$allowed_types = ['image/jpeg', 'image/png'];
$max_size = 5 * 1024 * 1024; // 5MB
if (isset($_FILES['user_file']) && $_FILES['user_file']['error'] === UPLOAD_ERR_OK) {
$file_tmp_path = $_FILES['user_file']['tmp_name'];
$file_name = $_FILES['user_file']['name'];
$file_type = $_FILES['user_file']['type'];
$file_size = $_FILES['user_file']['size'];
// Validate type and size
if (!in_array($file_type, $allowed_types)) {
// Error: Invalid file type
}
if ($file_size > $max_size) {
// Error: File too large
}
// Generate a secure, unique filename
$new_file_name = uniqid('upload_', true) . '.' . pathinfo($file_name, PATHINFO_EXTENSION);
$upload_dir = '/var/www/uploads/'; // Ensure this directory is NOT web-accessible
if (move_uploaded_file($file_tmp_path, $upload_dir . $new_file_name)) {
// File uploaded successfully
} else {
// Error moving file
}
}
5. Dependency Management: Use tools like Composer to manage third-party libraries. Regularly update dependencies and use tools like composer audit to check for known vulnerabilities.
composer update composer audit
System-Level Security and Monitoring
Auditing extends to the underlying Linode infrastructure and operational practices.
1. Firewall Configuration: Ensure a firewall (e.g., ufw) is active and configured to allow only necessary ports (e.g., 22 for SSH, 80 for HTTP, 443 for HTTPS).
sudo ufw status verbose sudo ufw allow ssh sudo ufw allow http sudo ufw allow https sudo ufw enable
2. SSH Security: Disable root login, use key-based authentication, and consider changing the default SSH port (though this is more obscurity than true security). Regularly review SSH logs.
# In /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes # Port 2222 # Optional: change port
Restart SSH service after changes:
sudo systemctl restart sshd
3. Log Monitoring: Centralize and monitor logs from PHP, the web server, and the system. Tools like fail2ban can automatically block IPs exhibiting malicious behavior.
# Example: Configure fail2ban to monitor Nginx access/error logs for 4xx/5xx errors # In /etc/fail2ban/jail.local [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/*error.log maxretry = 6 findtime = 600 # 10 minutes bantime = 3600 # 1 hour # Ensure you have the corresponding filter in /etc/fail2ban/filter.d/nginx-http-auth.conf # Example filter content: # [Definition] # failregex = ^-.*"(GET|POST).*HTTP/1\.[01]"\s[45]\d{2} # ignoreregex =
4. Regular Updates: Keep the operating system, web server, PHP, and all installed packages up-to-date with security patches.
sudo apt update && sudo apt upgrade -y
5. Access Control for Sensitive Files: Ensure that sensitive files (e.g., .env files, private keys, configuration files) have strict file permissions and are not readable by the web server user.
# Example: Set permissions for a .env file chmod 600 .env chown root:root .env # Or a dedicated deployment user, NOT www-data
-- Connect as root or a privileged user mysql -u root -p -- Create a new user for the application CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'secure_password_here'; -- Grant only necessary privileges on the application's database GRANT SELECT, INSERT, UPDATE, DELETE ON app_database.* TO 'app_user'@'localhost'; -- If the application needs to create tables or alter schema (less common for production apps) -- GRANT CREATE, ALTER, DROP ON app_database.* TO 'app_user'@'localhost'; -- Apply the changes FLUSH PRIVILEGES; -- Exit MySQL client EXIT;
2. Restrict Network Access: Configure the database server to listen only on localhost if the web server and database server are on the same machine. If they are on different machines, restrict access to specific IP addresses.
# In MySQL/MariaDB configuration file (e.g., /etc/mysql/my.cnf or /etc/mysql/mariadb.conf.d/50-server.cnf) [mysqld] bind-address = 127.0.0.1 # Or the specific IP of the web server if remote
After changing the configuration, restart the MySQL/MariaDB service:
sudo systemctl restart mysql # Or mariadb
3. Secure Connection (SSL/TLS): Ensure PHP applications connect to the database using SSL/TLS encryption. This requires configuring the database server for SSL and updating the PHP database connection parameters.
4. Regular Backups and Auditing: Implement a robust backup strategy and regularly audit database logs for suspicious activity.
Application-Level Security Checks
Beyond server and PHP configuration, the application code itself must be secure.
1. Input Validation and Sanitization: All user-supplied input (GET, POST, COOKIE, headers, file uploads) must be rigorously validated and sanitized to prevent injection attacks (SQLi, XSS, command injection).
// Example: Basic validation for a username
function sanitize_username($input) {
// Remove characters that are not alphanumeric, underscore, or hyphen
$sanitized = preg_replace('/[^a-zA-Z0-9_-]/', '', $input);
// Trim whitespace
return trim($sanitized);
}
$username = sanitize_username($_POST['username']);
// Example: Using prepared statements for SQL queries (essential!)
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->bindParam(':username', $username);
$stmt->execute();
$user = $stmt->fetch();
2. Output Encoding: When displaying user-provided data in HTML, ensure it is properly encoded to prevent XSS.
// Example: Displaying user-generated content echo htmlspecialchars($user_comment, ENT_QUOTES, 'UTF-8');
3. Secure Session Management: Use strong, random session IDs and regenerate them upon login or privilege change. Store session data securely.
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Regenerate session ID on login
if (!isset($_SESSION['user_id'])) {
session_regenerate_id(true); // true deletes the old session file
$_SESSION['user_id'] = $user_id;
$_SESSION['logged_in'] = time();
}
// Check for session timeout
$timeout_duration = 1800; // 30 minutes
if (isset($_SESSION['logged_in']) && (time() - $_SESSION['logged_in'] > $timeout_duration)) {
session_unset();
session_destroy();
// Redirect to login page
}
4. File Upload Security: Validate file types, sizes, and scan for malware. Store uploaded files outside the webroot and use non-executable file names.
// Basic example, real-world requires more checks
$allowed_types = ['image/jpeg', 'image/png'];
$max_size = 5 * 1024 * 1024; // 5MB
if (isset($_FILES['user_file']) && $_FILES['user_file']['error'] === UPLOAD_ERR_OK) {
$file_tmp_path = $_FILES['user_file']['tmp_name'];
$file_name = $_FILES['user_file']['name'];
$file_type = $_FILES['user_file']['type'];
$file_size = $_FILES['user_file']['size'];
// Validate type and size
if (!in_array($file_type, $allowed_types)) {
// Error: Invalid file type
}
if ($file_size > $max_size) {
// Error: File too large
}
// Generate a secure, unique filename
$new_file_name = uniqid('upload_', true) . '.' . pathinfo($file_name, PATHINFO_EXTENSION);
$upload_dir = '/var/www/uploads/'; // Ensure this directory is NOT web-accessible
if (move_uploaded_file($file_tmp_path, $upload_dir . $new_file_name)) {
// File uploaded successfully
} else {
// Error moving file
}
}
5. Dependency Management: Use tools like Composer to manage third-party libraries. Regularly update dependencies and use tools like composer audit to check for known vulnerabilities.
composer update composer audit
System-Level Security and Monitoring
Auditing extends to the underlying Linode infrastructure and operational practices.
1. Firewall Configuration: Ensure a firewall (e.g., ufw) is active and configured to allow only necessary ports (e.g., 22 for SSH, 80 for HTTP, 443 for HTTPS).
sudo ufw status verbose sudo ufw allow ssh sudo ufw allow http sudo ufw allow https sudo ufw enable
2. SSH Security: Disable root login, use key-based authentication, and consider changing the default SSH port (though this is more obscurity than true security). Regularly review SSH logs.
# In /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes # Port 2222 # Optional: change port
Restart SSH service after changes:
sudo systemctl restart sshd
3. Log Monitoring: Centralize and monitor logs from PHP, the web server, and the system. Tools like fail2ban can automatically block IPs exhibiting malicious behavior.
# Example: Configure fail2ban to monitor Nginx access/error logs for 4xx/5xx errors # In /etc/fail2ban/jail.local [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/*error.log maxretry = 6 findtime = 600 # 10 minutes bantime = 3600 # 1 hour # Ensure you have the corresponding filter in /etc/fail2ban/filter.d/nginx-http-auth.conf # Example filter content: # [Definition] # failregex = ^-.*"(GET|POST).*HTTP/1\.[01]"\s[45]\d{2} # ignoreregex =
4. Regular Updates: Keep the operating system, web server, PHP, and all installed packages up-to-date with security patches.
sudo apt update && sudo apt upgrade -y
5. Access Control for Sensitive Files: Ensure that sensitive files (e.g., .env files, private keys, configuration files) have strict file permissions and are not readable by the web server user.
# Example: Set permissions for a .env file chmod 600 .env chown root:root .env # Or a dedicated deployment user, NOT www-data
sudo nginx -t sudo systemctl reload nginx
Database Security (MySQL/MariaDB Example)
Securing the database backend is paramount. This involves user privileges, network access, and data encryption.
1. Principle of Least Privilege for Database Users: The PHP application should connect to the database using a dedicated user account with only the necessary privileges. Avoid using the root user.
-- Connect as root or a privileged user mysql -u root -p -- Create a new user for the application CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'secure_password_here'; -- Grant only necessary privileges on the application's database GRANT SELECT, INSERT, UPDATE, DELETE ON app_database.* TO 'app_user'@'localhost'; -- If the application needs to create tables or alter schema (less common for production apps) -- GRANT CREATE, ALTER, DROP ON app_database.* TO 'app_user'@'localhost'; -- Apply the changes FLUSH PRIVILEGES; -- Exit MySQL client EXIT;
2. Restrict Network Access: Configure the database server to listen only on localhost if the web server and database server are on the same machine. If they are on different machines, restrict access to specific IP addresses.
# In MySQL/MariaDB configuration file (e.g., /etc/mysql/my.cnf or /etc/mysql/mariadb.conf.d/50-server.cnf) [mysqld] bind-address = 127.0.0.1 # Or the specific IP of the web server if remote
After changing the configuration, restart the MySQL/MariaDB service:
sudo systemctl restart mysql # Or mariadb
3. Secure Connection (SSL/TLS): Ensure PHP applications connect to the database using SSL/TLS encryption. This requires configuring the database server for SSL and updating the PHP database connection parameters.
4. Regular Backups and Auditing: Implement a robust backup strategy and regularly audit database logs for suspicious activity.
Application-Level Security Checks
Beyond server and PHP configuration, the application code itself must be secure.
1. Input Validation and Sanitization: All user-supplied input (GET, POST, COOKIE, headers, file uploads) must be rigorously validated and sanitized to prevent injection attacks (SQLi, XSS, command injection).
// Example: Basic validation for a username
function sanitize_username($input) {
// Remove characters that are not alphanumeric, underscore, or hyphen
$sanitized = preg_replace('/[^a-zA-Z0-9_-]/', '', $input);
// Trim whitespace
return trim($sanitized);
}
$username = sanitize_username($_POST['username']);
// Example: Using prepared statements for SQL queries (essential!)
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->bindParam(':username', $username);
$stmt->execute();
$user = $stmt->fetch();
2. Output Encoding: When displaying user-provided data in HTML, ensure it is properly encoded to prevent XSS.
// Example: Displaying user-generated content echo htmlspecialchars($user_comment, ENT_QUOTES, 'UTF-8');
3. Secure Session Management: Use strong, random session IDs and regenerate them upon login or privilege change. Store session data securely.
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Regenerate session ID on login
if (!isset($_SESSION['user_id'])) {
session_regenerate_id(true); // true deletes the old session file
$_SESSION['user_id'] = $user_id;
$_SESSION['logged_in'] = time();
}
// Check for session timeout
$timeout_duration = 1800; // 30 minutes
if (isset($_SESSION['logged_in']) && (time() - $_SESSION['logged_in'] > $timeout_duration)) {
session_unset();
session_destroy();
// Redirect to login page
}
4. File Upload Security: Validate file types, sizes, and scan for malware. Store uploaded files outside the webroot and use non-executable file names.
// Basic example, real-world requires more checks
$allowed_types = ['image/jpeg', 'image/png'];
$max_size = 5 * 1024 * 1024; // 5MB
if (isset($_FILES['user_file']) && $_FILES['user_file']['error'] === UPLOAD_ERR_OK) {
$file_tmp_path = $_FILES['user_file']['tmp_name'];
$file_name = $_FILES['user_file']['name'];
$file_type = $_FILES['user_file']['type'];
$file_size = $_FILES['user_file']['size'];
// Validate type and size
if (!in_array($file_type, $allowed_types)) {
// Error: Invalid file type
}
if ($file_size > $max_size) {
// Error: File too large
}
// Generate a secure, unique filename
$new_file_name = uniqid('upload_', true) . '.' . pathinfo($file_name, PATHINFO_EXTENSION);
$upload_dir = '/var/www/uploads/'; // Ensure this directory is NOT web-accessible
if (move_uploaded_file($file_tmp_path, $upload_dir . $new_file_name)) {
// File uploaded successfully
} else {
// Error moving file
}
}
5. Dependency Management: Use tools like Composer to manage third-party libraries. Regularly update dependencies and use tools like composer audit to check for known vulnerabilities.
composer update composer audit
System-Level Security and Monitoring
Auditing extends to the underlying Linode infrastructure and operational practices.
1. Firewall Configuration: Ensure a firewall (e.g., ufw) is active and configured to allow only necessary ports (e.g., 22 for SSH, 80 for HTTP, 443 for HTTPS).
sudo ufw status verbose sudo ufw allow ssh sudo ufw allow http sudo ufw allow https sudo ufw enable
2. SSH Security: Disable root login, use key-based authentication, and consider changing the default SSH port (though this is more obscurity than true security). Regularly review SSH logs.
# In /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes # Port 2222 # Optional: change port
Restart SSH service after changes:
sudo systemctl restart sshd
3. Log Monitoring: Centralize and monitor logs from PHP, the web server, and the system. Tools like fail2ban can automatically block IPs exhibiting malicious behavior.
# Example: Configure fail2ban to monitor Nginx access/error logs for 4xx/5xx errors # In /etc/fail2ban/jail.local [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/*error.log maxretry = 6 findtime = 600 # 10 minutes bantime = 3600 # 1 hour # Ensure you have the corresponding filter in /etc/fail2ban/filter.d/nginx-http-auth.conf # Example filter content: # [Definition] # failregex = ^-.*"(GET|POST).*HTTP/1\.[01]"\s[45]\d{2} # ignoreregex =
4. Regular Updates: Keep the operating system, web server, PHP, and all installed packages up-to-date with security patches.
sudo apt update && sudo apt upgrade -y
5. Access Control for Sensitive Files: Ensure that sensitive files (e.g., .env files, private keys, configuration files) have strict file permissions and are not readable by the web server user.
# Example: Set permissions for a .env file chmod 600 .env chown root:root .env # Or a dedicated deployment user, NOT www-data
# Example: Limit requests to the login page
location /login {
limit_req zone=myloginburst burst=5 nodelay;
# ... other login-related configurations
}
# Define the rate limiting zone
# http block
http {
limit_req_zone $binary_remote_addr zone=myloginburst:10m rate=5r/min;
# ... other http configurations
}
After modifying Nginx configuration files (typically in /etc/nginx/sites-available/ or /etc/nginx/nginx.conf), always test the configuration and reload Nginx:
sudo nginx -t sudo systemctl reload nginx
Database Security (MySQL/MariaDB Example)
Securing the database backend is paramount. This involves user privileges, network access, and data encryption.
1. Principle of Least Privilege for Database Users: The PHP application should connect to the database using a dedicated user account with only the necessary privileges. Avoid using the root user.
-- Connect as root or a privileged user mysql -u root -p -- Create a new user for the application CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'secure_password_here'; -- Grant only necessary privileges on the application's database GRANT SELECT, INSERT, UPDATE, DELETE ON app_database.* TO 'app_user'@'localhost'; -- If the application needs to create tables or alter schema (less common for production apps) -- GRANT CREATE, ALTER, DROP ON app_database.* TO 'app_user'@'localhost'; -- Apply the changes FLUSH PRIVILEGES; -- Exit MySQL client EXIT;
2. Restrict Network Access: Configure the database server to listen only on localhost if the web server and database server are on the same machine. If they are on different machines, restrict access to specific IP addresses.
# In MySQL/MariaDB configuration file (e.g., /etc/mysql/my.cnf or /etc/mysql/mariadb.conf.d/50-server.cnf) [mysqld] bind-address = 127.0.0.1 # Or the specific IP of the web server if remote
After changing the configuration, restart the MySQL/MariaDB service:
sudo systemctl restart mysql # Or mariadb
3. Secure Connection (SSL/TLS): Ensure PHP applications connect to the database using SSL/TLS encryption. This requires configuring the database server for SSL and updating the PHP database connection parameters.
4. Regular Backups and Auditing: Implement a robust backup strategy and regularly audit database logs for suspicious activity.
Application-Level Security Checks
Beyond server and PHP configuration, the application code itself must be secure.
1. Input Validation and Sanitization: All user-supplied input (GET, POST, COOKIE, headers, file uploads) must be rigorously validated and sanitized to prevent injection attacks (SQLi, XSS, command injection).
// Example: Basic validation for a username
function sanitize_username($input) {
// Remove characters that are not alphanumeric, underscore, or hyphen
$sanitized = preg_replace('/[^a-zA-Z0-9_-]/', '', $input);
// Trim whitespace
return trim($sanitized);
}
$username = sanitize_username($_POST['username']);
// Example: Using prepared statements for SQL queries (essential!)
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->bindParam(':username', $username);
$stmt->execute();
$user = $stmt->fetch();
2. Output Encoding: When displaying user-provided data in HTML, ensure it is properly encoded to prevent XSS.
// Example: Displaying user-generated content echo htmlspecialchars($user_comment, ENT_QUOTES, 'UTF-8');
3. Secure Session Management: Use strong, random session IDs and regenerate them upon login or privilege change. Store session data securely.
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Regenerate session ID on login
if (!isset($_SESSION['user_id'])) {
session_regenerate_id(true); // true deletes the old session file
$_SESSION['user_id'] = $user_id;
$_SESSION['logged_in'] = time();
}
// Check for session timeout
$timeout_duration = 1800; // 30 minutes
if (isset($_SESSION['logged_in']) && (time() - $_SESSION['logged_in'] > $timeout_duration)) {
session_unset();
session_destroy();
// Redirect to login page
}
4. File Upload Security: Validate file types, sizes, and scan for malware. Store uploaded files outside the webroot and use non-executable file names.
// Basic example, real-world requires more checks
$allowed_types = ['image/jpeg', 'image/png'];
$max_size = 5 * 1024 * 1024; // 5MB
if (isset($_FILES['user_file']) && $_FILES['user_file']['error'] === UPLOAD_ERR_OK) {
$file_tmp_path = $_FILES['user_file']['tmp_name'];
$file_name = $_FILES['user_file']['name'];
$file_type = $_FILES['user_file']['type'];
$file_size = $_FILES['user_file']['size'];
// Validate type and size
if (!in_array($file_type, $allowed_types)) {
// Error: Invalid file type
}
if ($file_size > $max_size) {
// Error: File too large
}
// Generate a secure, unique filename
$new_file_name = uniqid('upload_', true) . '.' . pathinfo($file_name, PATHINFO_EXTENSION);
$upload_dir = '/var/www/uploads/'; // Ensure this directory is NOT web-accessible
if (move_uploaded_file($file_tmp_path, $upload_dir . $new_file_name)) {
// File uploaded successfully
} else {
// Error moving file
}
}
5. Dependency Management: Use tools like Composer to manage third-party libraries. Regularly update dependencies and use tools like composer audit to check for known vulnerabilities.
composer update composer audit
System-Level Security and Monitoring
Auditing extends to the underlying Linode infrastructure and operational practices.
1. Firewall Configuration: Ensure a firewall (e.g., ufw) is active and configured to allow only necessary ports (e.g., 22 for SSH, 80 for HTTP, 443 for HTTPS).
sudo ufw status verbose sudo ufw allow ssh sudo ufw allow http sudo ufw allow https sudo ufw enable
2. SSH Security: Disable root login, use key-based authentication, and consider changing the default SSH port (though this is more obscurity than true security). Regularly review SSH logs.
# In /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes # Port 2222 # Optional: change port
Restart SSH service after changes:
sudo systemctl restart sshd
3. Log Monitoring: Centralize and monitor logs from PHP, the web server, and the system. Tools like fail2ban can automatically block IPs exhibiting malicious behavior.
# Example: Configure fail2ban to monitor Nginx access/error logs for 4xx/5xx errors # In /etc/fail2ban/jail.local [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/*error.log maxretry = 6 findtime = 600 # 10 minutes bantime = 3600 # 1 hour # Ensure you have the corresponding filter in /etc/fail2ban/filter.d/nginx-http-auth.conf # Example filter content: # [Definition] # failregex = ^-.*"(GET|POST).*HTTP/1\.[01]"\s[45]\d{2} # ignoreregex =
4. Regular Updates: Keep the operating system, web server, PHP, and all installed packages up-to-date with security patches.
sudo apt update && sudo apt upgrade -y
5. Access Control for Sensitive Files: Ensure that sensitive files (e.g., .env files, private keys, configuration files) have strict file permissions and are not readable by the web server user.
# Example: Set permissions for a .env file chmod 600 .env chown root:root .env # Or a dedicated deployment user, NOT www-data
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; # Example, adjust as needed # Content Security Policy (CSP) is highly recommended but complex. Start simple and iterate. # add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none';" always;
4. Rate Limiting and Access Control: Protect against brute-force attacks and excessive requests.
# Example: Limit requests to the login page
location /login {
limit_req zone=myloginburst burst=5 nodelay;
# ... other login-related configurations
}
# Define the rate limiting zone
# http block
http {
limit_req_zone $binary_remote_addr zone=myloginburst:10m rate=5r/min;
# ... other http configurations
}
After modifying Nginx configuration files (typically in /etc/nginx/sites-available/ or /etc/nginx/nginx.conf), always test the configuration and reload Nginx:
sudo nginx -t sudo systemctl reload nginx
Database Security (MySQL/MariaDB Example)
Securing the database backend is paramount. This involves user privileges, network access, and data encryption.
1. Principle of Least Privilege for Database Users: The PHP application should connect to the database using a dedicated user account with only the necessary privileges. Avoid using the root user.
-- Connect as root or a privileged user mysql -u root -p -- Create a new user for the application CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'secure_password_here'; -- Grant only necessary privileges on the application's database GRANT SELECT, INSERT, UPDATE, DELETE ON app_database.* TO 'app_user'@'localhost'; -- If the application needs to create tables or alter schema (less common for production apps) -- GRANT CREATE, ALTER, DROP ON app_database.* TO 'app_user'@'localhost'; -- Apply the changes FLUSH PRIVILEGES; -- Exit MySQL client EXIT;
2. Restrict Network Access: Configure the database server to listen only on localhost if the web server and database server are on the same machine. If they are on different machines, restrict access to specific IP addresses.
# In MySQL/MariaDB configuration file (e.g., /etc/mysql/my.cnf or /etc/mysql/mariadb.conf.d/50-server.cnf) [mysqld] bind-address = 127.0.0.1 # Or the specific IP of the web server if remote
After changing the configuration, restart the MySQL/MariaDB service:
sudo systemctl restart mysql # Or mariadb
3. Secure Connection (SSL/TLS): Ensure PHP applications connect to the database using SSL/TLS encryption. This requires configuring the database server for SSL and updating the PHP database connection parameters.
4. Regular Backups and Auditing: Implement a robust backup strategy and regularly audit database logs for suspicious activity.
Application-Level Security Checks
Beyond server and PHP configuration, the application code itself must be secure.
1. Input Validation and Sanitization: All user-supplied input (GET, POST, COOKIE, headers, file uploads) must be rigorously validated and sanitized to prevent injection attacks (SQLi, XSS, command injection).
// Example: Basic validation for a username
function sanitize_username($input) {
// Remove characters that are not alphanumeric, underscore, or hyphen
$sanitized = preg_replace('/[^a-zA-Z0-9_-]/', '', $input);
// Trim whitespace
return trim($sanitized);
}
$username = sanitize_username($_POST['username']);
// Example: Using prepared statements for SQL queries (essential!)
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->bindParam(':username', $username);
$stmt->execute();
$user = $stmt->fetch();
2. Output Encoding: When displaying user-provided data in HTML, ensure it is properly encoded to prevent XSS.
// Example: Displaying user-generated content echo htmlspecialchars($user_comment, ENT_QUOTES, 'UTF-8');
3. Secure Session Management: Use strong, random session IDs and regenerate them upon login or privilege change. Store session data securely.
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Regenerate session ID on login
if (!isset($_SESSION['user_id'])) {
session_regenerate_id(true); // true deletes the old session file
$_SESSION['user_id'] = $user_id;
$_SESSION['logged_in'] = time();
}
// Check for session timeout
$timeout_duration = 1800; // 30 minutes
if (isset($_SESSION['logged_in']) && (time() - $_SESSION['logged_in'] > $timeout_duration)) {
session_unset();
session_destroy();
// Redirect to login page
}
4. File Upload Security: Validate file types, sizes, and scan for malware. Store uploaded files outside the webroot and use non-executable file names.
// Basic example, real-world requires more checks
$allowed_types = ['image/jpeg', 'image/png'];
$max_size = 5 * 1024 * 1024; // 5MB
if (isset($_FILES['user_file']) && $_FILES['user_file']['error'] === UPLOAD_ERR_OK) {
$file_tmp_path = $_FILES['user_file']['tmp_name'];
$file_name = $_FILES['user_file']['name'];
$file_type = $_FILES['user_file']['type'];
$file_size = $_FILES['user_file']['size'];
// Validate type and size
if (!in_array($file_type, $allowed_types)) {
// Error: Invalid file type
}
if ($file_size > $max_size) {
// Error: File too large
}
// Generate a secure, unique filename
$new_file_name = uniqid('upload_', true) . '.' . pathinfo($file_name, PATHINFO_EXTENSION);
$upload_dir = '/var/www/uploads/'; // Ensure this directory is NOT web-accessible
if (move_uploaded_file($file_tmp_path, $upload_dir . $new_file_name)) {
// File uploaded successfully
} else {
// Error moving file
}
}
5. Dependency Management: Use tools like Composer to manage third-party libraries. Regularly update dependencies and use tools like composer audit to check for known vulnerabilities.
composer update composer audit
System-Level Security and Monitoring
Auditing extends to the underlying Linode infrastructure and operational practices.
1. Firewall Configuration: Ensure a firewall (e.g., ufw) is active and configured to allow only necessary ports (e.g., 22 for SSH, 80 for HTTP, 443 for HTTPS).
sudo ufw status verbose sudo ufw allow ssh sudo ufw allow http sudo ufw allow https sudo ufw enable
2. SSH Security: Disable root login, use key-based authentication, and consider changing the default SSH port (though this is more obscurity than true security). Regularly review SSH logs.
# In /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes # Port 2222 # Optional: change port
Restart SSH service after changes:
sudo systemctl restart sshd
3. Log Monitoring: Centralize and monitor logs from PHP, the web server, and the system. Tools like fail2ban can automatically block IPs exhibiting malicious behavior.
# Example: Configure fail2ban to monitor Nginx access/error logs for 4xx/5xx errors # In /etc/fail2ban/jail.local [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/*error.log maxretry = 6 findtime = 600 # 10 minutes bantime = 3600 # 1 hour # Ensure you have the corresponding filter in /etc/fail2ban/filter.d/nginx-http-auth.conf # Example filter content: # [Definition] # failregex = ^-.*"(GET|POST).*HTTP/1\.[01]"\s[45]\d{2} # ignoreregex =
4. Regular Updates: Keep the operating system, web server, PHP, and all installed packages up-to-date with security patches.
sudo apt update && sudo apt upgrade -y
5. Access Control for Sensitive Files: Ensure that sensitive files (e.g., .env files, private keys, configuration files) have strict file permissions and are not readable by the web server user.
# Example: Set permissions for a .env file chmod 600 .env chown root:root .env # Or a dedicated deployment user, NOT www-data
location ~ \.php$ {
include snippets/fastcgi-php.conf;
# With php-fpm (or other unix sockets):
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Adjust path and version
# Or using TCP/IP:
# fastcgi_pass 127.0.0.1:9000;
# Prevent direct execution of non-script PHP files
try_files $uri =404;
}
3. Security Headers: Implement security headers to instruct browsers on how to behave securely.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; # Example, adjust as needed # Content Security Policy (CSP) is highly recommended but complex. Start simple and iterate. # add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none';" always;
4. Rate Limiting and Access Control: Protect against brute-force attacks and excessive requests.
# Example: Limit requests to the login page
location /login {
limit_req zone=myloginburst burst=5 nodelay;
# ... other login-related configurations
}
# Define the rate limiting zone
# http block
http {
limit_req_zone $binary_remote_addr zone=myloginburst:10m rate=5r/min;
# ... other http configurations
}
After modifying Nginx configuration files (typically in /etc/nginx/sites-available/ or /etc/nginx/nginx.conf), always test the configuration and reload Nginx:
sudo nginx -t sudo systemctl reload nginx
Database Security (MySQL/MariaDB Example)
Securing the database backend is paramount. This involves user privileges, network access, and data encryption.
1. Principle of Least Privilege for Database Users: The PHP application should connect to the database using a dedicated user account with only the necessary privileges. Avoid using the root user.
-- Connect as root or a privileged user mysql -u root -p -- Create a new user for the application CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'secure_password_here'; -- Grant only necessary privileges on the application's database GRANT SELECT, INSERT, UPDATE, DELETE ON app_database.* TO 'app_user'@'localhost'; -- If the application needs to create tables or alter schema (less common for production apps) -- GRANT CREATE, ALTER, DROP ON app_database.* TO 'app_user'@'localhost'; -- Apply the changes FLUSH PRIVILEGES; -- Exit MySQL client EXIT;
2. Restrict Network Access: Configure the database server to listen only on localhost if the web server and database server are on the same machine. If they are on different machines, restrict access to specific IP addresses.
# In MySQL/MariaDB configuration file (e.g., /etc/mysql/my.cnf or /etc/mysql/mariadb.conf.d/50-server.cnf) [mysqld] bind-address = 127.0.0.1 # Or the specific IP of the web server if remote
After changing the configuration, restart the MySQL/MariaDB service:
sudo systemctl restart mysql # Or mariadb
3. Secure Connection (SSL/TLS): Ensure PHP applications connect to the database using SSL/TLS encryption. This requires configuring the database server for SSL and updating the PHP database connection parameters.
4. Regular Backups and Auditing: Implement a robust backup strategy and regularly audit database logs for suspicious activity.
Application-Level Security Checks
Beyond server and PHP configuration, the application code itself must be secure.
1. Input Validation and Sanitization: All user-supplied input (GET, POST, COOKIE, headers, file uploads) must be rigorously validated and sanitized to prevent injection attacks (SQLi, XSS, command injection).
// Example: Basic validation for a username
function sanitize_username($input) {
// Remove characters that are not alphanumeric, underscore, or hyphen
$sanitized = preg_replace('/[^a-zA-Z0-9_-]/', '', $input);
// Trim whitespace
return trim($sanitized);
}
$username = sanitize_username($_POST['username']);
// Example: Using prepared statements for SQL queries (essential!)
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->bindParam(':username', $username);
$stmt->execute();
$user = $stmt->fetch();
2. Output Encoding: When displaying user-provided data in HTML, ensure it is properly encoded to prevent XSS.
// Example: Displaying user-generated content echo htmlspecialchars($user_comment, ENT_QUOTES, 'UTF-8');
3. Secure Session Management: Use strong, random session IDs and regenerate them upon login or privilege change. Store session data securely.
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Regenerate session ID on login
if (!isset($_SESSION['user_id'])) {
session_regenerate_id(true); // true deletes the old session file
$_SESSION['user_id'] = $user_id;
$_SESSION['logged_in'] = time();
}
// Check for session timeout
$timeout_duration = 1800; // 30 minutes
if (isset($_SESSION['logged_in']) && (time() - $_SESSION['logged_in'] > $timeout_duration)) {
session_unset();
session_destroy();
// Redirect to login page
}
4. File Upload Security: Validate file types, sizes, and scan for malware. Store uploaded files outside the webroot and use non-executable file names.
// Basic example, real-world requires more checks
$allowed_types = ['image/jpeg', 'image/png'];
$max_size = 5 * 1024 * 1024; // 5MB
if (isset($_FILES['user_file']) && $_FILES['user_file']['error'] === UPLOAD_ERR_OK) {
$file_tmp_path = $_FILES['user_file']['tmp_name'];
$file_name = $_FILES['user_file']['name'];
$file_type = $_FILES['user_file']['type'];
$file_size = $_FILES['user_file']['size'];
// Validate type and size
if (!in_array($file_type, $allowed_types)) {
// Error: Invalid file type
}
if ($file_size > $max_size) {
// Error: File too large
}
// Generate a secure, unique filename
$new_file_name = uniqid('upload_', true) . '.' . pathinfo($file_name, PATHINFO_EXTENSION);
$upload_dir = '/var/www/uploads/'; // Ensure this directory is NOT web-accessible
if (move_uploaded_file($file_tmp_path, $upload_dir . $new_file_name)) {
// File uploaded successfully
} else {
// Error moving file
}
}
5. Dependency Management: Use tools like Composer to manage third-party libraries. Regularly update dependencies and use tools like composer audit to check for known vulnerabilities.
composer update composer audit
System-Level Security and Monitoring
Auditing extends to the underlying Linode infrastructure and operational practices.
1. Firewall Configuration: Ensure a firewall (e.g., ufw) is active and configured to allow only necessary ports (e.g., 22 for SSH, 80 for HTTP, 443 for HTTPS).
sudo ufw status verbose sudo ufw allow ssh sudo ufw allow http sudo ufw allow https sudo ufw enable
2. SSH Security: Disable root login, use key-based authentication, and consider changing the default SSH port (though this is more obscurity than true security). Regularly review SSH logs.
# In /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes # Port 2222 # Optional: change port
Restart SSH service after changes:
sudo systemctl restart sshd
3. Log Monitoring: Centralize and monitor logs from PHP, the web server, and the system. Tools like fail2ban can automatically block IPs exhibiting malicious behavior.
# Example: Configure fail2ban to monitor Nginx access/error logs for 4xx/5xx errors # In /etc/fail2ban/jail.local [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/*error.log maxretry = 6 findtime = 600 # 10 minutes bantime = 3600 # 1 hour # Ensure you have the corresponding filter in /etc/fail2ban/filter.d/nginx-http-auth.conf # Example filter content: # [Definition] # failregex = ^-.*"(GET|POST).*HTTP/1\.[01]"\s[45]\d{2} # ignoreregex =
4. Regular Updates: Keep the operating system, web server, PHP, and all installed packages up-to-date with security patches.
sudo apt update && sudo apt upgrade -y
5. Access Control for Sensitive Files: Ensure that sensitive files (e.g., .env files, private keys, configuration files) have strict file permissions and are not readable by the web server user.
# Example: Set permissions for a .env file chmod 600 .env chown root:root .env # Or a dedicated deployment user, NOT www-data
location ~ /\.ht {
deny all;
}
location ~* \.(inc|bak|config|sql|log)$ {
deny all;
}
2. PHP File Handling: Configure Nginx to pass PHP files only to the PHP-FPM process and prevent direct execution of PHP files if they are not intended to be served as scripts (e.g., configuration files that might be accidentally placed in the webroot).
location ~ \.php$ {
include snippets/fastcgi-php.conf;
# With php-fpm (or other unix sockets):
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Adjust path and version
# Or using TCP/IP:
# fastcgi_pass 127.0.0.1:9000;
# Prevent direct execution of non-script PHP files
try_files $uri =404;
}
3. Security Headers: Implement security headers to instruct browsers on how to behave securely.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; # Example, adjust as needed # Content Security Policy (CSP) is highly recommended but complex. Start simple and iterate. # add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none';" always;
4. Rate Limiting and Access Control: Protect against brute-force attacks and excessive requests.
# Example: Limit requests to the login page
location /login {
limit_req zone=myloginburst burst=5 nodelay;
# ... other login-related configurations
}
# Define the rate limiting zone
# http block
http {
limit_req_zone $binary_remote_addr zone=myloginburst:10m rate=5r/min;
# ... other http configurations
}
After modifying Nginx configuration files (typically in /etc/nginx/sites-available/ or /etc/nginx/nginx.conf), always test the configuration and reload Nginx:
sudo nginx -t sudo systemctl reload nginx
Database Security (MySQL/MariaDB Example)
Securing the database backend is paramount. This involves user privileges, network access, and data encryption.
1. Principle of Least Privilege for Database Users: The PHP application should connect to the database using a dedicated user account with only the necessary privileges. Avoid using the root user.
-- Connect as root or a privileged user mysql -u root -p -- Create a new user for the application CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'secure_password_here'; -- Grant only necessary privileges on the application's database GRANT SELECT, INSERT, UPDATE, DELETE ON app_database.* TO 'app_user'@'localhost'; -- If the application needs to create tables or alter schema (less common for production apps) -- GRANT CREATE, ALTER, DROP ON app_database.* TO 'app_user'@'localhost'; -- Apply the changes FLUSH PRIVILEGES; -- Exit MySQL client EXIT;
2. Restrict Network Access: Configure the database server to listen only on localhost if the web server and database server are on the same machine. If they are on different machines, restrict access to specific IP addresses.
# In MySQL/MariaDB configuration file (e.g., /etc/mysql/my.cnf or /etc/mysql/mariadb.conf.d/50-server.cnf) [mysqld] bind-address = 127.0.0.1 # Or the specific IP of the web server if remote
After changing the configuration, restart the MySQL/MariaDB service:
sudo systemctl restart mysql # Or mariadb
3. Secure Connection (SSL/TLS): Ensure PHP applications connect to the database using SSL/TLS encryption. This requires configuring the database server for SSL and updating the PHP database connection parameters.
4. Regular Backups and Auditing: Implement a robust backup strategy and regularly audit database logs for suspicious activity.
Application-Level Security Checks
Beyond server and PHP configuration, the application code itself must be secure.
1. Input Validation and Sanitization: All user-supplied input (GET, POST, COOKIE, headers, file uploads) must be rigorously validated and sanitized to prevent injection attacks (SQLi, XSS, command injection).
// Example: Basic validation for a username
function sanitize_username($input) {
// Remove characters that are not alphanumeric, underscore, or hyphen
$sanitized = preg_replace('/[^a-zA-Z0-9_-]/', '', $input);
// Trim whitespace
return trim($sanitized);
}
$username = sanitize_username($_POST['username']);
// Example: Using prepared statements for SQL queries (essential!)
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->bindParam(':username', $username);
$stmt->execute();
$user = $stmt->fetch();
2. Output Encoding: When displaying user-provided data in HTML, ensure it is properly encoded to prevent XSS.
// Example: Displaying user-generated content echo htmlspecialchars($user_comment, ENT_QUOTES, 'UTF-8');
3. Secure Session Management: Use strong, random session IDs and regenerate them upon login or privilege change. Store session data securely.
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Regenerate session ID on login
if (!isset($_SESSION['user_id'])) {
session_regenerate_id(true); // true deletes the old session file
$_SESSION['user_id'] = $user_id;
$_SESSION['logged_in'] = time();
}
// Check for session timeout
$timeout_duration = 1800; // 30 minutes
if (isset($_SESSION['logged_in']) && (time() - $_SESSION['logged_in'] > $timeout_duration)) {
session_unset();
session_destroy();
// Redirect to login page
}
4. File Upload Security: Validate file types, sizes, and scan for malware. Store uploaded files outside the webroot and use non-executable file names.
// Basic example, real-world requires more checks
$allowed_types = ['image/jpeg', 'image/png'];
$max_size = 5 * 1024 * 1024; // 5MB
if (isset($_FILES['user_file']) && $_FILES['user_file']['error'] === UPLOAD_ERR_OK) {
$file_tmp_path = $_FILES['user_file']['tmp_name'];
$file_name = $_FILES['user_file']['name'];
$file_type = $_FILES['user_file']['type'];
$file_size = $_FILES['user_file']['size'];
// Validate type and size
if (!in_array($file_type, $allowed_types)) {
// Error: Invalid file type
}
if ($file_size > $max_size) {
// Error: File too large
}
// Generate a secure, unique filename
$new_file_name = uniqid('upload_', true) . '.' . pathinfo($file_name, PATHINFO_EXTENSION);
$upload_dir = '/var/www/uploads/'; // Ensure this directory is NOT web-accessible
if (move_uploaded_file($file_tmp_path, $upload_dir . $new_file_name)) {
// File uploaded successfully
} else {
// Error moving file
}
}
5. Dependency Management: Use tools like Composer to manage third-party libraries. Regularly update dependencies and use tools like composer audit to check for known vulnerabilities.
composer update composer audit
System-Level Security and Monitoring
Auditing extends to the underlying Linode infrastructure and operational practices.
1. Firewall Configuration: Ensure a firewall (e.g., ufw) is active and configured to allow only necessary ports (e.g., 22 for SSH, 80 for HTTP, 443 for HTTPS).
sudo ufw status verbose sudo ufw allow ssh sudo ufw allow http sudo ufw allow https sudo ufw enable
2. SSH Security: Disable root login, use key-based authentication, and consider changing the default SSH port (though this is more obscurity than true security). Regularly review SSH logs.
# In /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes # Port 2222 # Optional: change port
Restart SSH service after changes:
sudo systemctl restart sshd
3. Log Monitoring: Centralize and monitor logs from PHP, the web server, and the system. Tools like fail2ban can automatically block IPs exhibiting malicious behavior.
# Example: Configure fail2ban to monitor Nginx access/error logs for 4xx/5xx errors # In /etc/fail2ban/jail.local [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/*error.log maxretry = 6 findtime = 600 # 10 minutes bantime = 3600 # 1 hour # Ensure you have the corresponding filter in /etc/fail2ban/filter.d/nginx-http-auth.conf # Example filter content: # [Definition] # failregex = ^-.*"(GET|POST).*HTTP/1\.[01]"\s[45]\d{2} # ignoreregex =
4. Regular Updates: Keep the operating system, web server, PHP, and all installed packages up-to-date with security patches.
sudo apt update && sudo apt upgrade -y
5. Access Control for Sensitive Files: Ensure that sensitive files (e.g., .env files, private keys, configuration files) have strict file permissions and are not readable by the web server user.
# Example: Set permissions for a .env file chmod 600 .env chown root:root .env # Or a dedicated deployment user, NOT www-data
[PHP] expose_php = Off display_errors = Off log_errors = On error_log = /var/log/php/php_errors.log session.cookie_httponly = 1 session.cookie_secure = 1 session.use_strict_mode = 1 disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source allow_url_fopen = Off allow_url_include = Off
Remember to restart the PHP-FPM service after applying these changes.
Web Server Configuration (Nginx Example)
The web server plays a crucial role in fronting the PHP application. For Nginx, several configurations are vital for security.
1. Restrict Access to Sensitive Files: Ensure that configuration files, source code directories (if not directly served), and log files are not accessible via HTTP requests.
location ~ /\.ht {
deny all;
}
location ~* \.(inc|bak|config|sql|log)$ {
deny all;
}
2. PHP File Handling: Configure Nginx to pass PHP files only to the PHP-FPM process and prevent direct execution of PHP files if they are not intended to be served as scripts (e.g., configuration files that might be accidentally placed in the webroot).
location ~ \.php$ {
include snippets/fastcgi-php.conf;
# With php-fpm (or other unix sockets):
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Adjust path and version
# Or using TCP/IP:
# fastcgi_pass 127.0.0.1:9000;
# Prevent direct execution of non-script PHP files
try_files $uri =404;
}
3. Security Headers: Implement security headers to instruct browsers on how to behave securely.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; # Example, adjust as needed # Content Security Policy (CSP) is highly recommended but complex. Start simple and iterate. # add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none';" always;
4. Rate Limiting and Access Control: Protect against brute-force attacks and excessive requests.
# Example: Limit requests to the login page
location /login {
limit_req zone=myloginburst burst=5 nodelay;
# ... other login-related configurations
}
# Define the rate limiting zone
# http block
http {
limit_req_zone $binary_remote_addr zone=myloginburst:10m rate=5r/min;
# ... other http configurations
}
After modifying Nginx configuration files (typically in /etc/nginx/sites-available/ or /etc/nginx/nginx.conf), always test the configuration and reload Nginx:
sudo nginx -t sudo systemctl reload nginx
Database Security (MySQL/MariaDB Example)
Securing the database backend is paramount. This involves user privileges, network access, and data encryption.
1. Principle of Least Privilege for Database Users: The PHP application should connect to the database using a dedicated user account with only the necessary privileges. Avoid using the root user.
-- Connect as root or a privileged user mysql -u root -p -- Create a new user for the application CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'secure_password_here'; -- Grant only necessary privileges on the application's database GRANT SELECT, INSERT, UPDATE, DELETE ON app_database.* TO 'app_user'@'localhost'; -- If the application needs to create tables or alter schema (less common for production apps) -- GRANT CREATE, ALTER, DROP ON app_database.* TO 'app_user'@'localhost'; -- Apply the changes FLUSH PRIVILEGES; -- Exit MySQL client EXIT;
2. Restrict Network Access: Configure the database server to listen only on localhost if the web server and database server are on the same machine. If they are on different machines, restrict access to specific IP addresses.
# In MySQL/MariaDB configuration file (e.g., /etc/mysql/my.cnf or /etc/mysql/mariadb.conf.d/50-server.cnf) [mysqld] bind-address = 127.0.0.1 # Or the specific IP of the web server if remote
After changing the configuration, restart the MySQL/MariaDB service:
sudo systemctl restart mysql # Or mariadb
3. Secure Connection (SSL/TLS): Ensure PHP applications connect to the database using SSL/TLS encryption. This requires configuring the database server for SSL and updating the PHP database connection parameters.
4. Regular Backups and Auditing: Implement a robust backup strategy and regularly audit database logs for suspicious activity.
Application-Level Security Checks
Beyond server and PHP configuration, the application code itself must be secure.
1. Input Validation and Sanitization: All user-supplied input (GET, POST, COOKIE, headers, file uploads) must be rigorously validated and sanitized to prevent injection attacks (SQLi, XSS, command injection).
// Example: Basic validation for a username
function sanitize_username($input) {
// Remove characters that are not alphanumeric, underscore, or hyphen
$sanitized = preg_replace('/[^a-zA-Z0-9_-]/', '', $input);
// Trim whitespace
return trim($sanitized);
}
$username = sanitize_username($_POST['username']);
// Example: Using prepared statements for SQL queries (essential!)
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->bindParam(':username', $username);
$stmt->execute();
$user = $stmt->fetch();
2. Output Encoding: When displaying user-provided data in HTML, ensure it is properly encoded to prevent XSS.
// Example: Displaying user-generated content echo htmlspecialchars($user_comment, ENT_QUOTES, 'UTF-8');
3. Secure Session Management: Use strong, random session IDs and regenerate them upon login or privilege change. Store session data securely.
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Regenerate session ID on login
if (!isset($_SESSION['user_id'])) {
session_regenerate_id(true); // true deletes the old session file
$_SESSION['user_id'] = $user_id;
$_SESSION['logged_in'] = time();
}
// Check for session timeout
$timeout_duration = 1800; // 30 minutes
if (isset($_SESSION['logged_in']) && (time() - $_SESSION['logged_in'] > $timeout_duration)) {
session_unset();
session_destroy();
// Redirect to login page
}
4. File Upload Security: Validate file types, sizes, and scan for malware. Store uploaded files outside the webroot and use non-executable file names.
// Basic example, real-world requires more checks
$allowed_types = ['image/jpeg', 'image/png'];
$max_size = 5 * 1024 * 1024; // 5MB
if (isset($_FILES['user_file']) && $_FILES['user_file']['error'] === UPLOAD_ERR_OK) {
$file_tmp_path = $_FILES['user_file']['tmp_name'];
$file_name = $_FILES['user_file']['name'];
$file_type = $_FILES['user_file']['type'];
$file_size = $_FILES['user_file']['size'];
// Validate type and size
if (!in_array($file_type, $allowed_types)) {
// Error: Invalid file type
}
if ($file_size > $max_size) {
// Error: File too large
}
// Generate a secure, unique filename
$new_file_name = uniqid('upload_', true) . '.' . pathinfo($file_name, PATHINFO_EXTENSION);
$upload_dir = '/var/www/uploads/'; // Ensure this directory is NOT web-accessible
if (move_uploaded_file($file_tmp_path, $upload_dir . $new_file_name)) {
// File uploaded successfully
} else {
// Error moving file
}
}
5. Dependency Management: Use tools like Composer to manage third-party libraries. Regularly update dependencies and use tools like composer audit to check for known vulnerabilities.
composer update composer audit
System-Level Security and Monitoring
Auditing extends to the underlying Linode infrastructure and operational practices.
1. Firewall Configuration: Ensure a firewall (e.g., ufw) is active and configured to allow only necessary ports (e.g., 22 for SSH, 80 for HTTP, 443 for HTTPS).
sudo ufw status verbose sudo ufw allow ssh sudo ufw allow http sudo ufw allow https sudo ufw enable
2. SSH Security: Disable root login, use key-based authentication, and consider changing the default SSH port (though this is more obscurity than true security). Regularly review SSH logs.
# In /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes # Port 2222 # Optional: change port
Restart SSH service after changes:
sudo systemctl restart sshd
3. Log Monitoring: Centralize and monitor logs from PHP, the web server, and the system. Tools like fail2ban can automatically block IPs exhibiting malicious behavior.
# Example: Configure fail2ban to monitor Nginx access/error logs for 4xx/5xx errors # In /etc/fail2ban/jail.local [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/*error.log maxretry = 6 findtime = 600 # 10 minutes bantime = 3600 # 1 hour # Ensure you have the corresponding filter in /etc/fail2ban/filter.d/nginx-http-auth.conf # Example filter content: # [Definition] # failregex = ^-.*"(GET|POST).*HTTP/1\.[01]"\s[45]\d{2} # ignoreregex =
4. Regular Updates: Keep the operating system, web server, PHP, and all installed packages up-to-date with security patches.
sudo apt update && sudo apt upgrade -y
5. Access Control for Sensitive Files: Ensure that sensitive files (e.g., .env files, private keys, configuration files) have strict file permissions and are not readable by the web server user.
# Example: Set permissions for a .env file chmod 600 .env chown root:root .env # Or a dedicated deployment user, NOT www-data
sudo systemctl restart php8.1-fpm # Adjust version as needed sudo systemctl restart nginx # If Nginx is used as the web server
PHP Configuration Hardening (php.ini)
Beyond version and extensions, the php.ini file offers numerous directives that can significantly enhance security. Auditors should scrutinize these settings.
Key directives to review and harden include:
expose_php = Off: Prevents the PHP version from being revealed in HTTP headers (e.g.,X-Powered-By: PHP/8.1.2), reducing reconnaissance opportunities for attackers.display_errors = Off: In production environments, error messages should not be displayed to end-users. This can leak sensitive information about the application’s internal workings. Errors should be logged instead.log_errors = On: Ensures that errors are logged to a file for later analysis.error_log = /var/log/php/php_errors.log: Specifies the path for error logging. Ensure this directory is not web-accessible and has appropriate file permissions.session.cookie_httponly = 1: Makes session cookies inaccessible to client-side scripts (JavaScript), mitigating cross-site scripting (XSS) attacks that aim to steal session IDs.session.cookie_secure = 1: Ensures that session cookies are only sent over HTTPS connections, preventing them from being intercepted on unencrypted networks.session.use_strict_mode = 1: Prevents PHP from accepting uninitialized session IDs, mitigating session fixation vulnerabilities.disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source: A critical directive. This list should contain any PHP functions that are not strictly necessary for the application’s operation, especially those that can execute system commands or access the filesystem in potentially dangerous ways. The list above is a common starting point; tailor it to your application’s needs.allow_url_fopen = Off: Disables the ability for PHP functions likefopen(),file_get_contents(), etc., to fetch data from remote URLs. This prevents many types of SSRF (Server-Side Request Forgery) attacks.allow_url_include = Off: Disables the ability to include remote files viainclude(),require(), etc. This is a crucial defense against remote file inclusion (RFI) vulnerabilities.
An example snippet of a hardened php.ini section:
[PHP] expose_php = Off display_errors = Off log_errors = On error_log = /var/log/php/php_errors.log session.cookie_httponly = 1 session.cookie_secure = 1 session.use_strict_mode = 1 disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source allow_url_fopen = Off allow_url_include = Off
Remember to restart the PHP-FPM service after applying these changes.
Web Server Configuration (Nginx Example)
The web server plays a crucial role in fronting the PHP application. For Nginx, several configurations are vital for security.
1. Restrict Access to Sensitive Files: Ensure that configuration files, source code directories (if not directly served), and log files are not accessible via HTTP requests.
location ~ /\.ht {
deny all;
}
location ~* \.(inc|bak|config|sql|log)$ {
deny all;
}
2. PHP File Handling: Configure Nginx to pass PHP files only to the PHP-FPM process and prevent direct execution of PHP files if they are not intended to be served as scripts (e.g., configuration files that might be accidentally placed in the webroot).
location ~ \.php$ {
include snippets/fastcgi-php.conf;
# With php-fpm (or other unix sockets):
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Adjust path and version
# Or using TCP/IP:
# fastcgi_pass 127.0.0.1:9000;
# Prevent direct execution of non-script PHP files
try_files $uri =404;
}
3. Security Headers: Implement security headers to instruct browsers on how to behave securely.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; # Example, adjust as needed # Content Security Policy (CSP) is highly recommended but complex. Start simple and iterate. # add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none';" always;
4. Rate Limiting and Access Control: Protect against brute-force attacks and excessive requests.
# Example: Limit requests to the login page
location /login {
limit_req zone=myloginburst burst=5 nodelay;
# ... other login-related configurations
}
# Define the rate limiting zone
# http block
http {
limit_req_zone $binary_remote_addr zone=myloginburst:10m rate=5r/min;
# ... other http configurations
}
After modifying Nginx configuration files (typically in /etc/nginx/sites-available/ or /etc/nginx/nginx.conf), always test the configuration and reload Nginx:
sudo nginx -t sudo systemctl reload nginx
Database Security (MySQL/MariaDB Example)
Securing the database backend is paramount. This involves user privileges, network access, and data encryption.
1. Principle of Least Privilege for Database Users: The PHP application should connect to the database using a dedicated user account with only the necessary privileges. Avoid using the root user.
-- Connect as root or a privileged user mysql -u root -p -- Create a new user for the application CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'secure_password_here'; -- Grant only necessary privileges on the application's database GRANT SELECT, INSERT, UPDATE, DELETE ON app_database.* TO 'app_user'@'localhost'; -- If the application needs to create tables or alter schema (less common for production apps) -- GRANT CREATE, ALTER, DROP ON app_database.* TO 'app_user'@'localhost'; -- Apply the changes FLUSH PRIVILEGES; -- Exit MySQL client EXIT;
2. Restrict Network Access: Configure the database server to listen only on localhost if the web server and database server are on the same machine. If they are on different machines, restrict access to specific IP addresses.
# In MySQL/MariaDB configuration file (e.g., /etc/mysql/my.cnf or /etc/mysql/mariadb.conf.d/50-server.cnf) [mysqld] bind-address = 127.0.0.1 # Or the specific IP of the web server if remote
After changing the configuration, restart the MySQL/MariaDB service:
sudo systemctl restart mysql # Or mariadb
3. Secure Connection (SSL/TLS): Ensure PHP applications connect to the database using SSL/TLS encryption. This requires configuring the database server for SSL and updating the PHP database connection parameters.
4. Regular Backups and Auditing: Implement a robust backup strategy and regularly audit database logs for suspicious activity.
Application-Level Security Checks
Beyond server and PHP configuration, the application code itself must be secure.
1. Input Validation and Sanitization: All user-supplied input (GET, POST, COOKIE, headers, file uploads) must be rigorously validated and sanitized to prevent injection attacks (SQLi, XSS, command injection).
// Example: Basic validation for a username
function sanitize_username($input) {
// Remove characters that are not alphanumeric, underscore, or hyphen
$sanitized = preg_replace('/[^a-zA-Z0-9_-]/', '', $input);
// Trim whitespace
return trim($sanitized);
}
$username = sanitize_username($_POST['username']);
// Example: Using prepared statements for SQL queries (essential!)
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->bindParam(':username', $username);
$stmt->execute();
$user = $stmt->fetch();
2. Output Encoding: When displaying user-provided data in HTML, ensure it is properly encoded to prevent XSS.
// Example: Displaying user-generated content echo htmlspecialchars($user_comment, ENT_QUOTES, 'UTF-8');
3. Secure Session Management: Use strong, random session IDs and regenerate them upon login or privilege change. Store session data securely.
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Regenerate session ID on login
if (!isset($_SESSION['user_id'])) {
session_regenerate_id(true); // true deletes the old session file
$_SESSION['user_id'] = $user_id;
$_SESSION['logged_in'] = time();
}
// Check for session timeout
$timeout_duration = 1800; // 30 minutes
if (isset($_SESSION['logged_in']) && (time() - $_SESSION['logged_in'] > $timeout_duration)) {
session_unset();
session_destroy();
// Redirect to login page
}
4. File Upload Security: Validate file types, sizes, and scan for malware. Store uploaded files outside the webroot and use non-executable file names.
// Basic example, real-world requires more checks
$allowed_types = ['image/jpeg', 'image/png'];
$max_size = 5 * 1024 * 1024; // 5MB
if (isset($_FILES['user_file']) && $_FILES['user_file']['error'] === UPLOAD_ERR_OK) {
$file_tmp_path = $_FILES['user_file']['tmp_name'];
$file_name = $_FILES['user_file']['name'];
$file_type = $_FILES['user_file']['type'];
$file_size = $_FILES['user_file']['size'];
// Validate type and size
if (!in_array($file_type, $allowed_types)) {
// Error: Invalid file type
}
if ($file_size > $max_size) {
// Error: File too large
}
// Generate a secure, unique filename
$new_file_name = uniqid('upload_', true) . '.' . pathinfo($file_name, PATHINFO_EXTENSION);
$upload_dir = '/var/www/uploads/'; // Ensure this directory is NOT web-accessible
if (move_uploaded_file($file_tmp_path, $upload_dir . $new_file_name)) {
// File uploaded successfully
} else {
// Error moving file
}
}
5. Dependency Management: Use tools like Composer to manage third-party libraries. Regularly update dependencies and use tools like composer audit to check for known vulnerabilities.
composer update composer audit
System-Level Security and Monitoring
Auditing extends to the underlying Linode infrastructure and operational practices.
1. Firewall Configuration: Ensure a firewall (e.g., ufw) is active and configured to allow only necessary ports (e.g., 22 for SSH, 80 for HTTP, 443 for HTTPS).
sudo ufw status verbose sudo ufw allow ssh sudo ufw allow http sudo ufw allow https sudo ufw enable
2. SSH Security: Disable root login, use key-based authentication, and consider changing the default SSH port (though this is more obscurity than true security). Regularly review SSH logs.
# In /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes # Port 2222 # Optional: change port
Restart SSH service after changes:
sudo systemctl restart sshd
3. Log Monitoring: Centralize and monitor logs from PHP, the web server, and the system. Tools like fail2ban can automatically block IPs exhibiting malicious behavior.
# Example: Configure fail2ban to monitor Nginx access/error logs for 4xx/5xx errors # In /etc/fail2ban/jail.local [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/*error.log maxretry = 6 findtime = 600 # 10 minutes bantime = 3600 # 1 hour # Ensure you have the corresponding filter in /etc/fail2ban/filter.d/nginx-http-auth.conf # Example filter content: # [Definition] # failregex = ^-.*"(GET|POST).*HTTP/1\.[01]"\s[45]\d{2} # ignoreregex =
4. Regular Updates: Keep the operating system, web server, PHP, and all installed packages up-to-date with security patches.
sudo apt update && sudo apt upgrade -y
5. Access Control for Sensitive Files: Ensure that sensitive files (e.g., .env files, private keys, configuration files) have strict file permissions and are not readable by the web server user.
# Example: Set permissions for a .env file chmod 600 .env chown root:root .env # Or a dedicated deployment user, NOT www-data
; extension=redis
After modifying php.ini, the web server (e.g., Nginx with PHP-FPM) or the PHP CLI process must be restarted for changes to take effect.
sudo systemctl restart php8.1-fpm # Adjust version as needed sudo systemctl restart nginx # If Nginx is used as the web server
PHP Configuration Hardening (php.ini)
Beyond version and extensions, the php.ini file offers numerous directives that can significantly enhance security. Auditors should scrutinize these settings.
Key directives to review and harden include:
expose_php = Off: Prevents the PHP version from being revealed in HTTP headers (e.g.,X-Powered-By: PHP/8.1.2), reducing reconnaissance opportunities for attackers.display_errors = Off: In production environments, error messages should not be displayed to end-users. This can leak sensitive information about the application’s internal workings. Errors should be logged instead.log_errors = On: Ensures that errors are logged to a file for later analysis.error_log = /var/log/php/php_errors.log: Specifies the path for error logging. Ensure this directory is not web-accessible and has appropriate file permissions.session.cookie_httponly = 1: Makes session cookies inaccessible to client-side scripts (JavaScript), mitigating cross-site scripting (XSS) attacks that aim to steal session IDs.session.cookie_secure = 1: Ensures that session cookies are only sent over HTTPS connections, preventing them from being intercepted on unencrypted networks.session.use_strict_mode = 1: Prevents PHP from accepting uninitialized session IDs, mitigating session fixation vulnerabilities.disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source: A critical directive. This list should contain any PHP functions that are not strictly necessary for the application’s operation, especially those that can execute system commands or access the filesystem in potentially dangerous ways. The list above is a common starting point; tailor it to your application’s needs.allow_url_fopen = Off: Disables the ability for PHP functions likefopen(),file_get_contents(), etc., to fetch data from remote URLs. This prevents many types of SSRF (Server-Side Request Forgery) attacks.allow_url_include = Off: Disables the ability to include remote files viainclude(),require(), etc. This is a crucial defense against remote file inclusion (RFI) vulnerabilities.
An example snippet of a hardened php.ini section:
[PHP] expose_php = Off display_errors = Off log_errors = On error_log = /var/log/php/php_errors.log session.cookie_httponly = 1 session.cookie_secure = 1 session.use_strict_mode = 1 disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source allow_url_fopen = Off allow_url_include = Off
Remember to restart the PHP-FPM service after applying these changes.
Web Server Configuration (Nginx Example)
The web server plays a crucial role in fronting the PHP application. For Nginx, several configurations are vital for security.
1. Restrict Access to Sensitive Files: Ensure that configuration files, source code directories (if not directly served), and log files are not accessible via HTTP requests.
location ~ /\.ht {
deny all;
}
location ~* \.(inc|bak|config|sql|log)$ {
deny all;
}
2. PHP File Handling: Configure Nginx to pass PHP files only to the PHP-FPM process and prevent direct execution of PHP files if they are not intended to be served as scripts (e.g., configuration files that might be accidentally placed in the webroot).
location ~ \.php$ {
include snippets/fastcgi-php.conf;
# With php-fpm (or other unix sockets):
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Adjust path and version
# Or using TCP/IP:
# fastcgi_pass 127.0.0.1:9000;
# Prevent direct execution of non-script PHP files
try_files $uri =404;
}
3. Security Headers: Implement security headers to instruct browsers on how to behave securely.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; # Example, adjust as needed # Content Security Policy (CSP) is highly recommended but complex. Start simple and iterate. # add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none';" always;
4. Rate Limiting and Access Control: Protect against brute-force attacks and excessive requests.
# Example: Limit requests to the login page
location /login {
limit_req zone=myloginburst burst=5 nodelay;
# ... other login-related configurations
}
# Define the rate limiting zone
# http block
http {
limit_req_zone $binary_remote_addr zone=myloginburst:10m rate=5r/min;
# ... other http configurations
}
After modifying Nginx configuration files (typically in /etc/nginx/sites-available/ or /etc/nginx/nginx.conf), always test the configuration and reload Nginx:
sudo nginx -t sudo systemctl reload nginx
Database Security (MySQL/MariaDB Example)
Securing the database backend is paramount. This involves user privileges, network access, and data encryption.
1. Principle of Least Privilege for Database Users: The PHP application should connect to the database using a dedicated user account with only the necessary privileges. Avoid using the root user.
-- Connect as root or a privileged user mysql -u root -p -- Create a new user for the application CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'secure_password_here'; -- Grant only necessary privileges on the application's database GRANT SELECT, INSERT, UPDATE, DELETE ON app_database.* TO 'app_user'@'localhost'; -- If the application needs to create tables or alter schema (less common for production apps) -- GRANT CREATE, ALTER, DROP ON app_database.* TO 'app_user'@'localhost'; -- Apply the changes FLUSH PRIVILEGES; -- Exit MySQL client EXIT;
2. Restrict Network Access: Configure the database server to listen only on localhost if the web server and database server are on the same machine. If they are on different machines, restrict access to specific IP addresses.
# In MySQL/MariaDB configuration file (e.g., /etc/mysql/my.cnf or /etc/mysql/mariadb.conf.d/50-server.cnf) [mysqld] bind-address = 127.0.0.1 # Or the specific IP of the web server if remote
After changing the configuration, restart the MySQL/MariaDB service:
sudo systemctl restart mysql # Or mariadb
3. Secure Connection (SSL/TLS): Ensure PHP applications connect to the database using SSL/TLS encryption. This requires configuring the database server for SSL and updating the PHP database connection parameters.
4. Regular Backups and Auditing: Implement a robust backup strategy and regularly audit database logs for suspicious activity.
Application-Level Security Checks
Beyond server and PHP configuration, the application code itself must be secure.
1. Input Validation and Sanitization: All user-supplied input (GET, POST, COOKIE, headers, file uploads) must be rigorously validated and sanitized to prevent injection attacks (SQLi, XSS, command injection).
// Example: Basic validation for a username
function sanitize_username($input) {
// Remove characters that are not alphanumeric, underscore, or hyphen
$sanitized = preg_replace('/[^a-zA-Z0-9_-]/', '', $input);
// Trim whitespace
return trim($sanitized);
}
$username = sanitize_username($_POST['username']);
// Example: Using prepared statements for SQL queries (essential!)
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->bindParam(':username', $username);
$stmt->execute();
$user = $stmt->fetch();
2. Output Encoding: When displaying user-provided data in HTML, ensure it is properly encoded to prevent XSS.
// Example: Displaying user-generated content echo htmlspecialchars($user_comment, ENT_QUOTES, 'UTF-8');
3. Secure Session Management: Use strong, random session IDs and regenerate them upon login or privilege change. Store session data securely.
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Regenerate session ID on login
if (!isset($_SESSION['user_id'])) {
session_regenerate_id(true); // true deletes the old session file
$_SESSION['user_id'] = $user_id;
$_SESSION['logged_in'] = time();
}
// Check for session timeout
$timeout_duration = 1800; // 30 minutes
if (isset($_SESSION['logged_in']) && (time() - $_SESSION['logged_in'] > $timeout_duration)) {
session_unset();
session_destroy();
// Redirect to login page
}
4. File Upload Security: Validate file types, sizes, and scan for malware. Store uploaded files outside the webroot and use non-executable file names.
// Basic example, real-world requires more checks
$allowed_types = ['image/jpeg', 'image/png'];
$max_size = 5 * 1024 * 1024; // 5MB
if (isset($_FILES['user_file']) && $_FILES['user_file']['error'] === UPLOAD_ERR_OK) {
$file_tmp_path = $_FILES['user_file']['tmp_name'];
$file_name = $_FILES['user_file']['name'];
$file_type = $_FILES['user_file']['type'];
$file_size = $_FILES['user_file']['size'];
// Validate type and size
if (!in_array($file_type, $allowed_types)) {
// Error: Invalid file type
}
if ($file_size > $max_size) {
// Error: File too large
}
// Generate a secure, unique filename
$new_file_name = uniqid('upload_', true) . '.' . pathinfo($file_name, PATHINFO_EXTENSION);
$upload_dir = '/var/www/uploads/'; // Ensure this directory is NOT web-accessible
if (move_uploaded_file($file_tmp_path, $upload_dir . $new_file_name)) {
// File uploaded successfully
} else {
// Error moving file
}
}
5. Dependency Management: Use tools like Composer to manage third-party libraries. Regularly update dependencies and use tools like composer audit to check for known vulnerabilities.
composer update composer audit
System-Level Security and Monitoring
Auditing extends to the underlying Linode infrastructure and operational practices.
1. Firewall Configuration: Ensure a firewall (e.g., ufw) is active and configured to allow only necessary ports (e.g., 22 for SSH, 80 for HTTP, 443 for HTTPS).
sudo ufw status verbose sudo ufw allow ssh sudo ufw allow http sudo ufw allow https sudo ufw enable
2. SSH Security: Disable root login, use key-based authentication, and consider changing the default SSH port (though this is more obscurity than true security). Regularly review SSH logs.
# In /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes # Port 2222 # Optional: change port
Restart SSH service after changes:
sudo systemctl restart sshd
3. Log Monitoring: Centralize and monitor logs from PHP, the web server, and the system. Tools like fail2ban can automatically block IPs exhibiting malicious behavior.
# Example: Configure fail2ban to monitor Nginx access/error logs for 4xx/5xx errors # In /etc/fail2ban/jail.local [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/*error.log maxretry = 6 findtime = 600 # 10 minutes bantime = 3600 # 1 hour # Ensure you have the corresponding filter in /etc/fail2ban/filter.d/nginx-http-auth.conf # Example filter content: # [Definition] # failregex = ^-.*"(GET|POST).*HTTP/1\.[01]"\s[45]\d{2} # ignoreregex =
4. Regular Updates: Keep the operating system, web server, PHP, and all installed packages up-to-date with security patches.
sudo apt update && sudo apt upgrade -y
5. Access Control for Sensitive Files: Ensure that sensitive files (e.g., .env files, private keys, configuration files) have strict file permissions and are not readable by the web server user.
# Example: Set permissions for a .env file chmod 600 .env chown root:root .env # Or a dedicated deployment user, NOT www-data
<?php phpinfo(); ?>
Access this file via your web browser (e.g., http://your_domain.com/info.php). Look for the “PHP Version” section and the “Loaded Extensions” list. Auditors should verify that the version is within the supported lifecycle (e.g., PHP 8.1 or later, as of late 2023). Any extensions not explicitly required by the application should be disabled. This is typically done in the php.ini file. The location of this file can also be found in the phpinfo() output under “Loaded Configuration File”.
To disable an extension, comment out the corresponding extension=... line in php.ini. For example, to disable the redis extension:
; extension=redis
After modifying php.ini, the web server (e.g., Nginx with PHP-FPM) or the PHP CLI process must be restarted for changes to take effect.
sudo systemctl restart php8.1-fpm # Adjust version as needed sudo systemctl restart nginx # If Nginx is used as the web server
PHP Configuration Hardening (php.ini)
Beyond version and extensions, the php.ini file offers numerous directives that can significantly enhance security. Auditors should scrutinize these settings.
Key directives to review and harden include:
expose_php = Off: Prevents the PHP version from being revealed in HTTP headers (e.g.,X-Powered-By: PHP/8.1.2), reducing reconnaissance opportunities for attackers.display_errors = Off: In production environments, error messages should not be displayed to end-users. This can leak sensitive information about the application’s internal workings. Errors should be logged instead.log_errors = On: Ensures that errors are logged to a file for later analysis.error_log = /var/log/php/php_errors.log: Specifies the path for error logging. Ensure this directory is not web-accessible and has appropriate file permissions.session.cookie_httponly = 1: Makes session cookies inaccessible to client-side scripts (JavaScript), mitigating cross-site scripting (XSS) attacks that aim to steal session IDs.session.cookie_secure = 1: Ensures that session cookies are only sent over HTTPS connections, preventing them from being intercepted on unencrypted networks.session.use_strict_mode = 1: Prevents PHP from accepting uninitialized session IDs, mitigating session fixation vulnerabilities.disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source: A critical directive. This list should contain any PHP functions that are not strictly necessary for the application’s operation, especially those that can execute system commands or access the filesystem in potentially dangerous ways. The list above is a common starting point; tailor it to your application’s needs.allow_url_fopen = Off: Disables the ability for PHP functions likefopen(),file_get_contents(), etc., to fetch data from remote URLs. This prevents many types of SSRF (Server-Side Request Forgery) attacks.allow_url_include = Off: Disables the ability to include remote files viainclude(),require(), etc. This is a crucial defense against remote file inclusion (RFI) vulnerabilities.
An example snippet of a hardened php.ini section:
[PHP] expose_php = Off display_errors = Off log_errors = On error_log = /var/log/php/php_errors.log session.cookie_httponly = 1 session.cookie_secure = 1 session.use_strict_mode = 1 disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source allow_url_fopen = Off allow_url_include = Off
Remember to restart the PHP-FPM service after applying these changes.
Web Server Configuration (Nginx Example)
The web server plays a crucial role in fronting the PHP application. For Nginx, several configurations are vital for security.
1. Restrict Access to Sensitive Files: Ensure that configuration files, source code directories (if not directly served), and log files are not accessible via HTTP requests.
location ~ /\.ht {
deny all;
}
location ~* \.(inc|bak|config|sql|log)$ {
deny all;
}
2. PHP File Handling: Configure Nginx to pass PHP files only to the PHP-FPM process and prevent direct execution of PHP files if they are not intended to be served as scripts (e.g., configuration files that might be accidentally placed in the webroot).
location ~ \.php$ {
include snippets/fastcgi-php.conf;
# With php-fpm (or other unix sockets):
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Adjust path and version
# Or using TCP/IP:
# fastcgi_pass 127.0.0.1:9000;
# Prevent direct execution of non-script PHP files
try_files $uri =404;
}
3. Security Headers: Implement security headers to instruct browsers on how to behave securely.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; # Example, adjust as needed # Content Security Policy (CSP) is highly recommended but complex. Start simple and iterate. # add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none';" always;
4. Rate Limiting and Access Control: Protect against brute-force attacks and excessive requests.
# Example: Limit requests to the login page
location /login {
limit_req zone=myloginburst burst=5 nodelay;
# ... other login-related configurations
}
# Define the rate limiting zone
# http block
http {
limit_req_zone $binary_remote_addr zone=myloginburst:10m rate=5r/min;
# ... other http configurations
}
After modifying Nginx configuration files (typically in /etc/nginx/sites-available/ or /etc/nginx/nginx.conf), always test the configuration and reload Nginx:
sudo nginx -t sudo systemctl reload nginx
Database Security (MySQL/MariaDB Example)
Securing the database backend is paramount. This involves user privileges, network access, and data encryption.
1. Principle of Least Privilege for Database Users: The PHP application should connect to the database using a dedicated user account with only the necessary privileges. Avoid using the root user.
-- Connect as root or a privileged user mysql -u root -p -- Create a new user for the application CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'secure_password_here'; -- Grant only necessary privileges on the application's database GRANT SELECT, INSERT, UPDATE, DELETE ON app_database.* TO 'app_user'@'localhost'; -- If the application needs to create tables or alter schema (less common for production apps) -- GRANT CREATE, ALTER, DROP ON app_database.* TO 'app_user'@'localhost'; -- Apply the changes FLUSH PRIVILEGES; -- Exit MySQL client EXIT;
2. Restrict Network Access: Configure the database server to listen only on localhost if the web server and database server are on the same machine. If they are on different machines, restrict access to specific IP addresses.
# In MySQL/MariaDB configuration file (e.g., /etc/mysql/my.cnf or /etc/mysql/mariadb.conf.d/50-server.cnf) [mysqld] bind-address = 127.0.0.1 # Or the specific IP of the web server if remote
After changing the configuration, restart the MySQL/MariaDB service:
sudo systemctl restart mysql # Or mariadb
3. Secure Connection (SSL/TLS): Ensure PHP applications connect to the database using SSL/TLS encryption. This requires configuring the database server for SSL and updating the PHP database connection parameters.
4. Regular Backups and Auditing: Implement a robust backup strategy and regularly audit database logs for suspicious activity.
Application-Level Security Checks
Beyond server and PHP configuration, the application code itself must be secure.
1. Input Validation and Sanitization: All user-supplied input (GET, POST, COOKIE, headers, file uploads) must be rigorously validated and sanitized to prevent injection attacks (SQLi, XSS, command injection).
// Example: Basic validation for a username
function sanitize_username($input) {
// Remove characters that are not alphanumeric, underscore, or hyphen
$sanitized = preg_replace('/[^a-zA-Z0-9_-]/', '', $input);
// Trim whitespace
return trim($sanitized);
}
$username = sanitize_username($_POST['username']);
// Example: Using prepared statements for SQL queries (essential!)
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->bindParam(':username', $username);
$stmt->execute();
$user = $stmt->fetch();
2. Output Encoding: When displaying user-provided data in HTML, ensure it is properly encoded to prevent XSS.
// Example: Displaying user-generated content echo htmlspecialchars($user_comment, ENT_QUOTES, 'UTF-8');
3. Secure Session Management: Use strong, random session IDs and regenerate them upon login or privilege change. Store session data securely.
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Regenerate session ID on login
if (!isset($_SESSION['user_id'])) {
session_regenerate_id(true); // true deletes the old session file
$_SESSION['user_id'] = $user_id;
$_SESSION['logged_in'] = time();
}
// Check for session timeout
$timeout_duration = 1800; // 30 minutes
if (isset($_SESSION['logged_in']) && (time() - $_SESSION['logged_in'] > $timeout_duration)) {
session_unset();
session_destroy();
// Redirect to login page
}
4. File Upload Security: Validate file types, sizes, and scan for malware. Store uploaded files outside the webroot and use non-executable file names.
// Basic example, real-world requires more checks
$allowed_types = ['image/jpeg', 'image/png'];
$max_size = 5 * 1024 * 1024; // 5MB
if (isset($_FILES['user_file']) && $_FILES['user_file']['error'] === UPLOAD_ERR_OK) {
$file_tmp_path = $_FILES['user_file']['tmp_name'];
$file_name = $_FILES['user_file']['name'];
$file_type = $_FILES['user_file']['type'];
$file_size = $_FILES['user_file']['size'];
// Validate type and size
if (!in_array($file_type, $allowed_types)) {
// Error: Invalid file type
}
if ($file_size > $max_size) {
// Error: File too large
}
// Generate a secure, unique filename
$new_file_name = uniqid('upload_', true) . '.' . pathinfo($file_name, PATHINFO_EXTENSION);
$upload_dir = '/var/www/uploads/'; // Ensure this directory is NOT web-accessible
if (move_uploaded_file($file_tmp_path, $upload_dir . $new_file_name)) {
// File uploaded successfully
} else {
// Error moving file
}
}
5. Dependency Management: Use tools like Composer to manage third-party libraries. Regularly update dependencies and use tools like composer audit to check for known vulnerabilities.
composer update composer audit
System-Level Security and Monitoring
Auditing extends to the underlying Linode infrastructure and operational practices.
1. Firewall Configuration: Ensure a firewall (e.g., ufw) is active and configured to allow only necessary ports (e.g., 22 for SSH, 80 for HTTP, 443 for HTTPS).
sudo ufw status verbose sudo ufw allow ssh sudo ufw allow http sudo ufw allow https sudo ufw enable
2. SSH Security: Disable root login, use key-based authentication, and consider changing the default SSH port (though this is more obscurity than true security). Regularly review SSH logs.
# In /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes # Port 2222 # Optional: change port
Restart SSH service after changes:
sudo systemctl restart sshd
3. Log Monitoring: Centralize and monitor logs from PHP, the web server, and the system. Tools like fail2ban can automatically block IPs exhibiting malicious behavior.
# Example: Configure fail2ban to monitor Nginx access/error logs for 4xx/5xx errors # In /etc/fail2ban/jail.local [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/*error.log maxretry = 6 findtime = 600 # 10 minutes bantime = 3600 # 1 hour # Ensure you have the corresponding filter in /etc/fail2ban/filter.d/nginx-http-auth.conf # Example filter content: # [Definition] # failregex = ^-.*"(GET|POST).*HTTP/1\.[01]"\s[45]\d{2} # ignoreregex =
4. Regular Updates: Keep the operating system, web server, PHP, and all installed packages up-to-date with security patches.
sudo apt update && sudo apt upgrade -y
5. Access Control for Sensitive Files: Ensure that sensitive files (e.g., .env files, private keys, configuration files) have strict file permissions and are not readable by the web server user.
# Example: Set permissions for a .env file chmod 600 .env chown root:root .env # Or a dedicated deployment user, NOT www-data
php -v
For a more detailed breakdown, including loaded extensions, you can create a simple PHP info file. Ensure this file is removed immediately after auditing.
<?php phpinfo(); ?>
Access this file via your web browser (e.g., http://your_domain.com/info.php). Look for the “PHP Version” section and the “Loaded Extensions” list. Auditors should verify that the version is within the supported lifecycle (e.g., PHP 8.1 or later, as of late 2023). Any extensions not explicitly required by the application should be disabled. This is typically done in the php.ini file. The location of this file can also be found in the phpinfo() output under “Loaded Configuration File”.
To disable an extension, comment out the corresponding extension=... line in php.ini. For example, to disable the redis extension:
; extension=redis
After modifying php.ini, the web server (e.g., Nginx with PHP-FPM) or the PHP CLI process must be restarted for changes to take effect.
sudo systemctl restart php8.1-fpm # Adjust version as needed sudo systemctl restart nginx # If Nginx is used as the web server
PHP Configuration Hardening (php.ini)
Beyond version and extensions, the php.ini file offers numerous directives that can significantly enhance security. Auditors should scrutinize these settings.
Key directives to review and harden include:
expose_php = Off: Prevents the PHP version from being revealed in HTTP headers (e.g.,X-Powered-By: PHP/8.1.2), reducing reconnaissance opportunities for attackers.display_errors = Off: In production environments, error messages should not be displayed to end-users. This can leak sensitive information about the application’s internal workings. Errors should be logged instead.log_errors = On: Ensures that errors are logged to a file for later analysis.error_log = /var/log/php/php_errors.log: Specifies the path for error logging. Ensure this directory is not web-accessible and has appropriate file permissions.session.cookie_httponly = 1: Makes session cookies inaccessible to client-side scripts (JavaScript), mitigating cross-site scripting (XSS) attacks that aim to steal session IDs.session.cookie_secure = 1: Ensures that session cookies are only sent over HTTPS connections, preventing them from being intercepted on unencrypted networks.session.use_strict_mode = 1: Prevents PHP from accepting uninitialized session IDs, mitigating session fixation vulnerabilities.disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source: A critical directive. This list should contain any PHP functions that are not strictly necessary for the application’s operation, especially those that can execute system commands or access the filesystem in potentially dangerous ways. The list above is a common starting point; tailor it to your application’s needs.allow_url_fopen = Off: Disables the ability for PHP functions likefopen(),file_get_contents(), etc., to fetch data from remote URLs. This prevents many types of SSRF (Server-Side Request Forgery) attacks.allow_url_include = Off: Disables the ability to include remote files viainclude(),require(), etc. This is a crucial defense against remote file inclusion (RFI) vulnerabilities.
An example snippet of a hardened php.ini section:
[PHP] expose_php = Off display_errors = Off log_errors = On error_log = /var/log/php/php_errors.log session.cookie_httponly = 1 session.cookie_secure = 1 session.use_strict_mode = 1 disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source allow_url_fopen = Off allow_url_include = Off
Remember to restart the PHP-FPM service after applying these changes.
Web Server Configuration (Nginx Example)
The web server plays a crucial role in fronting the PHP application. For Nginx, several configurations are vital for security.
1. Restrict Access to Sensitive Files: Ensure that configuration files, source code directories (if not directly served), and log files are not accessible via HTTP requests.
location ~ /\.ht {
deny all;
}
location ~* \.(inc|bak|config|sql|log)$ {
deny all;
}
2. PHP File Handling: Configure Nginx to pass PHP files only to the PHP-FPM process and prevent direct execution of PHP files if they are not intended to be served as scripts (e.g., configuration files that might be accidentally placed in the webroot).
location ~ \.php$ {
include snippets/fastcgi-php.conf;
# With php-fpm (or other unix sockets):
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Adjust path and version
# Or using TCP/IP:
# fastcgi_pass 127.0.0.1:9000;
# Prevent direct execution of non-script PHP files
try_files $uri =404;
}
3. Security Headers: Implement security headers to instruct browsers on how to behave securely.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; # Example, adjust as needed # Content Security Policy (CSP) is highly recommended but complex. Start simple and iterate. # add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none';" always;
4. Rate Limiting and Access Control: Protect against brute-force attacks and excessive requests.
# Example: Limit requests to the login page
location /login {
limit_req zone=myloginburst burst=5 nodelay;
# ... other login-related configurations
}
# Define the rate limiting zone
# http block
http {
limit_req_zone $binary_remote_addr zone=myloginburst:10m rate=5r/min;
# ... other http configurations
}
After modifying Nginx configuration files (typically in /etc/nginx/sites-available/ or /etc/nginx/nginx.conf), always test the configuration and reload Nginx:
sudo nginx -t sudo systemctl reload nginx
Database Security (MySQL/MariaDB Example)
Securing the database backend is paramount. This involves user privileges, network access, and data encryption.
1. Principle of Least Privilege for Database Users: The PHP application should connect to the database using a dedicated user account with only the necessary privileges. Avoid using the root user.
-- Connect as root or a privileged user mysql -u root -p -- Create a new user for the application CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'secure_password_here'; -- Grant only necessary privileges on the application's database GRANT SELECT, INSERT, UPDATE, DELETE ON app_database.* TO 'app_user'@'localhost'; -- If the application needs to create tables or alter schema (less common for production apps) -- GRANT CREATE, ALTER, DROP ON app_database.* TO 'app_user'@'localhost'; -- Apply the changes FLUSH PRIVILEGES; -- Exit MySQL client EXIT;
2. Restrict Network Access: Configure the database server to listen only on localhost if the web server and database server are on the same machine. If they are on different machines, restrict access to specific IP addresses.
# In MySQL/MariaDB configuration file (e.g., /etc/mysql/my.cnf or /etc/mysql/mariadb.conf.d/50-server.cnf) [mysqld] bind-address = 127.0.0.1 # Or the specific IP of the web server if remote
After changing the configuration, restart the MySQL/MariaDB service:
sudo systemctl restart mysql # Or mariadb
3. Secure Connection (SSL/TLS): Ensure PHP applications connect to the database using SSL/TLS encryption. This requires configuring the database server for SSL and updating the PHP database connection parameters.
4. Regular Backups and Auditing: Implement a robust backup strategy and regularly audit database logs for suspicious activity.
Application-Level Security Checks
Beyond server and PHP configuration, the application code itself must be secure.
1. Input Validation and Sanitization: All user-supplied input (GET, POST, COOKIE, headers, file uploads) must be rigorously validated and sanitized to prevent injection attacks (SQLi, XSS, command injection).
// Example: Basic validation for a username
function sanitize_username($input) {
// Remove characters that are not alphanumeric, underscore, or hyphen
$sanitized = preg_replace('/[^a-zA-Z0-9_-]/', '', $input);
// Trim whitespace
return trim($sanitized);
}
$username = sanitize_username($_POST['username']);
// Example: Using prepared statements for SQL queries (essential!)
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->bindParam(':username', $username);
$stmt->execute();
$user = $stmt->fetch();
2. Output Encoding: When displaying user-provided data in HTML, ensure it is properly encoded to prevent XSS.
// Example: Displaying user-generated content echo htmlspecialchars($user_comment, ENT_QUOTES, 'UTF-8');
3. Secure Session Management: Use strong, random session IDs and regenerate them upon login or privilege change. Store session data securely.
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Regenerate session ID on login
if (!isset($_SESSION['user_id'])) {
session_regenerate_id(true); // true deletes the old session file
$_SESSION['user_id'] = $user_id;
$_SESSION['logged_in'] = time();
}
// Check for session timeout
$timeout_duration = 1800; // 30 minutes
if (isset($_SESSION['logged_in']) && (time() - $_SESSION['logged_in'] > $timeout_duration)) {
session_unset();
session_destroy();
// Redirect to login page
}
4. File Upload Security: Validate file types, sizes, and scan for malware. Store uploaded files outside the webroot and use non-executable file names.
// Basic example, real-world requires more checks
$allowed_types = ['image/jpeg', 'image/png'];
$max_size = 5 * 1024 * 1024; // 5MB
if (isset($_FILES['user_file']) && $_FILES['user_file']['error'] === UPLOAD_ERR_OK) {
$file_tmp_path = $_FILES['user_file']['tmp_name'];
$file_name = $_FILES['user_file']['name'];
$file_type = $_FILES['user_file']['type'];
$file_size = $_FILES['user_file']['size'];
// Validate type and size
if (!in_array($file_type, $allowed_types)) {
// Error: Invalid file type
}
if ($file_size > $max_size) {
// Error: File too large
}
// Generate a secure, unique filename
$new_file_name = uniqid('upload_', true) . '.' . pathinfo($file_name, PATHINFO_EXTENSION);
$upload_dir = '/var/www/uploads/'; // Ensure this directory is NOT web-accessible
if (move_uploaded_file($file_tmp_path, $upload_dir . $new_file_name)) {
// File uploaded successfully
} else {
// Error moving file
}
}
5. Dependency Management: Use tools like Composer to manage third-party libraries. Regularly update dependencies and use tools like composer audit to check for known vulnerabilities.
composer update composer audit
System-Level Security and Monitoring
Auditing extends to the underlying Linode infrastructure and operational practices.
1. Firewall Configuration: Ensure a firewall (e.g., ufw) is active and configured to allow only necessary ports (e.g., 22 for SSH, 80 for HTTP, 443 for HTTPS).
sudo ufw status verbose sudo ufw allow ssh sudo ufw allow http sudo ufw allow https sudo ufw enable
2. SSH Security: Disable root login, use key-based authentication, and consider changing the default SSH port (though this is more obscurity than true security). Regularly review SSH logs.
# In /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes # Port 2222 # Optional: change port
Restart SSH service after changes:
sudo systemctl restart sshd
3. Log Monitoring: Centralize and monitor logs from PHP, the web server, and the system. Tools like fail2ban can automatically block IPs exhibiting malicious behavior.
# Example: Configure fail2ban to monitor Nginx access/error logs for 4xx/5xx errors # In /etc/fail2ban/jail.local [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/*error.log maxretry = 6 findtime = 600 # 10 minutes bantime = 3600 # 1 hour # Ensure you have the corresponding filter in /etc/fail2ban/filter.d/nginx-http-auth.conf # Example filter content: # [Definition] # failregex = ^-.*"(GET|POST).*HTTP/1\.[01]"\s[45]\d{2} # ignoreregex =
4. Regular Updates: Keep the operating system, web server, PHP, and all installed packages up-to-date with security patches.
sudo apt update && sudo apt upgrade -y
5. Access Control for Sensitive Files: Ensure that sensitive files (e.g., .env files, private keys, configuration files) have strict file permissions and are not readable by the web server user.
# Example: Set permissions for a .env file chmod 600 .env chown root:root .env # Or a dedicated deployment user, NOT www-data
PHP Version and Extension Management
A fundamental aspect of securing any PHP backend is ensuring you’re running a supported and actively patched version. Outdated PHP versions are a significant attack vector due to known, unpatched vulnerabilities. Similarly, unnecessary or insecure extensions must be disabled or removed.
On a Linode server, you can check your current PHP version using the command line:
php -v
For a more detailed breakdown, including loaded extensions, you can create a simple PHP info file. Ensure this file is removed immediately after auditing.
<?php phpinfo(); ?>
Access this file via your web browser (e.g., http://your_domain.com/info.php). Look for the “PHP Version” section and the “Loaded Extensions” list. Auditors should verify that the version is within the supported lifecycle (e.g., PHP 8.1 or later, as of late 2023). Any extensions not explicitly required by the application should be disabled. This is typically done in the php.ini file. The location of this file can also be found in the phpinfo() output under “Loaded Configuration File”.
To disable an extension, comment out the corresponding extension=... line in php.ini. For example, to disable the redis extension:
; extension=redis
After modifying php.ini, the web server (e.g., Nginx with PHP-FPM) or the PHP CLI process must be restarted for changes to take effect.
sudo systemctl restart php8.1-fpm # Adjust version as needed sudo systemctl restart nginx # If Nginx is used as the web server
PHP Configuration Hardening (php.ini)
Beyond version and extensions, the php.ini file offers numerous directives that can significantly enhance security. Auditors should scrutinize these settings.
Key directives to review and harden include:
expose_php = Off: Prevents the PHP version from being revealed in HTTP headers (e.g.,X-Powered-By: PHP/8.1.2), reducing reconnaissance opportunities for attackers.display_errors = Off: In production environments, error messages should not be displayed to end-users. This can leak sensitive information about the application’s internal workings. Errors should be logged instead.log_errors = On: Ensures that errors are logged to a file for later analysis.error_log = /var/log/php/php_errors.log: Specifies the path for error logging. Ensure this directory is not web-accessible and has appropriate file permissions.session.cookie_httponly = 1: Makes session cookies inaccessible to client-side scripts (JavaScript), mitigating cross-site scripting (XSS) attacks that aim to steal session IDs.session.cookie_secure = 1: Ensures that session cookies are only sent over HTTPS connections, preventing them from being intercepted on unencrypted networks.session.use_strict_mode = 1: Prevents PHP from accepting uninitialized session IDs, mitigating session fixation vulnerabilities.disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source: A critical directive. This list should contain any PHP functions that are not strictly necessary for the application’s operation, especially those that can execute system commands or access the filesystem in potentially dangerous ways. The list above is a common starting point; tailor it to your application’s needs.allow_url_fopen = Off: Disables the ability for PHP functions likefopen(),file_get_contents(), etc., to fetch data from remote URLs. This prevents many types of SSRF (Server-Side Request Forgery) attacks.allow_url_include = Off: Disables the ability to include remote files viainclude(),require(), etc. This is a crucial defense against remote file inclusion (RFI) vulnerabilities.
An example snippet of a hardened php.ini section:
[PHP] expose_php = Off display_errors = Off log_errors = On error_log = /var/log/php/php_errors.log session.cookie_httponly = 1 session.cookie_secure = 1 session.use_strict_mode = 1 disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source allow_url_fopen = Off allow_url_include = Off
Remember to restart the PHP-FPM service after applying these changes.
Web Server Configuration (Nginx Example)
The web server plays a crucial role in fronting the PHP application. For Nginx, several configurations are vital for security.
1. Restrict Access to Sensitive Files: Ensure that configuration files, source code directories (if not directly served), and log files are not accessible via HTTP requests.
location ~ /\.ht {
deny all;
}
location ~* \.(inc|bak|config|sql|log)$ {
deny all;
}
2. PHP File Handling: Configure Nginx to pass PHP files only to the PHP-FPM process and prevent direct execution of PHP files if they are not intended to be served as scripts (e.g., configuration files that might be accidentally placed in the webroot).
location ~ \.php$ {
include snippets/fastcgi-php.conf;
# With php-fpm (or other unix sockets):
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Adjust path and version
# Or using TCP/IP:
# fastcgi_pass 127.0.0.1:9000;
# Prevent direct execution of non-script PHP files
try_files $uri =404;
}
3. Security Headers: Implement security headers to instruct browsers on how to behave securely.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; # Example, adjust as needed # Content Security Policy (CSP) is highly recommended but complex. Start simple and iterate. # add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none';" always;
4. Rate Limiting and Access Control: Protect against brute-force attacks and excessive requests.
# Example: Limit requests to the login page
location /login {
limit_req zone=myloginburst burst=5 nodelay;
# ... other login-related configurations
}
# Define the rate limiting zone
# http block
http {
limit_req_zone $binary_remote_addr zone=myloginburst:10m rate=5r/min;
# ... other http configurations
}
After modifying Nginx configuration files (typically in /etc/nginx/sites-available/ or /etc/nginx/nginx.conf), always test the configuration and reload Nginx:
sudo nginx -t sudo systemctl reload nginx
Database Security (MySQL/MariaDB Example)
Securing the database backend is paramount. This involves user privileges, network access, and data encryption.
1. Principle of Least Privilege for Database Users: The PHP application should connect to the database using a dedicated user account with only the necessary privileges. Avoid using the root user.
-- Connect as root or a privileged user mysql -u root -p -- Create a new user for the application CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'secure_password_here'; -- Grant only necessary privileges on the application's database GRANT SELECT, INSERT, UPDATE, DELETE ON app_database.* TO 'app_user'@'localhost'; -- If the application needs to create tables or alter schema (less common for production apps) -- GRANT CREATE, ALTER, DROP ON app_database.* TO 'app_user'@'localhost'; -- Apply the changes FLUSH PRIVILEGES; -- Exit MySQL client EXIT;
2. Restrict Network Access: Configure the database server to listen only on localhost if the web server and database server are on the same machine. If they are on different machines, restrict access to specific IP addresses.
# In MySQL/MariaDB configuration file (e.g., /etc/mysql/my.cnf or /etc/mysql/mariadb.conf.d/50-server.cnf) [mysqld] bind-address = 127.0.0.1 # Or the specific IP of the web server if remote
After changing the configuration, restart the MySQL/MariaDB service:
sudo systemctl restart mysql # Or mariadb
3. Secure Connection (SSL/TLS): Ensure PHP applications connect to the database using SSL/TLS encryption. This requires configuring the database server for SSL and updating the PHP database connection parameters.
4. Regular Backups and Auditing: Implement a robust backup strategy and regularly audit database logs for suspicious activity.
Application-Level Security Checks
Beyond server and PHP configuration, the application code itself must be secure.
1. Input Validation and Sanitization: All user-supplied input (GET, POST, COOKIE, headers, file uploads) must be rigorously validated and sanitized to prevent injection attacks (SQLi, XSS, command injection).
// Example: Basic validation for a username
function sanitize_username($input) {
// Remove characters that are not alphanumeric, underscore, or hyphen
$sanitized = preg_replace('/[^a-zA-Z0-9_-]/', '', $input);
// Trim whitespace
return trim($sanitized);
}
$username = sanitize_username($_POST['username']);
// Example: Using prepared statements for SQL queries (essential!)
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->bindParam(':username', $username);
$stmt->execute();
$user = $stmt->fetch();
2. Output Encoding: When displaying user-provided data in HTML, ensure it is properly encoded to prevent XSS.
// Example: Displaying user-generated content echo htmlspecialchars($user_comment, ENT_QUOTES, 'UTF-8');
3. Secure Session Management: Use strong, random session IDs and regenerate them upon login or privilege change. Store session data securely.
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Regenerate session ID on login
if (!isset($_SESSION['user_id'])) {
session_regenerate_id(true); // true deletes the old session file
$_SESSION['user_id'] = $user_id;
$_SESSION['logged_in'] = time();
}
// Check for session timeout
$timeout_duration = 1800; // 30 minutes
if (isset($_SESSION['logged_in']) && (time() - $_SESSION['logged_in'] > $timeout_duration)) {
session_unset();
session_destroy();
// Redirect to login page
}
4. File Upload Security: Validate file types, sizes, and scan for malware. Store uploaded files outside the webroot and use non-executable file names.
// Basic example, real-world requires more checks
$allowed_types = ['image/jpeg', 'image/png'];
$max_size = 5 * 1024 * 1024; // 5MB
if (isset($_FILES['user_file']) && $_FILES['user_file']['error'] === UPLOAD_ERR_OK) {
$file_tmp_path = $_FILES['user_file']['tmp_name'];
$file_name = $_FILES['user_file']['name'];
$file_type = $_FILES['user_file']['type'];
$file_size = $_FILES['user_file']['size'];
// Validate type and size
if (!in_array($file_type, $allowed_types)) {
// Error: Invalid file type
}
if ($file_size > $max_size) {
// Error: File too large
}
// Generate a secure, unique filename
$new_file_name = uniqid('upload_', true) . '.' . pathinfo($file_name, PATHINFO_EXTENSION);
$upload_dir = '/var/www/uploads/'; // Ensure this directory is NOT web-accessible
if (move_uploaded_file($file_tmp_path, $upload_dir . $new_file_name)) {
// File uploaded successfully
} else {
// Error moving file
}
}
5. Dependency Management: Use tools like Composer to manage third-party libraries. Regularly update dependencies and use tools like composer audit to check for known vulnerabilities.
composer update composer audit
System-Level Security and Monitoring
Auditing extends to the underlying Linode infrastructure and operational practices.
1. Firewall Configuration: Ensure a firewall (e.g., ufw) is active and configured to allow only necessary ports (e.g., 22 for SSH, 80 for HTTP, 443 for HTTPS).
sudo ufw status verbose sudo ufw allow ssh sudo ufw allow http sudo ufw allow https sudo ufw enable
2. SSH Security: Disable root login, use key-based authentication, and consider changing the default SSH port (though this is more obscurity than true security). Regularly review SSH logs.
# In /etc/ssh/sshd_config PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes # Port 2222 # Optional: change port
Restart SSH service after changes:
sudo systemctl restart sshd
3. Log Monitoring: Centralize and monitor logs from PHP, the web server, and the system. Tools like fail2ban can automatically block IPs exhibiting malicious behavior.
# Example: Configure fail2ban to monitor Nginx access/error logs for 4xx/5xx errors # In /etc/fail2ban/jail.local [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/*error.log maxretry = 6 findtime = 600 # 10 minutes bantime = 3600 # 1 hour # Ensure you have the corresponding filter in /etc/fail2ban/filter.d/nginx-http-auth.conf # Example filter content: # [Definition] # failregex = ^-.*"(GET|POST).*HTTP/1\.[01]"\s[45]\d{2} # ignoreregex =
4. Regular Updates: Keep the operating system, web server, PHP, and all installed packages up-to-date with security patches.
sudo apt update && sudo apt upgrade -y
5. Access Control for Sensitive Files: Ensure that sensitive files (e.g., .env files, private keys, configuration files) have strict file permissions and are not readable by the web server user.
# Example: Set permissions for a .env file chmod 600 .env chown root:root .env # Or a dedicated deployment user, NOT www-data