An Auditor’s Checklist for Securing Laravel Backends on OVH
Environment Hardening: OVH Instance & Laravel Deployment
Securing a Laravel backend deployed on OVH infrastructure requires a multi-layered approach, starting with the foundational server environment. This section outlines critical checks for an auditor, focusing on instance configuration and the initial deployment of the Laravel application.
Instance Security Group & Firewall Rules
OVH’s Public Cloud instances offer robust firewall capabilities. An auditor must verify that only necessary ports are exposed. For a typical Laravel application, this means:
- SSH (Port 22): Restricted to specific, trusted IP addresses or ranges. Avoid opening to 0.0.0.0/0.
- HTTP/HTTPS (Ports 80/443): Open to the public internet for web traffic.
- Database Ports (e.g., 3306 for MySQL): Strictly prohibited from public access. Access should be limited to localhost or specific internal network IPs if the database is on a separate, private instance.
- Other Services: Any other open ports must have a clear justification and be similarly restricted.
Verification Steps:
Access the OVH Control Panel, navigate to “Public Cloud” > “Network” > “Security Groups”. Review the rules applied to the instance’s security group. For command-line verification on the instance itself, check iptables or ufw status:
sudo iptables -L -n -v # or sudo ufw status verbose
SSH Access Control
SSH is the primary gateway to the server. Its security is paramount. Key areas to audit include:
- Password Authentication: Disabled. SSH key-based authentication is mandatory.
- Root Login: Disabled. All administrative tasks should be performed via a non-root user with
sudoprivileges. - PermitEmptyPasswords: Set to
noinsshd_config. - Protocol Version: Ensure only Protocol 2 is enabled.
- Port: Consider changing the default SSH port (22) to a non-standard port to reduce automated scan noise, though this is a minor obscurity measure.
Verification Steps:
Examine the SSH daemon configuration file:
sudo grep -v '^\s*#' /etc/ssh/sshd_config | grep -v '^\s*$'
Specifically look for:
PasswordAuthentication no PermitRootLogin no PubkeyAuthentication yes ChallengeResponseAuthentication no UsePAM no # If not using PAM for other reasons, otherwise ensure it's configured securely Protocol 2
After any changes, the SSH service must be reloaded:
sudo systemctl reload sshd
Laravel Deployment & Configuration
The Laravel application itself must be configured securely. This involves environment variables, file permissions, and framework-specific settings.
Environment Variables (.env) Security
The .env file contains sensitive credentials. It should never be committed to version control.
- File Permissions: The
.envfile should have restrictive permissions, readable only by the web server user (e.g.,www-data) or the deployment user. - APP_KEY: Must be set and unique. Regenerate if it’s the default.
- APP_DEBUG: Must be set to
falsein production. - Database Credentials: Ensure these are correct and not hardcoded elsewhere.
- External Service Keys: API keys for third-party services should be stored here and kept confidential.
Verification Steps:
Check the .env file permissions:
ls -l .env
Permissions should ideally be 600 or 640 if the web server user needs read access and is in the same group.
Inspect the .env file content (use caution and appropriate access controls):
cat .env
Verify the following lines:
APP_NAME=Your Secure App Name APP_ENV=production APP_KEY=base64:YOUR_GENERATED_APP_KEY_HERE= APP_DEBUG=false APP_URL=https://yourdomain.com DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=your_database DB_USERNAME=your_db_user DB_PASSWORD=your_db_password
File & Directory Permissions
Incorrect file permissions are a common vulnerability vector. The web server user (e.g., www-data for Apache/Nginx on Debian/Ubuntu) should only have write access where absolutely necessary.
storage/directory: Must be writable by the web server user.bootstrap/cache/directory: Must be writable by the web server user.- Other directories (e.g.,
public/,app/,config/,routes/): Should be owned by the deployment user and not writable by the web server user. - Individual PHP files: Should not be writable by the web server user.
Verification Steps:
Assuming the web server user is www-data and the deployment user is deployer:
# Check current permissions
ls -ld storage bootstrap/cache
ls -ld public app config routes
# Set correct ownership and permissions (example for a typical setup)
# Ensure the deployment user owns the project
sudo chown -R deployer:deployer /var/www/your-laravel-app
# Make storage and cache writable by the web server user
sudo chown -R www-data:www-data storage bootstrap/cache
sudo chmod -R 755 storage bootstrap/cache # Or 775 if group write is needed and managed
# Ensure other directories are not writable by the web server user
sudo chmod -R o-w,g-w storage bootstrap/cache # Remove group/other write permissions if not needed
# Ensure all PHP files are not writable by the web server user
find . -type f -name "*.php" -exec sudo chmod o-w,g-w {} \;
# Ensure the web server can read public assets
sudo chmod -R 755 public
Web Server Configuration (Nginx Example)
The web server acts as the first line of defense for the application. Secure Nginx configuration is critical.
HTTPS Enforcement & TLS Configuration
All traffic must be served over HTTPS. The TLS configuration should be robust.
- TLS Version: Use TLS 1.2 and 1.3. Disable older, vulnerable versions (SSLv3, TLSv1.0, TLSv1.1).
- Cipher Suites: Configure strong, modern cipher suites.
- HSTS (HTTP Strict Transport Security): Implement HSTS headers to force browsers to use HTTPS.
- OCSP Stapling: Enable for faster certificate validation.
- Certificate Management: Ensure certificates are valid, not expired, and managed via a robust process (e.g., Let’s Encrypt with automated renewal).
Verification Steps:
Examine the Nginx site configuration file (e.g., /etc/nginx/sites-available/your-laravel-app):
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
# Redirect HTTP to HTTPS
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
root /var/www/your-laravel-app/public;
index index.php index.html index.htm;
# SSL Configuration
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# Modern TLS Configuration (example, use tools like Mozilla SSL Config Generator for up-to-date recommendations)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off; # Consider security implications
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s; # Use your preferred DNS resolvers
resolver_timeout 5s;
# HSTS Header
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
# Ensure the socket path is correct for your PHP-FPM setup
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; # Adjust PHP version as needed
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# Deny access to sensitive files
location ~ /\.env { deny all; }
location ~ /\.git { deny all; }
location ~ /\.composer { deny all; }
location ~ vendor/ { deny all; } # If vendor is not in a secure location outside web root
location ~ storage/framework/sessions/ { deny all; } # If session files are exposed
location ~ storage/logs/ { deny all; } # If logs are exposed
}
After modifying Nginx configuration, test and reload:
sudo nginx -t sudo systemctl reload nginx
PHP-FPM Configuration
PHP-FPM configuration impacts performance and security. Key settings include:
cgi.fix_pathinfo: Set to0to prevent certain path traversal vulnerabilities.- User/Group: Ensure PHP-FPM runs as a non-privileged user (e.g.,
www-data). - Socket vs. TCP: Using a Unix socket is generally more performant and secure than a TCP port for local communication between Nginx and PHP-FPM.
- Resource Limits: Configure appropriate memory limits and execution times.
Verification Steps:
Locate the PHP-FPM pool configuration file (e.g., /etc/php/8.1/fpm/pool.d/www.conf):
[www] user = www-data group = www-data listen = /var/run/php/php8.1-fpm.sock # Or a TCP port if used listen.owner = www-data listen.group = www-data listen.mode = 0660 ; Other settings like pm.max_children, pm.start_servers etc. should be tuned for performance. ; Security settings cgi.fix_pathinfo = 0
Reload PHP-FPM after changes:
sudo systemctl reload php8.1-fpm # Adjust version as needed
Database Security
Protecting the application’s data store is critical. This section covers database access and configuration.
MySQL/MariaDB Access Control
Database credentials should be managed with extreme care.
- Remote Access: Disabled. The database server should only listen on
127.0.0.1if co-located with the web server, or on a private network interface if on a separate instance. - User Privileges: The Laravel application user should have the minimum necessary privileges (e.g.,
SELECT, INSERT, UPDATE, DELETEon specific tables, notALL PRIVILEGES). - Root User: The MySQL root user should have a strong, unique password and its access restricted.
- Password Hashing: Ensure strong password hashing algorithms are used for application users stored in the database (Laravel’s default Eloquent authentication handles this well).
Verification Steps:
Check the MySQL configuration file (e.g., /etc/mysql/mysql.conf.d/mysqld.cnf or /etc/my.cnf):
[mysqld] bind-address = 127.0.0.1 # Or bind-address = private_ip_address if on a private network
Log in to MySQL and check user privileges:
sudo mysql -u root -p SHOW GRANTS FOR 'laravel_user'@'localhost';
The output should show specific grants, not ALL PRIVILEGES. For example:
GRANT SELECT, INSERT, UPDATE, DELETE ON `your_database`.* TO 'laravel_user'@'localhost'; FLUSH PRIVILEGES;
Database Backups
Regular, secure, and tested database backups are a non-negotiable compliance requirement.
- Frequency: Daily or more frequent, depending on data change rate.
- Retention Policy: Define how long backups are kept.
- Storage: Store backups securely, ideally off-site or in a separate OVH storage service (e.g., Object Storage).
- Encryption: Encrypt sensitive backup data.
- Testing: Periodically test backup restoration.
Verification Steps:
Review the backup scripts and scheduler (e.g., cron jobs). Example backup script snippet:
#!/bin/bash
DB_USER="your_db_user"
DB_PASS="your_db_password"
DB_NAME="your_database"
BACKUP_DIR="/path/to/db_backups"
DATE=$(date +"%Y%m%d_%H%M%S")
FILENAME="${DB_NAME}_${DATE}.sql.gz"
mkdir -p ${BACKUP_DIR}
mysqldump -u ${DB_USER} -p${DB_PASS} ${DB_NAME} | gzip > ${BACKUP_DIR}/${FILENAME}
# Optional: Encrypt the backup
# openssl enc -aes-256-cbc -salt -in ${BACKUP_DIR}/${FILENAME} -out ${BACKUP_DIR}/${FILENAME}.enc -pass pass:"your_encryption_password"
# rm ${BACKUP_DIR}/${FILENAME} # Remove unencrypted file
# Optional: Upload to cloud storage (e.g., AWS S3, OVH Object Storage)
# aws s3 cp ${BACKUP_DIR}/${FILENAME}.enc s3://your-backup-bucket/
# Optional: Clean up old backups
find ${BACKUP_DIR} -type f -name "*.sql.gz*" -mtime +7 -delete
echo "Database backup completed: ${BACKUP_DIR}/${FILENAME}"
Check cron jobs:
crontab -l
Laravel Application Security Best Practices
Beyond infrastructure, the Laravel application itself must adhere to security best practices.
Dependency Management & Updates
Outdated dependencies are a major source of vulnerabilities.
- Composer: Regularly run
composer updateto pull in the latest stable versions and security patches. - Security Audits: Use tools like
composer audit(orsymfony/security-checkerfor older versions) to identify known vulnerabilities in dependencies. - Lock File: Ensure
composer.lockis committed to version control to guarantee consistent dependency versions across environments.
Verification Steps:
# Check for vulnerabilities composer audit # Ensure lock file is up-to-date composer update --no-plugins --no-scripts # Check if composer.lock needs updating
Input Validation & Output Encoding
Preventing common web vulnerabilities like XSS and SQL Injection.
- Laravel Validation: Utilize Laravel’s built-in validation rules extensively for all user-provided input.
- Eloquent ORM: Use Eloquent for database interactions to automatically handle SQL injection prevention. Avoid raw SQL queries where possible.
- Blade Templating: Blade automatically escapes output by default (
{{ $variable }}). Use{!! $variable !!}only when absolutely necessary and after sanitizing the output. - CSRF Protection: Laravel’s built-in CSRF protection middleware should be enabled for all state-changing requests.
Verification Steps:
Review controller validation logic:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
public function store(Request $request)
{
$validatedData = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|min:8|confirmed',
// Other fields...
]);
// Use validated data to create user
// $user = User::create($validatedData);
// Example of potentially unsafe output if not using Blade's auto-escaping
// echo "Welcome, " . htmlspecialchars($request->input('name'), ENT_QUOTES, 'UTF-8'); // Use this if not in Blade
}
}
Check for CSRF token usage in forms:
<form method="POST" action="/users">
@csrf // This generates the hidden token field
<!-- Form fields -->
<button type="submit">Submit</button>
</form>
Logging & Monitoring
Comprehensive logging and monitoring are essential for detecting and responding to security incidents.
- Laravel Logs: Ensure logging is enabled in production (
APP_DEBUG=falseimplies logging is active). Configure log rotation to prevent disk space exhaustion. - Security Events: Log critical security events such as failed login attempts, authorization failures, and significant data modifications.
- Centralized Logging: Consider sending logs to a centralized logging system (e.g., ELK stack, Graylog, or a cloud-based solution) for easier analysis and retention.
- Monitoring: Implement application performance monitoring (APM) and server health checks. Set up alerts for suspicious activities or performance degradation.
Verification Steps:
Check Laravel’s logging configuration (config/logging.php) and ensure it’s set to a suitable driver for production (e.g., daily or single with log rotation handled by systemd/cron). Check the .env file for LOG_CHANNEL.
# Check log file rotation (e.g., using logrotate) sudo cat /etc/logrotate.d/laravel # Or similar configuration file
Example logrotate configuration for Laravel logs:
/var/www/your-laravel-app/storage/logs/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
create 0640 www-data adm
sharedscripts
postrotate
# Reload php-fpm or other services if needed
# systemctl reload php8.1-fpm
endscript
}
OVH Specific Considerations
Leveraging OVH’s platform features can enhance security.
OVH Firewall & DDoS Protection
OVH provides network-level security features that should be utilized.
- Network Firewall: Configure OVH’s network firewall rules in the control panel to complement instance-level firewalls.
- DDoS Protection: Ensure OVH’s automatic DDoS protection is enabled and configured appropriately for your service type. Understand the different protection modes (Standard, Advanced).
- WAF (Web Application Firewall): If available and applicable, consider deploying OVH’s WAF service for an additional layer of protection against common web attacks.
Verification Steps:
Navigate to the OVH Control Panel: “Public Cloud” > “Network” > “Firewall” and “Network” > “DDoS Protection”. Review the active rules and protection settings.
Instance Hardening & Patching
Keeping the underlying operating system and installed packages up-to-date is fundamental.
- OS Updates: Regularly apply security patches to the operating system (e.g., Ubuntu, CentOS). Automate this process where feasible, but with careful testing.
- Package Management: Use the OS package manager (
apt,yum) to install and manage software. Avoid manual installations from untrusted sources. - Kernel Live Patching: Explore OVH’s offerings or OS-level solutions for live kernel patching to minimize downtime during critical security updates.
Verification Steps:
# For Debian/Ubuntu sudo apt update && sudo apt list --upgradable sudo apt upgrade -y # Use with caution, or schedule for maintenance windows # For CentOS/RHEL sudo yum check-update sudo yum update -y # Use with caution
An auditor should verify the patch management policy and review logs for recent updates.