An Auditor’s Checklist for Securing Perl Backends on Linode
System Hardening: Core OS and Network Access
Before even considering Perl application security, the underlying Linode infrastructure must be robustly hardened. This involves minimizing the attack surface and strictly controlling network access. An auditor will first verify the integrity of the operating system and its network posture.
1. Kernel Parameter Tuning for Security
Key kernel parameters can be adjusted to enhance network security. These settings are typically managed via sysctl. A common practice is to create a dedicated configuration file for security-related parameters.
Create or edit the file /etc/sysctl.d/99-security.conf:
# IP Spoofing protection net.ipv4.conf.all.rp_filter = 1 net.ipv4.conf.default.rp_filter = 1 # Ignore ICMP broadcast requests net.ipv4.icmp_echo_ignore_broadcasts = 1 # Disable source routing net.ipv4.conf.all.accept_source_route = 0 net.ipv6.conf.all.accept_source_route = 0 net.ipv4.conf.default.accept_source_route = 0 net.ipv6.conf.default.accept_source_route = 0 # Ignore send redirects net.ipv4.conf.all.send_redirects = 0 net.ipv4.conf.default.send_redirects = 0 # Block SYN-flooding net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_max_syn_backlog = 2048 net.ipv4.tcp_synack_retries = 2 net.ipv4.tcp_syn_retries = 5 # Log Martians net.ipv4.conf.all.log_martians = 1 # Ignore ICMP redirects net.ipv4.conf.all.accept_redirects = 0 net.ipv6.conf.all.accept_redirects = 0 net.ipv4.conf.default.accept_redirects = 0 net.ipv6.conf.default.accept_redirects = 0 net.ipv4.conf.all.secure_redirects = 0 net.ipv4.conf.default.secure_redirects = 0 # Enable TCP RST cookies net.ipv4.tcp_timestamps = 1 net.ipv4.tcp_sack = 1 # Disable ICMP ping # net.ipv4.icmp_echo_ignore_all = 1 # Uncomment if ping is not required for monitoring
Apply these settings immediately without a reboot:
sudo sysctl -p /etc/sysctl.d/99-security.conf
2. Firewall Configuration (UFW Example)
A strict firewall is paramount. Uncomplicated Firewall (UFW) is a user-friendly frontend for iptables. The default policy should deny all incoming traffic, and only explicitly allowed ports should be opened.
Ensure UFW is installed and enabled:
sudo apt update && sudo apt install ufw -y sudo ufw enable
Configure default policies and essential rules. For a typical web backend, this might include SSH, HTTP, and HTTPS.
sudo ufw default deny incoming sudo ufw default allow outgoing # Allow SSH (Port 22) - restrict to specific IPs if possible sudo ufw allow from <YOUR_ADMIN_IP_ADDRESS> to any port 22 proto tcp # Or, if dynamic IPs are unavoidable, allow from anywhere but monitor logs closely # sudo ufw allow 22/tcp # Allow HTTP (Port 80) sudo ufw allow 80/tcp # Allow HTTPS (Port 443) sudo ufw allow 443/tcp # Allow access to your Perl application's specific port if it's not standard HTTP/S # sudo ufw allow <YOUR_APP_PORT>/tcp # Reload UFW to apply changes sudo ufw reload # Check status sudo ufw status verbose
3. SSH Hardening
Secure the primary remote access vector. Disabling root login and password authentication is a critical step.
Edit the SSH daemon configuration file:
# /etc/ssh/sshd_config Port 22 # Consider changing this to a non-standard port, though it's security by obscurity. Protocol 2 PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes ChallengeResponseAuthentication no AllowUsers your_ssh_user # Replace with your actual non-root user
After editing, restart the SSH service. Ensure you have key-based authentication set up for your_ssh_user before restarting, or you will be locked out.
sudo systemctl restart sshd
Perl Application Security: Code and Runtime
With the infrastructure secured, attention shifts to the Perl application itself. This involves secure coding practices, dependency management, and runtime environment configuration.
1. Input Validation and Sanitization
This is the most critical aspect of web application security. All external input (from HTTP requests, databases, files, etc.) must be treated as untrusted. Perl’s flexibility can be a double-edged sword here; robust validation is essential.
Example: Validating User Input in a CGI script (or similar web framework context)
#!/usr/bin/perl
use strict;
use warnings;
use CGI;
use CGI::Carp qw(fatalsToBrowser);
my $cgi = CGI->new;
# --- Example: Sanitizing a username parameter ---
my $username = $cgi->param('username');
# Basic validation: Ensure it's not empty and contains only alphanumeric characters and underscores.
# A more robust solution would use a dedicated validation module.
if (defined $username && $username =~ /^[a-zA-Z0-9_]+$/) {
# Input is considered safe for this basic example.
# Further sanitization might be needed depending on context (e.g., database escaping).
print "Valid username: " . &escape_html($username) . "\n";
} else {
print "Invalid username provided.\n";
# Log this attempt
log_security_event("Invalid username input: " . $cgi->remote_addr);
}
# --- Example: Sanitizing a numeric ID parameter ---
my $item_id = $cgi->param('id');
# Validate that it's a positive integer.
if (defined $item_id && $item_id =~ /^\d+$/ && $item_id > 0) {
# Input is a positive integer.
print "Valid item ID: " . $item_id . "\n";
# Use $item_id safely in database queries or other operations.
} else {
print "Invalid item ID provided.\n";
log_security_event("Invalid item ID input: " . $cgi->remote_addr);
}
# --- Helper function for basic HTML escaping ---
sub escape_html {
my ($text) = @_;
$text =~ s/&/&/g;
$text =~ s/</g;
$text =~ s/>/>/g;
$text =~ s/"/"/g;
$text =~ s/'/'/g;
return $text;
}
# --- Placeholder for logging ---
sub log_security_event {
my ($message) = @_;
# Implement robust logging to a secure file or SIEM.
# Example: print STDERR "SECURITY ALERT: $message\n";
}
# In a real application, you'd also handle other parameters,
# potential injection vectors (SQL, OS command, etc.), and output encoding.
Auditors will look for the use of established modules like CGI:: segurança or custom, well-tested validation routines. Relying solely on regular expressions without understanding their limitations (e.g., catastrophic backtracking) can be risky.
2. Dependency Management and Vulnerability Scanning
Perl applications often rely on modules from CPAN. Unpatched or vulnerable modules are a significant risk. A systematic approach to managing and scanning these dependencies is crucial.
Workflow:
- Maintain a
cpanfileor similar manifest for your project’s dependencies. - Use
cpanm(App::cpanminus) for installation, as it’s generally faster and simpler than the oldercpanshell. - Regularly scan installed modules for known vulnerabilities.
Example using cpanm and a hypothetical vulnerability scanner:
# Install cpanm if you don't have it
curl -L https://cpanmin.us | perl - --sudo App::cpanminus
# Install dependencies from cpanfile (if using Carton or similar)
# carton install
# Or install individual modules
cpanm --sudo Module::Name::Example
# --- Vulnerability Scanning ---
# There isn't a single de facto standard Perl vulnerability scanner like npm audit.
# However, you can leverage CPAN Testers and manual checks.
# For a more automated approach, consider integrating with security tools that
# can parse CPAN metadata or known CVE databases.
# Example: Checking a specific module's version against known vulnerabilities
# This requires a database of CVEs and their mapping to Perl modules.
# A common approach is to script checks against public vulnerability databases.
# Manual check example:
# Go to https://metacpan.org/search?q=Module::Name
# Check the "Security" tab or linked advisories.
# Scripted approach (conceptual):
# 1. Get a list of installed modules and their versions:
# perl -MApp::ModuleBuild -MModule::CoreList -E 'print join(" ", @$_), "\n" for grep { !Module::CoreList->is_core($_->[0]) } App::ModuleBuild->list_installed'
# 2. Query a vulnerability database (e.g., NVD, OSV) for these modules.
# This often involves API calls or parsing downloaded data.
# Example: Using a hypothetical 'perl_vuln_scanner' tool
# perl_vuln_scanner --check-modules
Auditors will verify that a process exists for tracking and updating dependencies, and that critical vulnerabilities are addressed promptly. The use of `local::lib` or `plenv` for managing per-project Perl environments can also be a positive indicator, preventing global conflicts and simplifying dependency management.
3. Secure Configuration of Web Server (Nginx Example)
If Perl is serving web requests (e.g., via FastCGI, PSGI/Plack), the web server configuration is critical. Nginx is a common choice for its performance and security features.
Example Nginx Configuration Snippet for a PSGI Application:
# /etc/nginx/sites-available/your_perl_app
server {
listen 80;
server_name your.domain.com;
# Redirect HTTP to HTTPS
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
server_name your.domain.com;
# SSL Configuration
ssl_certificate /etc/letsencrypt/live/your.domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your.domain.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf; # Recommended by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # Recommended by Certbot
# Security Headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" 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 Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none';" always; # Example CSP, tailor carefully
# Logging
access_log /var/log/nginx/your_perl_app.access.log;
error_log /var/log/nginx/your_perl_app.error.log warn;
# Proxy to PSGI/Plack application (running via Starman/Plackup)
location / {
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;
# Assuming your PSGI app is running on http://127.0.0.1:5000
proxy_pass http://127.0.0.1:5000;
proxy_read_timeout 300s; # Adjust as needed
proxy_connect_timeout 75s; # Adjust as needed
}
# Deny access to hidden files
location ~ /\. {
deny all;
}
}
Auditors will check for:
- Use of HTTPS with strong TLS configurations (e.g., TLS 1.2/1.3, modern cipher suites).
- Appropriate security headers (HSTS, X-Frame-Options, X-Content-Type-Options).
- Rate limiting and request filtering (though often handled at the application level or via WAF).
- Minimizing exposed information in error messages.
- Properly configured access and error logs.
- Disabling directory listing and protecting sensitive files (e.g.,
.git, configuration files).
Runtime Security and Monitoring
Securing the application doesn’t end with deployment. Continuous monitoring and secure runtime practices are essential.
1. Logging and Auditing
Comprehensive logging is vital for detecting and investigating security incidents. Logs should capture relevant events, including authentication attempts, errors, and significant application actions.
Perl Logging Example (using Log::Log4perl):
#!/usr/bin/perl
use strict;
use warnings;
use Log::Log4perl qw(:easy);
# Initialize logger (typically done once at application startup)
# Configuration can be in a file or inline.
# Example inline config:
Log::Log4perl::init(q{
log4perl.rootLogger = INFO, Screen
log4perl::appender.Screen = Log::Log4perl::Appender::Screen
log4perl::appender.Screen::layout = Log::Log4perl::Layout::PatternLayout
log4perl::appender.Screen::layout::pattern = [%p] %m at %F line %L.
});
# Example configuration for file logging:
# Log::Log4perl::init("log4perl.conf"); # Assuming log4perl.conf exists
sub log_security_event {
my ($message) = @_;
ERROR("SECURITY EVENT: $message"); # Use ERROR or FATAL for security-critical events
}
sub log_user_activity {
my ($user, $action) = @_;
INFO("User '$user' performed action: '$action'");
}
# --- Usage Examples ---
log_user_activity("admin", "login");
# Simulate a potential security event
my $user_input = "../../../etc/passwd"; # Malicious input
if ($user_input =~ /\.\.\//) {
log_security_event("Potential directory traversal attempt detected with input: $user_input");
}
# Simulate a successful operation
log_user_activity("user123", "view_profile");
# Log an error
WARN("Database connection failed.");
Auditors will verify that logs are:
- Enabled and configured correctly.
- Written to a secure, non-web-accessible location.
- Rotated regularly to manage disk space.
- Protected against tampering.
- Centralized if multiple servers are involved (e.g., using rsyslog, Filebeat to a SIEM).
- Contain sufficient detail to reconstruct events (timestamps, source IP, user, action).
2. Process Isolation and Least Privilege
The Perl application should run under a dedicated, unprivileged user account. This limits the potential damage if the application process is compromised.
Example: Running a PSGI app with Starman under a specific user
# Ensure the user exists and has minimal privileges sudo useradd -r -s /bin/false perlapp_user # Grant necessary permissions to directories/files the app needs to access (read-only where possible) sudo chown -R perlapp_user:perlapp_user /path/to/your/app/code sudo chown perlapp_user:perlapp_user /path/to/your/app/logs # Ensure web server (e.g., Nginx) can communicate with the app's socket/port # Example systemd service file for Starman: # /etc/systemd/system/your_perl_app.service [Unit] Description=Your Perl PSGI Application (Starman) After=network.target [Service] User=perlapp_user Group=perlapp_user WorkingDirectory=/path/to/your/app/code ExecStart=/usr/local/bin/starman --port 5000 --workers 4 --user perlapp_user --group perlapp_user your_app.psgi Restart=on-failure # Consider setting environment variables securely if needed # Environment="DB_PASSWORD=your_secret_password" [Install] WantedBy=multi-user.target
Auditors will verify that the application process:
- Runs as a non-root user.
- Has only the necessary file system permissions.
- Does not have unnecessary network access.
- Is managed by a process supervisor (like
systemd,supervisord) for reliability and restarts.
3. Secure Handling of Secrets
Database credentials, API keys, and other sensitive information must be stored and accessed securely, never hardcoded directly in the application code.
Methods:
- Environment variables (as shown in the
systemdexample). - Dedicated secrets management tools (e.g., HashiCorp Vault, AWS Secrets Manager, Linode Object Storage with encryption).
- Encrypted configuration files (requiring a key to decrypt at runtime).
Example using environment variables:
#!/usr/bin/perl
use strict;
use warnings;
use DBI;
# Retrieve credentials from environment variables
my $db_host = $ENV{DB_HOST} || 'localhost';
my $db_name = $ENV{DB_NAME} || 'myapp_db';
my $db_user = $ENV{DB_USER} || 'myapp_user';
my $db_pass = $ENV{DB_PASS}; # Crucial: Should not have a default
if (!defined $db_pass) {
die "FATAL: DB_PASS environment variable not set.\n";
}
# Connect to the database
my $dsn = "DBI:mysql:database=$db_name;host=$db_host";
my $dbh = DBI->connect($dsn, $db_user, $db_pass, { RaiseError => 1, AutoCommit => 1 });
print "Successfully connected to database '$db_name' on '$db_host'.\n";
# ... perform database operations ...
$dbh->disconnect;
Auditors will check that secrets are not present in version control (e.g., Git) and are managed through secure, audited channels.