Legacy PHP Containerization: Securing PHP 5.3.3 with Podman
Containerizing legacy PHP applications presents unique challenges, especially when dealing with end-of-life operating systems and PHP versions. This guide provides a comprehensive approach to safely containerizing PHP 5.3.3 applications using Podman, focusing on security research environments and migration planning.
Table of Contents
Open Table of Contents
Introduction
Legacy PHP applications often run on outdated systems that pose significant security risks. Containerization offers a path to isolate these applications while planning for modernization. This guide focuses on PHP 5.3.3 running on CentOS 6, demonstrating secure containerization practices with Podman.
Why Containerize Legacy PHP?
- Security Isolation: Contain legacy applications in isolated environments
- Migration Planning: Enable gradual migration to modern platforms
- Research Environments: Safely analyze legacy codebases
- Dependency Management: Preserve exact runtime environments
- Risk Mitigation: Reduce attack surface through isolation
Podman vs Docker for Legacy Systems
Advantages of Podman
Podman offers several advantages for legacy PHP containerization:
- Rootless Operation: Enhanced security through unprivileged containers
- No Daemon: Eliminates single point of failure and attack surface
- Systemd Integration: Better process management for legacy services
- OCI Compliance: Standard container formats and compatibility
- SELinux Support: Enhanced security through mandatory access controls
Key Differences for Legacy Workloads
# Podman rootless execution
podman run --rm -p 8080:8080 php-legacy
# Docker requires daemon (running as root)
docker run --rm -p 8080:8080 php-legacy
Basic PHP 5.3.3 Container
Dockerfile Configuration
FROM centos:6
LABEL maintainer="Security Research Environment - NOT FOR PRODUCTION"
# Configure vault repositories for CentOS 6 (EOL)
RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-Base.repo && \
sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-Base.repo
# Install EPEL repository for additional packages
RUN rpm -Uvh https://vault.centos.org/6.10/extras/x86_64/Packages/epel-release-6-8.noarch.rpm
# Install PHP 5.3.3 and dependencies
RUN yum -y update && \
yum -y install \
php \
php-cli \
php-common \
php-mysql \
php-pdo \
php-gd \
php-xml \
php-mbstring \
httpd \
&& yum clean all
# Configure Apache for container environment
RUN sed -i 's/Listen 80/Listen 8080/g' /etc/httpd/conf/httpd.conf && \
echo "ServerName localhost" >> /etc/httpd/conf/httpd.conf && \
echo "<?php phpinfo(); ?>" > /var/www/html/info.php
# Set necessary permissions for rootless container
RUN chmod -R 755 /var/www/html/ && \
chmod -R 755 /var/log/httpd/ && \
chmod -R 755 /var/run/httpd/ && \
chown -R root:root /var/log/httpd/ && \
chown -R root:root /var/run/httpd/ && \
chown -R root:root /var/www/html/
# Create non-root user for security
RUN useradd -r -s /bin/false -c "PHP Application User" phpuser && \
chown -R phpuser:phpuser /var/www/html
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8080/info.php || exit 1
# Run Apache in foreground
CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]
Enhanced Dockerfile for Production-Like Usage
FROM centos:6
LABEL maintainer="Legacy PHP Research Environment"
LABEL version="1.0"
LABEL description="CentOS 6 with PHP 5.3.3 for legacy application research"
# Security warning label
LABEL security.warning="This container contains EOL software and should NOT be used in production"
# Configure repositories
RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-Base.repo && \
sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-Base.repo
# Install EPEL
RUN rpm -Uvh https://vault.centos.org/6.10/extras/x86_64/Packages/epel-release-6-8.noarch.rpm || true
# Install comprehensive PHP stack
RUN yum -y update && \
yum -y install \
# Core PHP
php \
php-cli \
php-common \
php-fpm \
# Database extensions
php-mysql \
php-pgsql \
php-pdo \
# Common extensions
php-gd \
php-xml \
php-mbstring \
php-json \
php-curl \
php-soap \
php-ldap \
php-zip \
# Web server
httpd \
# Utilities
curl \
wget \
vim \
less \
# Security tools
sudo \
&& yum clean all && \
rm -rf /var/cache/yum
# Configure Apache
COPY apache/httpd.conf /etc/httpd/conf/httpd.conf
COPY apache/security.conf /etc/httpd/conf.d/security.conf
# Configure PHP
COPY php/php.ini /etc/php.ini
COPY php/security.ini /etc/php.d/99-security.ini
# Create application structure
RUN mkdir -p /var/www/html/app && \
mkdir -p /var/log/app && \
mkdir -p /var/lib/php/session
# Create non-privileged user
RUN groupadd -r phpapp && \
useradd -r -g phpapp -s /bin/false -c "PHP Application User" phpapp
# Set ownership and permissions
RUN chown -R phpapp:phpapp /var/www/html/app && \
chown -R phpapp:phpapp /var/log/app && \
chown -R phpapp:phpapp /var/lib/php/session && \
chmod -R 755 /var/www/html && \
chmod -R 750 /var/log/app
# Security hardening
RUN chmod 600 /etc/shadow && \
chmod 644 /etc/passwd && \
find /var/www/html -name "*.php" -exec chmod 644 {} \; && \
find /var/www/html -type d -exec chmod 755 {} \;
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8080/ || exit 1
# Use exec form for better signal handling
CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]
Configuration Files
Apache Security Configuration
# /etc/httpd/conf.d/security.conf
# Hide Apache version
ServerTokens Prod
ServerSignature Off
# Disable unnecessary modules
LoadModule rewrite_module modules/mod_rewrite.so
# Security headers
Header always set X-Content-Type-Options nosniff
Header always set X-Frame-Options DENY
Header always set X-XSS-Protection "1; mode=block"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
# Disable server status
<Location "/server-status">
Order deny,allow
Deny from all
</Location>
# Disable server info
<Location "/server-info">
Order deny,allow
Deny from all
</Location>
# Hide .git directories
<DirectoryMatch "\.git">
Order deny,allow
Deny from all
</DirectoryMatch>
# Hide configuration files
<Files ~ "\.(conf|ini|log|sql|md)$">
Order deny,allow
Deny from all
</Files>
# Disable directory browsing
Options -Indexes
# Prevent access to PHP files in uploads directory
<Directory "/var/www/html/uploads">
<Files "*.php">
Order deny,allow
Deny from all
</Files>
</Directory>
PHP Security Configuration
; /etc/php.d/99-security.ini
; Disable dangerous functions
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source,file_get_contents
; Hide PHP version
expose_php = Off
; Limit script execution
max_execution_time = 30
max_input_time = 30
memory_limit = 64M
; File upload restrictions
file_uploads = On
upload_max_filesize = 2M
max_file_uploads = 5
upload_tmp_dir = /tmp
; Session security
session.cookie_secure = On
session.cookie_httponly = On
session.use_strict_mode = On
session.cookie_samesite = Strict
; Error handling
display_errors = Off
log_errors = On
error_log = /var/log/app/php_errors.log
; Open basedir restriction
open_basedir = /var/www/html:/tmp:/var/lib/php/session
; SQL injection protection
magic_quotes_gpc = Off
magic_quotes_runtime = Off
; Include path security
include_path = ".:/usr/share/php"
; Allow only specific file extensions
; This should be configured per application
Building and Running with Podman
Build Process
#!/bin/bash
# Build script for legacy PHP container
set -e
CONTAINER_NAME="php-legacy"
TAG="5.3.3-centos6"
FULL_NAME="${CONTAINER_NAME}:${TAG}"
echo "Building legacy PHP container..."
# Build with Podman
podman build \
--tag "$FULL_NAME" \
--label "build.date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
--label "build.version=1.0.0" \
--label "security.scan=required" \
.
# Verify build
if podman images | grep -q "$CONTAINER_NAME"; then
echo "✓ Container built successfully: $FULL_NAME"
else
echo "✗ Container build failed"
exit 1
fi
# Security scan (if available)
if command -v podman &> /dev/null; then
echo "Running security scan..."
podman run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy image "$FULL_NAME" || true
fi
echo "Build completed: $FULL_NAME"
Running the Container
#!/bin/bash
# Run script for legacy PHP container
CONTAINER_NAME="php-legacy"
TAG="5.3.3-centos6"
INSTANCE_NAME="php-legacy-instance"
# Stop and remove existing instance
podman stop "$INSTANCE_NAME" 2>/dev/null || true
podman rm "$INSTANCE_NAME" 2>/dev/null || true
echo "Starting legacy PHP container..."
# Run with security considerations
podman run \
--name "$INSTANCE_NAME" \
--detach \
--publish 8080:8080 \
--volume "./app:/var/www/html/app:Z" \
--volume "./logs:/var/log/app:Z" \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=100m \
--tmpfs /var/run/httpd:rw,size=10m \
--security-opt no-new-privileges \
--cap-drop ALL \
--cap-add SETUID \
--cap-add SETGID \
--cap-add DAC_OVERRIDE \
--memory 256m \
--cpus 0.5 \
--restart unless-stopped \
"$CONTAINER_NAME:$TAG"
# Verify container is running
if podman ps | grep -q "$INSTANCE_NAME"; then
echo "✓ Container started successfully"
echo "Access the application at: http://localhost:8080"
echo "PHP info available at: http://localhost:8080/info.php"
else
echo "✗ Container failed to start"
echo "Checking logs..."
podman logs "$INSTANCE_NAME"
exit 1
fi
Security Hardening
Container Security Configuration
# Enhanced security run command
run_secure_php_container() {
local container_name="php-legacy-secure"
local image_name="php-legacy:5.3.3-centos6"
# Create necessary directories
mkdir -p ./app ./logs ./tmp
# Set proper permissions
chmod 755 ./app ./logs
chmod 1777 ./tmp
podman run \
--name "$container_name" \
--detach \
--publish 127.0.0.1:8080:8080 \
\
`# Volume mounts with SELinux labels` \
--volume "./app:/var/www/html/app:Z,ro" \
--volume "./logs:/var/log/app:Z" \
\
`# Read-only root filesystem` \
--read-only \
\
`# Temporary filesystems` \
--tmpfs /tmp:rw,noexec,nosuid,nodev,size=50m \
--tmpfs /var/run:rw,noexec,nosuid,nodev,size=10m \
--tmpfs /var/lib/php/session:rw,noexec,nosuid,nodev,size=10m \
\
`# Security options` \
--security-opt no-new-privileges \
--security-opt label=type:container_t \
\
`# Capability restrictions` \
--cap-drop ALL \
--cap-add SETUID \
--cap-add SETGID \
--cap-add DAC_OVERRIDE \
\
`# Resource limits` \
--memory 128m \
--memory-swap 128m \
--cpus 0.25 \
--pids-limit 50 \
\
`# Network security` \
--network slirp4netns:allow_host_loopback=false \
\
`# Process limits` \
--ulimit nproc=50:50 \
--ulimit nofile=1024:1024 \
\
`# Environment restrictions` \
--env-file /dev/null \
\
"$image_name"
}
Security Monitoring
#!/bin/bash
# Security monitoring script for PHP container
monitor_container_security() {
local container_name="$1"
echo "=== Container Security Monitoring ==="
# Check container status
if podman ps | grep -q "$container_name"; then
echo "✓ Container is running"
else
echo "✗ Container is not running"
return 1
fi
# Check resource usage
echo "Resource Usage:"
podman stats --no-stream "$container_name" | tail -n +2
# Check for privilege escalation attempts
echo "Security Events:"
podman logs "$container_name" 2>&1 | grep -i "permission\|denied\|failed\|error" | tail -5
# Check file system integrity
echo "File System Check:"
podman exec "$container_name" find /var/www/html -type f -perm /u+s,g+s 2>/dev/null | head -5
# Check network connections
echo "Network Connections:"
podman exec "$container_name" netstat -tuln 2>/dev/null | grep LISTEN || echo "No listening ports found"
# Check process list
echo "Running Processes:"
podman exec "$container_name" ps aux | head -10
}
# Usage
monitor_container_security "php-legacy-instance"
Migration Strategies
Assessment and Planning
#!/bin/bash
# Legacy PHP application assessment script
assess_php_application() {
local app_path="$1"
echo "=== PHP Application Assessment ==="
echo "Application path: $app_path"
# PHP version detection
echo "PHP Version Requirements:"
grep -r "php" "$app_path" | grep -i version | head -5
# Framework detection
echo "Framework Detection:"
if [ -f "$app_path/wp-config.php" ]; then
echo "- WordPress detected"
elif [ -f "$app_path/configuration.php" ]; then
echo "- Joomla detected"
elif [ -f "$app_path/config/config.php" ]; then
echo "- Custom framework detected"
fi
# Security issues
echo "Potential Security Issues:"
grep -r "mysql_query\|eval\|system\|exec" "$app_path" --include="*.php" | wc -l | xargs echo "Dangerous function calls:"
grep -r "register_globals\|magic_quotes" "$app_path" --include="*.php" | wc -l | xargs echo "Legacy PHP features:"
# Database connections
echo "Database Connections:"
grep -r "mysql_connect\|mysqli_connect\|PDO" "$app_path" --include="*.php" | head -3
# File upload handling
echo "File Upload Handlers:"
grep -r "\$_FILES\|move_uploaded_file" "$app_path" --include="*.php" | wc -l | xargs echo "Upload handlers found:"
# External dependencies
echo "External Dependencies:"
find "$app_path" -name "*.xml" -o -name "composer.json" -o -name "package.json" | head -5
}
# Usage
assess_php_application "./legacy-app"
Modernization Path
# migration-plan.yml
apiVersion: v1
kind: ConfigMap
metadata:
name: php-migration-plan
data:
phases: |
Phase 1: Containerization (Current)
- Isolate legacy application in container
- Implement security hardening
- Establish monitoring and logging
Phase 2: Security Hardening
- Update to PHP 5.6 (last 5.x version)
- Implement WAF protection
- Add vulnerability scanning
Phase 3: Framework Modernization
- Identify migration path to PHP 7.4/8.x
- Refactor deprecated functions
- Update database connections
Phase 4: Full Migration
- Migrate to modern PHP version
- Implement modern framework
- Deploy to production infrastructure
security_checklist: |
- [ ] SQL injection protection
- [ ] XSS prevention
- [ ] CSRF protection
- [ ] File upload validation
- [ ] Session security
- [ ] Error handling
- [ ] Input validation
- [ ] Output encoding
compatibility_matrix: |
Legacy (PHP 5.3.3):
- mysql_* functions (deprecated)
- register_globals support
- magic_quotes support
Modern (PHP 8.x):
- mysqli_* or PDO required
- Strict type checking
- Improved error handling
Monitoring and Logging
Container Monitoring Setup
#!/bin/bash
# Monitoring setup for legacy PHP container
setup_monitoring() {
local container_name="$1"
# Create monitoring directories
mkdir -p ./monitoring/{logs,metrics,alerts}
# Setup log aggregation
cat > ./monitoring/rsyslog.conf << 'EOF'
# Forward container logs to central location
*.* @@localhost:514
EOF
# Create monitoring script
cat > ./monitoring/monitor.sh << 'EOF'
#!/bin/bash
CONTAINER_NAME="$1"
LOG_DIR="./monitoring/logs"
while true; do
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
# Resource usage
STATS=$(podman stats --no-stream "$CONTAINER_NAME" 2>/dev/null | tail -n +2)
echo "$TIMESTAMP - STATS: $STATS" >> "$LOG_DIR/resource.log"
# Health check
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/info.php)
echo "$TIMESTAMP - HTTP_STATUS: $HTTP_STATUS" >> "$LOG_DIR/health.log"
# Security events
SECURITY_EVENTS=$(podman logs "$CONTAINER_NAME" 2>&1 | grep -i "error\|warning\|failed" | tail -1)
[ -n "$SECURITY_EVENTS" ] && echo "$TIMESTAMP - SECURITY: $SECURITY_EVENTS" >> "$LOG_DIR/security.log"
sleep 60
done
EOF
chmod +x ./monitoring/monitor.sh
# Start monitoring in background
nohup ./monitoring/monitor.sh "$container_name" > ./monitoring/monitor.out 2>&1 &
echo "Monitoring started for container: $container_name"
}
# Alerting system
setup_alerting() {
cat > ./monitoring/alerts.sh << 'EOF'
#!/bin/bash
LOG_DIR="./monitoring/logs"
ALERT_EMAIL="admin@example.com"
# Check for high resource usage
check_resources() {
local cpu_threshold=80
local mem_threshold=80
if [ -f "$LOG_DIR/resource.log" ]; then
local latest_stats=$(tail -1 "$LOG_DIR/resource.log")
local cpu_usage=$(echo "$latest_stats" | awk '{print $4}' | sed 's/%//')
local mem_usage=$(echo "$latest_stats" | awk '{print $6}' | sed 's/%//')
if (( $(echo "$cpu_usage > $cpu_threshold" | bc -l) )); then
echo "ALERT: High CPU usage: $cpu_usage%" | mail -s "Container Alert" "$ALERT_EMAIL"
fi
if (( $(echo "$mem_usage > $mem_threshold" | bc -l) )); then
echo "ALERT: High memory usage: $mem_usage%" | mail -s "Container Alert" "$ALERT_EMAIL"
fi
fi
}
# Check for security events
check_security() {
if [ -f "$LOG_DIR/security.log" ]; then
local recent_events=$(tail -10 "$LOG_DIR/security.log" | grep "$(date '+%Y-%m-%d')")
if [ -n "$recent_events" ]; then
echo "SECURITY ALERT: Recent security events detected" | mail -s "Security Alert" "$ALERT_EMAIL"
fi
fi
}
check_resources
check_security
EOF
chmod +x ./monitoring/alerts.sh
# Add to crontab
(crontab -l 2>/dev/null; echo "*/5 * * * * $(pwd)/monitoring/alerts.sh") | crontab -
}
Application Performance Monitoring
<?php
// monitoring/apm.php - Simple APM for legacy PHP
class LegacyAPM {
private $start_time;
private $start_memory;
private $log_file;
public function __construct($log_file = '/var/log/app/apm.log') {
$this->start_time = microtime(true);
$this->start_memory = memory_get_usage();
$this->log_file = $log_file;
// Register shutdown function
register_shutdown_function(array($this, 'shutdown_handler'));
}
public function log_event($event, $data = array()) {
$timestamp = date('Y-m-d H:i:s');
$memory = memory_get_usage();
$elapsed = microtime(true) - $this->start_time;
$log_entry = array(
'timestamp' => $timestamp,
'event' => $event,
'memory_mb' => round($memory / 1024 / 1024, 2),
'elapsed_ms' => round($elapsed * 1000, 2),
'data' => $data
);
error_log(json_encode($log_entry) . "\n", 3, $this->log_file);
}
public function shutdown_handler() {
$error = error_get_last();
$total_time = microtime(true) - $this->start_time;
$peak_memory = memory_get_peak_usage();
$this->log_event('request_end', array(
'total_time_ms' => round($total_time * 1000, 2),
'peak_memory_mb' => round($peak_memory / 1024 / 1024, 2),
'error' => $error
));
}
public function track_database_query($query, $execution_time) {
$this->log_event('database_query', array(
'query' => substr($query, 0, 100),
'execution_time_ms' => round($execution_time * 1000, 2)
));
}
}
// Usage in legacy application
$apm = new LegacyAPM();
$apm->log_event('request_start', array('uri' => $_SERVER['REQUEST_URI']));
?>
Backup and Recovery
Container Backup Strategy
#!/bin/bash
# Backup strategy for legacy PHP container
backup_container() {
local container_name="$1"
local backup_dir="./backups/$(date +%Y%m%d_%H%M%S)"
echo "Creating backup for container: $container_name"
mkdir -p "$backup_dir"
# Export container as tar
podman export "$container_name" > "$backup_dir/container.tar"
# Backup volumes
if [ -d "./app" ]; then
tar -czf "$backup_dir/app_data.tar.gz" ./app
fi
if [ -d "./logs" ]; then
tar -czf "$backup_dir/logs.tar.gz" ./logs
fi
# Backup configuration
cp Dockerfile "$backup_dir/"
cp -r ./config "$backup_dir/" 2>/dev/null || true
# Create manifest
cat > "$backup_dir/manifest.json" << EOF
{
"container_name": "$container_name",
"backup_date": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"podman_version": "$(podman --version)",
"image_info": $(podman inspect "$container_name" | jq '.[0].Image' || echo "null"),
"files": [
"container.tar",
"app_data.tar.gz",
"logs.tar.gz",
"Dockerfile"
]
}
EOF
echo "Backup completed: $backup_dir"
# Cleanup old backups (keep last 7 days)
find ./backups -type d -mtime +7 -exec rm -rf {} + 2>/dev/null || true
}
# Restore from backup
restore_container() {
local backup_dir="$1"
local container_name="$2"
if [ ! -d "$backup_dir" ]; then
echo "Backup directory not found: $backup_dir"
return 1
fi
echo "Restoring container from: $backup_dir"
# Stop existing container
podman stop "$container_name" 2>/dev/null || true
podman rm "$container_name" 2>/dev/null || true
# Import container
cat "$backup_dir/container.tar" | podman import - "restored-$container_name"
# Restore volumes
if [ -f "$backup_dir/app_data.tar.gz" ]; then
tar -xzf "$backup_dir/app_data.tar.gz"
fi
if [ -f "$backup_dir/logs.tar.gz" ]; then
tar -xzf "$backup_dir/logs.tar.gz"
fi
echo "Restore completed. You may need to manually start the container."
}
Best Practices
Development Workflow
- Isolated Development: Use containers for all legacy development work
- Version Control: Maintain Dockerfile and configurations in version control
- Security Scanning: Regular vulnerability assessments of container images
- Resource Monitoring: Continuous monitoring of container performance
- Backup Strategy: Regular automated backups of container state and data
Security Guidelines
- Principle of Least Privilege: Run containers with minimal required permissions
- Network Isolation: Restrict network access to necessary services only
- Regular Updates: Keep base images updated within compatibility constraints
- Log Monitoring: Implement comprehensive logging and alerting
- Incident Response: Prepare procedures for security incidents
Migration Planning
- Assessment Phase: Thoroughly assess legacy application requirements
- Compatibility Testing: Test modernization changes in isolated environments
- Incremental Updates: Plan gradual migration to avoid breaking changes
- Rollback Procedures: Maintain ability to revert to legacy systems
- Documentation: Document all changes and migration steps
Conclusion
Containerizing legacy PHP applications with Podman provides a secure approach to managing outdated systems while planning for modernization. This approach offers:
- Security Isolation: Contains legacy vulnerabilities within controlled environments
- Migration Path: Enables gradual modernization without service disruption
- Research Environment: Provides safe space for security analysis and testing
- Operational Continuity: Maintains service availability during transition periods
Remember that this approach is intended for research, migration planning, and controlled environments. Legacy PHP versions contain known security vulnerabilities and should never be exposed to production traffic without proper security controls and isolation measures.
This containerization approach is designed for security research and migration planning only. Always consult security professionals before deploying legacy systems in any environment.