An Auditor’s Checklist for Securing Perl Backends on OVH
Perl Backend Security Audit: OVH Environment Specifics
This document outlines a rigorous audit checklist for Perl-based backend applications deployed on OVH infrastructure. The focus is on identifying and mitigating common security vulnerabilities, with specific considerations for the OVH hosting environment. This is not a beginner’s guide; it assumes a working knowledge of Perl, web server configurations (Apache/Nginx), and basic system administration on Linux-based systems.
1. Input Validation and Sanitization
Inadequate input validation is a primary vector for attacks like SQL injection, Cross-Site Scripting (XSS), and command injection. All external inputs, including HTTP request parameters (GET, POST, COOKIE), file uploads, and data from external APIs, must be strictly validated and sanitized.
1.1. Parameter Handling
Use robust libraries for parameter parsing and validation. Avoid manual parsing of CGI variables where possible. For common web frameworks, leverage their built-in security features.
1.2. SQL Injection Prevention
Always use parameterized queries or prepared statements. Never construct SQL queries by directly interpolating user-supplied data.
use DBI;
my $dbh = DBI->connect("dbi:mysql:database=mydb;host=localhost", "user", "password", { RaiseError => 1 });
# Vulnerable example (DO NOT USE)
my $user_id = $ENV{QUERY_STRING};
my $sql_vulnerable = "SELECT * FROM users WHERE id = $user_id";
my $sth_vulnerable = $dbh->prepare($sql_vulnerable);
$sth_vulnerable->execute();
# Secure example using placeholders
my $user_id_secure = param('user_id'); # Assuming CGI::Simple or similar
my $sql_secure = "SELECT * FROM users WHERE id = ?";
my $sth_secure = $dbh->prepare($sql_secure);
$sth_secure->execute($user_id_secure);
my @row = $sth_secure->fetchrow_array();
1.3. Cross-Site Scripting (XSS) Prevention
Sanitize all output that is rendered in an HTML context. Use libraries that provide context-aware escaping.
use HTML::Entities;
my $user_comment = param('comment');
# Vulnerable example (DO NOT USE)
print "<div>User Comment: $user_comment</div>";
# Secure example
my $sanitized_comment = HTML::Entities::encode_numeric($user_comment);
print "<div>User Comment: $sanitized_comment</div>";
# For frameworks like Mojolicious, use built-in helpers
# print $c->escape_html($user_comment);
1.4. Command Injection Prevention
Avoid executing external commands with user-supplied input. If absolutely necessary, use strict whitelisting of allowed commands and arguments, and carefully escape all input.
use IPC::System::Simple qw(capture);
my $filename = param('file');
# Vulnerable example (DO NOT USE)
# my $output = capture("ls -l $filename");
# Secure example (if absolutely necessary, and with extreme caution)
# Whitelist allowed filenames or patterns
my %allowed_files = ('report.txt' => 1, 'data.csv' => 1);
unless (exists $allowed_files{$filename}) {
die "Invalid filename provided.";
}
my $safe_filename = quotemeta($filename); # Basic quoting, not foolproof for all cases
my $output = capture("ls -l $safe_filename");
2. Authentication and Session Management
Secure authentication mechanisms and robust session management are critical. Weaknesses here can lead to account compromise and unauthorized access.
2.1. Password Storage
Passwords must never be stored in plain text. Use strong, modern hashing algorithms with salts. The `Crypt::PasswdHash` or `Password::Util` modules are recommended.
use Crypt::PasswdHash qw(hash_password verify_password);
my $password = "user_supplied_password";
my $salt = Crypt::PasswdHash::generate_salt(); # Or use a fixed, strong salt if managed carefully
my $hashed_password = hash_password($password, { salt => $salt, algorithm => 'sha512' });
# Store $hashed_password and $salt in the database
# Verification
my ($stored_hash, $stored_salt) = fetch_user_credentials($username);
if (verify_password($password, $stored_hash, { salt => $stored_salt, algorithm => 'sha512' })) {
# Authentication successful
} else {
# Authentication failed
}
2.2. Session Hijacking Prevention
Sessions should be managed securely. Regenerate session IDs upon login and privilege changes. Use secure, HttpOnly, and SameSite cookies for session tokens. Consider IP address and User-Agent binding for sessions, but be aware of usability implications (e.g., mobile users changing networks).
use CGI::Session;
my $session = CGI::Session->new(
'driver' => 'File',
'directory' => '/var/www/sessions', # Ensure this directory is secure and not web-accessible
'prefix' => 'sess_',
'id' => param('session_id'), # Or retrieve from cookie
);
# Regenerate session ID on login
$session->regenerate_id();
# Set session cookie attributes (example using CGI::Simple)
use CGI::Simple;
my $cgi = CGI::Simple->new;
print $cgi->header(
-cookie => [
$cgi->cookie(
-name => 'session_id',
-value => $session->id(),
-path => '/',
-secure => 1, # Only send over HTTPS
-httponly => 1, # Prevent JavaScript access
-samesite => 'Lax' # Or 'Strict' depending on requirements
)
]
);
3. Access Control and Authorization
Implement granular access control to ensure users can only access resources and perform actions they are authorized for. This is distinct from authentication (who you are) and focuses on authorization (what you can do).
3.1. Role-Based Access Control (RBAC)
Design and implement an RBAC system. Map users to roles, and roles to specific permissions. Check permissions at every critical endpoint.
sub is_authorized {
my ($user_id, $required_permission) = @_;
# Fetch user's roles
my @user_roles = get_user_roles($user_id);
# Fetch permissions for each role
my %role_permissions;
foreach my $role (@user_roles) {
push @{$role_permissions{$role}}, get_role_permissions($role);
}
# Check if any role has the required permission
foreach my $role (keys %role_permissions) {
if (grep { $_ eq $required_permission } @{$role_permissions{$role}}) {
return 1; # Authorized
}
}
return 0; # Not authorized
}
# Usage example
my $current_user_id = get_current_user_id(); # From session
if (is_authorized($current_user_id, 'edit_article')) {
# Allow editing
} else {
# Deny access
http_response(403, "Forbidden");
}
4. Error Handling and Logging
Proper error handling prevents information leakage. Comprehensive logging aids in incident detection and forensics.
4.1. Preventing Information Leakage
Do not expose detailed error messages (stack traces, database errors, file paths) to end-users. Log these details server-side and display generic error messages to the client.
use Log::Log4perl qw(:easy);
# Configure logging (e.g., in a startup script)
Log::Log4perl->easy_init($Log::Log4perl::Configurator::DEFAULT_LOGGING);
sub handle_request {
my $dbh;
eval {
$dbh = DBI->connect(...) or die $DBI::errstr;
# ... database operations ...
};
if ($@) {
my $error = $@;
ERROR "Database error: $error"; # Log detailed error server-side
http_response(500, "An internal server error occurred."); # Generic message to client
return;
}
# ... process successful request ...
}
4.2. Audit Logging
Log all security-relevant events: login attempts (success/failure), access to sensitive data, administrative actions, and significant configuration changes. Ensure logs include timestamps, source IP addresses, user IDs, and the action performed.
use Log::Log4perl qw(:easy);
sub log_security_event {
my ($user_id, $action, $details, $ip_address) = @_;
my $timestamp = scalar localtime;
INFO sprintf "[%s] User: %s, Action: %s, Details: %s, IP: %s",
$timestamp, $user_id // 'N/A', $action, $details // '', $ip_address // 'N/A';
}
# Example usage
my $user_id = get_current_user_id();
my $ip = $ENV{REMOTE_ADDR};
log_security_event($user_id, 'login_success', undef, $ip);
log_security_event($user_id, 'update_profile', 'Changed email address', $ip);
5. OVH Specific Configurations and Best Practices
Leverage OVH’s infrastructure features and adhere to their recommended configurations for enhanced security.
5.1. Web Server Configuration (Apache/Nginx)
Ensure your web server is configured to serve Perl applications securely. This typically involves using FastCGI or PSGI/Plack.
5.1.1. Apache with mod_fcgid/mod_proxy_fcgi
# httpd.conf or vhost config
<VirtualHost *:80>
ServerName yourdomain.com
DocumentRoot /var/www/yourdomain.com/public_html
# Enable CGI execution for Perl scripts
<Directory /var/www/yourdomain.com/public_html>
Options +ExecCGI
AddHandler fcgid-script .pl
AllowOverride None
Require all granted
</Directory>
# Or for PSGI/Plack applications
# ProxyPassMatch ^/(.*)$ fcgi://127.0.0.1:5000/$1
# Ensure CGI scripts are not directly accessible if not intended
# <FilesMatch "\.pl$">
# Require all denied
# </FilesMatch>
ErrorLog ${APACHE_LOG_DIR}/yourdomain.com_error.log
CustomLog ${APACHE_LOG_DIR}/yourdomain.com_access.log combined
</VirtualHost>
5.1.2. Nginx with uWSGI/FCGIwrap
# nginx.conf or site-available config
server {
listen 80;
server_name yourdomain.com;
root /var/www/yourdomain.com/public_html;
index index.html index.htm;
location / {
try_files $uri $uri/ /index.pl?$args;
}
# For CGI Perl scripts
location ~ \.pl$ {
# Ensure Perl scripts are not directly executable by the web server
# unless explicitly configured for CGI execution.
# If using FCGIwrap:
# fastcgi_pass unix:/var/run/fcgiwrap.socket;
# include fastcgi_params;
# fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# If using PSGI/Plack via uWSGI
# proxy_pass http://127.0.0.1:5000;
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header X-Forwarded-Proto $scheme;
# For security, deny direct access to .pl files if not intended as CGI
# deny all;
}
# Access logs
access_log /var/log/nginx/yourdomain.com.access.log;
error_log /var/log/nginx/yourdomain.com.error.log;
}
5.2. File Permissions and Ownership
Ensure that the web server user (e.g., `www-data`, `apache`, `nginx`) has only the necessary read/write permissions. Sensitive configuration files, logs, and temporary directories should not be writable by the web server user.
# Example: Set ownership and permissions for application files
sudo chown -R appuser:appgroup /var/www/yourdomain.com/app
sudo find /var/www/yourdomain.com/app -type d -exec chmod 755 {} \;
sudo find /var/www/yourdomain.com/app -type f -exec chmod 644 {} \;
# Ensure web server user can write to specific directories (e.g., uploads, cache)
sudo chown -R www-data:www-data /var/www/yourdomain.com/app/storage/cache
sudo chown -R www-data:www-data /var/www/yourdomain.com/app/uploads
# Ensure sensitive config files are not readable by the web server user
sudo chmod 600 /var/www/yourdomain.com/app/config/database.yml
5.3. OVH Security Features
Familiarize yourself with and configure OVH’s provided security services:
- OVHcloud Web Control Panel: Review firewall rules, DDoS protection settings, and SSL certificate management.
- SSH Access: Ensure SSH access is secured (key-based authentication, disable root login, change default port if feasible).
- Fail2Ban: Implement and configure Fail2Ban to protect against brute-force attacks on SSH, web server logs, etc.
- Regular Updates: Keep the operating system, Perl interpreter, and all installed Perl modules up-to-date. Use `cpanm` for module management and consider tools like `cpan-outdated` to identify outdated modules.
# Example: Update Perl modules cpanm --self-update cpanm --update-installed
6. Dependency Management and Vulnerability Scanning
Third-party modules are a common source of vulnerabilities. Proactive management is essential.
6.1. CPAN Module Security
Regularly scan your project’s dependencies for known vulnerabilities. Tools like `cpan-audit` (requires installation) can help.
# Install cpan-audit (may require root privileges) # cpanm App::cpan_audit # Run audit on your project's dependencies (e.g., from your project root) cpan-audit -r
6.2. `cpanfile` and `carton`
Use `cpanfile` to declare dependencies and `carton` to manage them. This ensures reproducible builds and makes it easier to track and update dependencies.
# Example cpanfile requires 'Dancer2', '1.000000'; requires 'DBI'; requires 'JSON'; # Install dependencies using carton # carton install
7. Secure Development Lifecycle (SDL) Integration
Security should be a consideration throughout the development process, not an afterthought.
7.1. Code Reviews
Incorporate security-focused code reviews. Train developers to identify common vulnerabilities in Perl code.
7.2. Static and Dynamic Analysis
Utilize static analysis tools (e.g., `Perl::Critic` with security-focused policies) and dynamic analysis tools (e.g., OWASP ZAP, Burp Suite) during testing phases.
# Example using Perl::Critic with security policies # Install Perl::Critic and relevant policies # cpanm Perl::Critic PPI::XS # Run Perl::Critic with specific policies perlcritic --severity 1 --profile=path/to/your/perlcriticrc your_script.pl
8. HTTPS Enforcement
All communication between the client and the server must be encrypted using TLS/SSL. OVH provides tools for SSL certificate management.
8.1. Redirect HTTP to HTTPS
Configure your web server to automatically redirect all HTTP requests to HTTPS.
# Nginx configuration
server {
listen 80;
server_name yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name yourdomain.com;
# SSL certificate configuration (obtained via OVH or Let's Encrypt)
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# ... other SSL settings ...
# ... rest of your HTTPS server configuration ...
}
# Apache httpd.conf or vhost config
<VirtualHost *:80>
ServerName yourdomain.com
Redirect permanent / https://yourdomain.com/
</VirtualHost>
<VirtualHost *:443>
ServerName yourdomain.com
SSLEngine on
SSLCertificateFile /path/to/your/certificate.crt
SSLCertificateKeyFile /path/to/your/private.key
# ... other SSL settings ...
# ... rest of your HTTPS server configuration ...
</VirtualHost>
Conclusion
This checklist provides a comprehensive framework for auditing Perl backends on OVH. Consistent application of these principles, combined with vigilance regarding new threats and OVH-specific features, is crucial for maintaining a secure environment.