Skip to content

Legacy PHP Containerization: Securing PHP 5.3.3 with Podman and CentOS 6

Published: at 02:19 AM

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?

Podman vs Docker for Legacy Systems

Advantages of Podman

Podman offers several advantages for legacy PHP containerization:

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

  1. Isolated Development: Use containers for all legacy development work
  2. Version Control: Maintain Dockerfile and configurations in version control
  3. Security Scanning: Regular vulnerability assessments of container images
  4. Resource Monitoring: Continuous monitoring of container performance
  5. Backup Strategy: Regular automated backups of container state and data

Security Guidelines

  1. Principle of Least Privilege: Run containers with minimal required permissions
  2. Network Isolation: Restrict network access to necessary services only
  3. Regular Updates: Keep base images updated within compatibility constraints
  4. Log Monitoring: Implement comprehensive logging and alerting
  5. Incident Response: Prepare procedures for security incidents

Migration Planning

  1. Assessment Phase: Thoroughly assess legacy application requirements
  2. Compatibility Testing: Test modernization changes in isolated environments
  3. Incremental Updates: Plan gradual migration to avoid breaking changes
  4. Rollback Procedures: Maintain ability to revert to legacy systems
  5. 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:

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.