← back to Watches

scripts/security-audit.sh

535 lines

#!/bin/bash

###############################################################################
# COMPREHENSIVE SECURITY AUDIT SCRIPT
# Omega Watch Price History Platform
###############################################################################

set -e

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Configuration
PROJECT_DIR="/root/Projects/watches"
PORT=7600
HOST="localhost"
BASE_URL="http://$HOST:$PORT"
REPORT_DIR="$PROJECT_DIR/logs/security-audits"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
REPORT_FILE="$REPORT_DIR/audit_$TIMESTAMP.txt"

# Create report directory
mkdir -p "$REPORT_DIR"

# Initialize report
echo "╔════════════════════════════════════════════════════════════════╗" | tee "$REPORT_FILE"
echo "║        SECURITY AUDIT REPORT - OMEGA WATCHES PLATFORM          ║" | tee -a "$REPORT_FILE"
echo "╚════════════════════════════════════════════════════════════════╝" | tee -a "$REPORT_FILE"
echo "" | tee -a "$REPORT_FILE"
echo "Date: $(date)" | tee -a "$REPORT_FILE"
echo "Target: $BASE_URL" | tee -a "$REPORT_FILE"
echo "" | tee -a "$REPORT_FILE"

###############################################################################
# Helper Functions
###############################################################################

print_section() {
    echo "" | tee -a "$REPORT_FILE"
    echo "═══════════════════════════════════════════════════════════════" | tee -a "$REPORT_FILE"
    echo "$1" | tee -a "$REPORT_FILE"
    echo "═══════════════════════════════════════════════════════════════" | tee -a "$REPORT_FILE"
    echo "" | tee -a "$REPORT_FILE"
}

print_test() {
    echo -e "${BLUE}[TEST]${NC} $1" | tee -a "$REPORT_FILE"
}

print_pass() {
    echo -e "${GREEN}[PASS]${NC} $1" | tee -a "$REPORT_FILE"
}

print_fail() {
    echo -e "${RED}[FAIL]${NC} $1" | tee -a "$REPORT_FILE"
}

print_warn() {
    echo -e "${YELLOW}[WARN]${NC} $1" | tee -a "$REPORT_FILE"
}

print_info() {
    echo -e "${BLUE}[INFO]${NC} $1" | tee -a "$REPORT_FILE"
}

###############################################################################
# Test 1: Server Accessibility
###############################################################################

print_section "1. SERVER ACCESSIBILITY TEST"

print_test "Checking if server is running on port $PORT..."
if netstat -tulnp 2>/dev/null | grep -q ":$PORT "; then
    print_pass "Server is running on port $PORT"
else
    print_fail "Server is NOT running on port $PORT"
    echo "Please start the server first: pm2 start server-enterprise-security.js"
    exit 1
fi

print_test "Testing HTTP connectivity..."
if curl -s -f -o /dev/null "$BASE_URL/api/health"; then
    print_pass "HTTP connectivity successful"
else
    print_fail "Cannot connect to $BASE_URL"
    exit 1
fi

###############################################################################
# Test 2: Security Headers
###############################################################################

print_section "2. SECURITY HEADERS TEST"

print_test "Fetching security headers..."
HEADERS=$(curl -s -I "$BASE_URL/api/health")

# Check for required headers
check_header() {
    local header=$1
    local description=$2

    if echo "$HEADERS" | grep -qi "$header:"; then
        local value=$(echo "$HEADERS" | grep -i "$header:" | cut -d: -f2- | xargs)
        print_pass "$description: $value"
        return 0
    else
        print_fail "$description is MISSING"
        return 1
    fi
}

check_header "Strict-Transport-Security" "HSTS Header"
check_header "X-Frame-Options" "X-Frame-Options Header"
check_header "X-Content-Type-Options" "X-Content-Type-Options Header"
check_header "X-XSS-Protection" "X-XSS-Protection Header"
check_header "Content-Security-Policy" "CSP Header"
check_header "Referrer-Policy" "Referrer-Policy Header"

# Check for headers that should NOT be present
if echo "$HEADERS" | grep -qi "X-Powered-By:"; then
    print_warn "X-Powered-By header present (information disclosure)"
else
    print_pass "X-Powered-By header not present"
fi

###############################################################################
# Test 3: SQL Injection Protection
###############################################################################

print_section "3. SQL INJECTION PROTECTION TEST"

print_test "Testing SQL injection in search query..."

# Test various SQL injection patterns
sql_payloads=(
    "' OR '1'='1"
    "'; DROP TABLE watches; --"
    "' UNION SELECT * FROM users --"
    "1' AND '1'='1"
    "admin'--"
)

for payload in "${sql_payloads[@]}"; do
    response=$(curl -s "$BASE_URL/api/search?q=$(echo "$payload" | jq -sRr @uri)" || echo "ERROR")

    if echo "$response" | grep -qi "error\|syntax"; then
        if echo "$response" | grep -qi "validation"; then
            print_pass "Payload blocked: $payload"
        else
            print_warn "Payload may have caused error: $payload"
        fi
    else
        print_pass "Payload sanitized: $payload"
    fi
done

###############################################################################
# Test 4: XSS Protection
###############################################################################

print_section "4. XSS PROTECTION TEST"

print_test "Testing XSS protection..."

xss_payloads=(
    "<script>alert(1)</script>"
    "<img src=x onerror=alert(1)>"
    "javascript:alert(1)"
    "<svg onload=alert(1)>"
)

for payload in "${xss_payloads[@]}"; do
    response=$(curl -s -X POST "$BASE_URL/api/watchlist" \
        -H "Content-Type: application/json" \
        -d "{\"userId\":\"$payload\",\"watchId\":\"test\",\"action\":\"add\"}" 2>/dev/null || echo "ERROR")

    if echo "$response" | grep -q "<script>\|javascript:\|onerror"; then
        print_fail "XSS payload NOT sanitized: $payload"
    else
        print_pass "XSS payload sanitized: $payload"
    fi
done

###############################################################################
# Test 5: Rate Limiting
###############################################################################

print_section "5. RATE LIMITING TEST"

print_test "Testing API rate limiting (100 requests / 15 min)..."

success_count=0
rate_limited=false

for i in {1..120}; do
    response=$(curl -s -w "%{http_code}" -o /dev/null "$BASE_URL/api/health" 2>/dev/null)

    if [ "$response" = "200" ]; then
        ((success_count++))
    elif [ "$response" = "429" ]; then
        rate_limited=true
        break
    fi

    # Small delay to avoid overwhelming the server
    sleep 0.05
done

if [ "$rate_limited" = true ]; then
    print_pass "Rate limiting active (blocked after $success_count requests)"
else
    if [ $success_count -gt 100 ]; then
        print_warn "Rate limiting may not be working correctly ($success_count requests succeeded)"
    else
        print_info "Rate limiting not triggered ($success_count < 100 requests)"
    fi
fi

###############################################################################
# Test 6: Authentication
###############################################################################

print_section "6. AUTHENTICATION TEST"

print_test "Testing unauthenticated admin access..."
response=$(curl -s -w "%{http_code}" -o /dev/null "$BASE_URL/api/admin/security-logs")

if [ "$response" = "401" ] || [ "$response" = "403" ]; then
    print_pass "Admin endpoint requires authentication (HTTP $response)"
else
    print_fail "Admin endpoint accessible without authentication (HTTP $response)"
fi

print_test "Testing authentication with invalid credentials..."
response=$(curl -s -X POST "$BASE_URL/api/admin/login" \
    -H "Content-Type: application/json" \
    -d '{"username":"admin","password":"wrongpassword"}')

if echo "$response" | grep -qi "unauthorized\|invalid"; then
    print_pass "Invalid credentials rejected"
else
    print_fail "Authentication may be compromised"
fi

###############################################################################
# Test 7: CORS Protection
###############################################################################

print_section "7. CORS PROTECTION TEST"

print_test "Testing CORS headers..."
cors_headers=$(curl -s -I -H "Origin: http://malicious-site.com" "$BASE_URL/api/health")

if echo "$cors_headers" | grep -qi "Access-Control-Allow-Origin"; then
    allowed_origin=$(echo "$cors_headers" | grep -i "Access-Control-Allow-Origin:" | cut -d: -f2- | xargs)

    if [ "$allowed_origin" = "*" ]; then
        print_warn "CORS allows all origins (*)"
    else
        print_pass "CORS is restricted: $allowed_origin"
    fi
else
    print_info "CORS headers not present (may be configured elsewhere)"
fi

###############################################################################
# Test 8: Input Validation
###############################################################################

print_section "8. INPUT VALIDATION TEST"

print_test "Testing input validation on search endpoint..."

# Test invalid page number
response=$(curl -s "$BASE_URL/api/search?page=999999")
if echo "$response" | grep -qi "error\|validation"; then
    print_pass "Large page number handled correctly"
else
    print_info "Large page number accepted (may be valid)"
fi

# Test negative numbers
response=$(curl -s "$BASE_URL/api/search?limit=-100")
if echo "$response" | grep -qi "error\|validation\|invalid"; then
    print_pass "Negative limit rejected"
else
    print_warn "Negative limit may be accepted"
fi

# Test extremely long query
long_query=$(python3 -c "print('A' * 1000)")
response=$(curl -s "$BASE_URL/api/search?q=$long_query")
if echo "$response" | grep -qi "error\|validation\|too long"; then
    print_pass "Excessive input length handled"
else
    print_warn "Long input may not be validated"
fi

###############################################################################
# Test 9: Dependency Vulnerabilities
###############################################################################

print_section "9. DEPENDENCY VULNERABILITY SCAN"

print_test "Running npm audit..."
cd "$PROJECT_DIR"

audit_output=$(npm audit --json 2>/dev/null || echo '{"error":"audit failed"}')
vulnerabilities=$(echo "$audit_output" | jq -r '.metadata.vulnerabilities // empty' 2>/dev/null || echo "{}")

if [ "$vulnerabilities" = "{}" ]; then
    print_pass "No dependency vulnerabilities found"
else
    critical=$(echo "$vulnerabilities" | jq -r '.critical // 0')
    high=$(echo "$vulnerabilities" | jq -r '.high // 0')
    moderate=$(echo "$vulnerabilities" | jq -r '.moderate // 0')
    low=$(echo "$vulnerabilities" | jq -r '.low // 0')

    if [ "$critical" -gt 0 ]; then
        print_fail "CRITICAL vulnerabilities found: $critical"
    fi
    if [ "$high" -gt 0 ]; then
        print_fail "HIGH vulnerabilities found: $high"
    fi
    if [ "$moderate" -gt 0 ]; then
        print_warn "MODERATE vulnerabilities found: $moderate"
    fi
    if [ "$low" -gt 0 ]; then
        print_info "LOW vulnerabilities found: $low"
    fi
fi

###############################################################################
# Test 10: File Permissions
###############################################################################

print_section "10. FILE PERMISSIONS TEST"

print_test "Checking sensitive file permissions..."

check_file_perms() {
    local file=$1
    local max_perms=$2

    if [ -f "$file" ]; then
        perms=$(stat -c "%a" "$file" 2>/dev/null || stat -f "%OLp" "$file" 2>/dev/null)

        if [ "$perms" -le "$max_perms" ]; then
            print_pass "$file permissions: $perms (secure)"
        else
            print_warn "$file permissions: $perms (should be <= $max_perms)"
        fi
    else
        print_info "$file not found"
    fi
}

check_file_perms "$PROJECT_DIR/.env" 600
check_file_perms "$PROJECT_DIR/package.json" 644
check_file_perms "$PROJECT_DIR/server-enterprise-security.js" 644

###############################################################################
# Test 11: Port Exposure
###############################################################################

print_section "11. PORT EXPOSURE TEST"

print_test "Checking for exposed ports..."

listening_ports=$(netstat -tulnp 2>/dev/null | grep LISTEN || ss -tulnp 2>/dev/null | grep LISTEN)

if echo "$listening_ports" | grep -q ":$PORT "; then
    print_pass "Application port $PORT is listening"
fi

# Check for common dangerous ports
dangerous_ports=(3306 5432 27017 6379 9200)
for port in "${dangerous_ports[@]}"; do
    if echo "$listening_ports" | grep -q ":$port "; then
        print_warn "Database/service port $port is exposed"
    fi
done

###############################################################################
# Test 12: Security Configuration
###############################################################################

print_section "12. SECURITY CONFIGURATION TEST"

print_test "Checking security status endpoint..."
security_status=$(curl -s "$BASE_URL/api/security/status")

if echo "$security_status" | jq -e '.securityFeatures' > /dev/null 2>&1; then
    print_pass "Security status endpoint accessible"

    # Check individual features
    features=$(echo "$security_status" | jq -r '.securityFeatures | to_entries[] | "\(.key): \(.value)"')
    echo "$features" | while read -r line; do
        feature=$(echo "$line" | cut -d: -f1)
        status=$(echo "$line" | cut -d: -f2 | xargs)

        if [ "$status" = "true" ]; then
            print_pass "  $feature: enabled"
        else
            print_info "  $feature: $status"
        fi
    done
else
    print_warn "Security status endpoint not responding correctly"
fi

###############################################################################
# Test 13: Memory Leaks
###############################################################################

print_section "13. MEMORY USAGE TEST"

print_test "Checking memory usage..."

if command -v pm2 > /dev/null; then
    pm2_info=$(pm2 jlist 2>/dev/null | jq -r '.[] | select(.name=="omega-watches-secure" or .name=="omega-watches") | {name: .name, memory: .monit.memory, cpu: .monit.cpu}' 2>/dev/null)

    if [ -n "$pm2_info" ]; then
        memory=$(echo "$pm2_info" | jq -r '.memory')
        cpu=$(echo "$pm2_info" | jq -r '.cpu')

        # Convert memory to MB
        memory_mb=$((memory / 1024 / 1024))

        print_info "Current memory usage: ${memory_mb}MB"
        print_info "Current CPU usage: ${cpu}%"

        if [ $memory_mb -lt 500 ]; then
            print_pass "Memory usage is within acceptable range"
        else
            print_warn "High memory usage detected: ${memory_mb}MB"
        fi
    else
        print_info "PM2 process information not available"
    fi
else
    print_info "PM2 not installed, skipping memory check"
fi

###############################################################################
# Summary
###############################################################################

print_section "AUDIT SUMMARY"

total_tests=13
echo "Total test categories: $total_tests" | tee -a "$REPORT_FILE"
echo "" | tee -a "$REPORT_FILE"

# Count results
pass_count=$(grep -c "\[PASS\]" "$REPORT_FILE" || echo 0)
fail_count=$(grep -c "\[FAIL\]" "$REPORT_FILE" || echo 0)
warn_count=$(grep -c "\[WARN\]" "$REPORT_FILE" || echo 0)

echo "Results:" | tee -a "$REPORT_FILE"
echo "  Passed: $pass_count" | tee -a "$REPORT_FILE"
echo "  Failed: $fail_count" | tee -a "$REPORT_FILE"
echo "  Warnings: $warn_count" | tee -a "$REPORT_FILE"
echo "" | tee -a "$REPORT_FILE"

# Calculate score
total_checks=$((pass_count + fail_count + warn_count))
if [ $total_checks -gt 0 ]; then
    score=$((pass_count * 100 / total_checks))

    if [ $fail_count -eq 0 ]; then
        if [ $warn_count -eq 0 ]; then
            grade="A+"
            print_pass "Security Grade: $grade (Score: $score/100)"
        else
            grade="A"
            print_pass "Security Grade: $grade (Score: $score/100)"
        fi
    elif [ $fail_count -le 2 ]; then
        grade="B"
        print_warn "Security Grade: $grade (Score: $score/100)"
    else
        grade="C"
        print_fail "Security Grade: $grade (Score: $score/100)"
    fi
else
    print_info "Unable to calculate security score"
fi

echo "" | tee -a "$REPORT_FILE"
echo "Report saved to: $REPORT_FILE" | tee -a "$REPORT_FILE"

# Create latest symlink
ln -sf "$REPORT_FILE" "$REPORT_DIR/latest.txt"

echo "" | tee -a "$REPORT_FILE"
echo "View latest report: cat $REPORT_DIR/latest.txt" | tee -a "$REPORT_FILE"

###############################################################################
# Recommendations
###############################################################################

if [ $fail_count -gt 0 ] || [ $warn_count -gt 0 ]; then
    print_section "RECOMMENDATIONS"

    echo "Based on the audit results, consider the following actions:" | tee -a "$REPORT_FILE"
    echo "" | tee -a "$REPORT_FILE"

    if [ $fail_count -gt 0 ]; then
        echo "CRITICAL:" | tee -a "$REPORT_FILE"
        grep "\[FAIL\]" "$REPORT_FILE" | sed 's/\x1b\[[0-9;]*m//g' | tee -a "$REPORT_FILE"
        echo "" | tee -a "$REPORT_FILE"
    fi

    if [ $warn_count -gt 0 ]; then
        echo "WARNINGS:" | tee -a "$REPORT_FILE"
        grep "\[WARN\]" "$REPORT_FILE" | sed 's/\x1b\[[0-9;]*m//g' | tee -a "$REPORT_FILE"
        echo "" | tee -a "$REPORT_FILE"
    fi

    echo "Review the full report for detailed information." | tee -a "$REPORT_FILE"
fi

echo "" | tee -a "$REPORT_FILE"
echo "═══════════════════════════════════════════════════════════════" | tee -a "$REPORT_FILE"
echo "END OF SECURITY AUDIT" | tee -a "$REPORT_FILE"
echo "═══════════════════════════════════════════════════════════════" | tee -a "$REPORT_FILE"

exit 0