#!/bin/bash
# =============================================================================
# CVE-2026-48611 — verification of the proposed fix
# -----------------------------------------------------------------------------
# Strategy: take the VULNERABLE phpBB 3.3.16 source, apply bundle/coding/
# proposed_fix.diff, build a Docker image, and exercise the real Apache+mod_php
# service with two HTTP requests from inside the container:
#
#   TEST A (exploit, wrong password): the original CVE payload
#       POST ucp.php?mode=login_link&auth_provider=apache&login_link_aikido=1
#       Authorization: Basic base64(admin:x)
#       body: login_username=admin&login_password=x&login=Login
#     EXPECTED on PATCHED build: NO admin session (session *_u cookie stays 1,
#     anonymous), the login-link form is re-rendered with a login error. The
#     attacker can no longer steer the password-less `apache` provider.
#
#   TEST B (legitimate, correct password): the same request shape but with the
#     real admin password in the body, proving the legitimate login-link flow
#     is NOT broken by the fix:
#       POST ucp.php?mode=login_link&auth_provider=apache&login_link_aikido=1
#       Authorization: Basic base64(admin:x)   (ignored by the db provider)
#       body: login_username=admin&login_password=adminadmin&login=Login
#     EXPECTED on PATCHED build: admin session created (*_u=2) and a 302
#     redirect to index.php — i.e. correct credentials still log in.
#
#   CONTROL (optional, on the unpatched :vuln image): the original exploit
#     still hijacks admin (*_u=2). This proves the harness is sound and that the
#     patch is what makes the difference.
#
# Exit 0 = fix verified (exploit blocked on the patched build AND the legitimate
# login-link login still works). Exit 1 = fix failed or regression introduced.
# =============================================================================
set -euo pipefail

# --- paths ---
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"          # .../bundle/coding
BUNDLE="$(cd "$SCRIPT_DIR/.." && pwd)"                # .../bundle
PATCH="$SCRIPT_DIR/proposed_fix.diff"
LOGS="$BUNDLE/logs"
VERIFY_ART="$SCRIPT_DIR/verify_artifacts"
mkdir -p "$LOGS" "$VERIFY_ART"
VERIFY_LOG="$LOGS/verify_fix.log"
: > "$VERIFY_LOG"

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

# --- project cache (reuse the durable repo mirror + worktrees from the repro) ---
CTX="$BUNDLE/project_cache_context.json"
CACHE_DIR=""
if [ -f "$CTX" ]; then
  CACHE_DIR="$(jq -r '.project_cache_dir // empty' "$CTX" 2>/dev/null || true)"
fi
if [ -z "${CACHE_DIR:-}" ] || [ ! -d "${CACHE_DIR:-}" ]; then
  CACHE_DIR="$BUNDLE/artifacts/phpbb-cache"
fi
VULN_WS="$CACHE_DIR/repo"                 # vulnerable 3.3.16 worktree (READ-ONLY)
PATCHED_WS="$CACHE_DIR/repo-patched"      # our patched copy
log "project cache dir: $CACHE_DIR"

if [ ! -d "$VULN_WS/phpBB" ]; then
  log "ERROR: vulnerable worktree not found at $VULN_WS/phpBB"
  log "Run the repro script first (bundle/repro/reproduction_steps.sh) to populate the cache."
  exit 1
fi

# --- docker image / container names ---
IMG_PATCHED="phpbb-cve2026-48611:patched"
IMG_VULN="phpbb-cve2026-48611:vuln"
CT_PATCHED="phpbb-cve2026-48611-patched-verify"
CT_VULN="phpbb-cve2026-48611-vuln-verify"
TARGET_USER="admin"
ADMIN_UID="2"
ADMIN_PASS="adminadmin"   # set by install-config.yml

BASIC_CREDS="$(printf '%s' "${TARGET_USER}:x" | base64)"
EXPLOIT_BODY="login_username=${TARGET_USER}&login_password=x&login=Login"
LEGIT_BODY="login_username=${TARGET_USER}&login_password=${ADMIN_PASS}&login=Login"

u_value_from_jar() { awk -F'\t' '$6 ~ /_u$/ {print $7}' "$1" 2>/dev/null | tail -1; }

# =============================================================================
# 1. Create / refresh the patched source tree (vulnerable 3.3.16 + our patch)
# =============================================================================
needs_rebuild_source="no"
if [ ! -d "$PATCHED_WS/phpBB" ]; then
  needs_rebuild_source="yes"
elif ! grep -q "Never allow the auth_provider request parameter to steer provider" "$PATCHED_WS/phpBB/includes/ucp/ucp_login_link.php" 2>/dev/null; then
  needs_rebuild_source="yes"
fi

if [ "$needs_rebuild_source" = "yes" ]; then
  log "creating patched source tree from $VULN_WS"
  rm -rf "$PATCHED_WS"
  cp -a "$VULN_WS" "$PATCHED_WS"
  rm -rf "$PATCHED_WS/.git" 2>/dev/null || true
  ( cd "$PATCHED_WS" && patch -p1 < "$PATCH" )
  log "patch applied to patched source tree"
else
  log "patched source tree already present and patched, reusing"
fi

# Sanity: the patched source must no longer take auth_provider from the request
# in the login-link flow, and must use the board-configured provider.
if grep -q "get_provider(\$request->variable('auth_provider'" "$PATCHED_WS/phpBB/includes/ucp/ucp_login_link.php"; then
  log "ERROR: ucp_login_link.php still steers provider from the request — patch did not apply"
  exit 1
fi
if ! grep -q "\$auth_provider = \$provider_collection->get_provider();" "$PATCHED_WS/phpBB/includes/ucp/ucp_login_link.php"; then
  log "ERROR: ucp_login_link.php does not use no-arg get_provider() — patch did not apply"
  exit 1
fi

# Ensure the docker build files exist in the patched phpBB/ context (copy from
# the vuln worktree, where the repro already wrote them, or from bundle/docker).
for f in Dockerfile apache-site.conf install-config.yml; do
  if [ ! -f "$PATCHED_WS/phpBB/$f" ]; then
    cp "$VULN_WS/phpBB/$f" "$PATCHED_WS/phpBB/$f" 2>/dev/null || cp "$BUNDLE/docker/$f" "$PATCHED_WS/phpBB/$f"
  fi
done

# =============================================================================
# 2. Build the patched Docker image (idempotent)
# =============================================================================
if docker image inspect "$IMG_PATCHED" >/dev/null 2>&1; then
  log "patched image $IMG_PATCHED already present, reusing"
else
  log "building patched image $IMG_PATCHED from $PATCHED_WS/phpBB"
  docker build -t "$IMG_PATCHED" "$PATCHED_WS/phpBB" >"$LOGS/verify_build_patched.log" 2>&1
  log "built $IMG_PATCHED"
fi

# =============================================================================
# 3. Start the patched container (Apache+mod_php on :80 inside the container)
# =============================================================================
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 40); 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 -25 | tee -a "$VERIFY_LOG"
    return 1
  fi
  log "container $name healthy"
}
start_container "$IMG_PATCHED" "$CT_PATCHED"

# Confirm the patched code is really running inside the container.
docker exec "$CT_PATCHED" grep -n "get_provider();" /var/www/html/includes/ucp/ucp_login_link.php \
  | tee -a "$VERIFY_LOG" >/dev/null
log "patched ucp_login_link.php confirmed inside container (no-arg get_provider())"

# =============================================================================
# 4. TEST A — original exploit against the PATCHED build (must be blocked)
# =============================================================================
log "=== TEST A: exploit (wrong password) against PATCHED build ==="
docker exec "$CT_PATCHED" 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_a.txt \
  >"$VERIFY_ART/testA_response.txt" 2>&1 || true
docker exec "$CT_PATCHED" cat /tmp/cookies_a.txt > "$VERIFY_ART/testA_cookies.txt" 2>/dev/null || true

TESTA_U="$(u_value_from_jar "$VERIFY_ART/testA_cookies.txt")"
TESTA_ERR="$(grep -c 'class="error"' "$VERIFY_ART/testA_response.txt" 2>/dev/null || true)"
TESTA_REDIRECT="$(grep -c -i '^Location:.*index\.php' "$VERIFY_ART/testA_response.txt" 2>/dev/null || true)"
log "TEST A: session *_u = ${TESTA_U:-<none>} (admin is $ADMIN_UID); error block = ${TESTA_ERR:-0}; redirect-to-index = ${TESTA_REDIRECT:-0}"

TESTA_BLOCKED="no"
if [ "${TESTA_U:-}" != "$ADMIN_UID" ]; then
  TESTA_BLOCKED="yes"
fi

# =============================================================================
# 5. TEST B — legitimate login-link login (correct password) against PATCHED
# =============================================================================
log "=== TEST B: legitimate login (correct password) against PATCHED build ==="
docker exec "$CT_PATCHED" 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 "$LEGIT_BODY" \
  -c /tmp/cookies_b.txt \
  >"$VERIFY_ART/testB_response.txt" 2>&1 || true
docker exec "$CT_PATCHED" cat /tmp/cookies_b.txt > "$VERIFY_ART/testB_cookies.txt" 2>/dev/null || true

TESTB_U="$(u_value_from_jar "$VERIFY_ART/testB_cookies.txt")"
TESTB_REDIRECT="$(grep -c -i '^Location:.*index\.php' "$VERIFY_ART/testB_response.txt" 2>/dev/null || true)"
log "TEST B: session *_u = ${TESTB_U:-<none>} (admin is $ADMIN_UID); redirect-to-index = ${TESTB_REDIRECT:-0}"

# Confirm the TEST B session is a genuine admin session (ACP link on index page).
docker exec "$CT_PATCHED" curl -s -b /tmp/cookies_b.txt \
  "http://127.0.0.1:80/index.php" >"$VERIFY_ART/testB_index_with_session.html" 2>&1 || true
TESTB_ACP="$(grep -c "Administration Control Panel\|adm/index\.php" "$VERIFY_ART/testB_index_with_session.html" 2>/dev/null || true)"
log "TEST B: admin indicators on index page (ACP link count) = ${TESTB_ACP:-0}"

TESTB_WORKS="no"
if [ "${TESTB_U:-}" = "$ADMIN_UID" ] && [ "${TESTB_ACP:-0}" -ge 1 ]; then
  TESTB_WORKS="yes"
fi

# =============================================================================
# 6. CONTROL — original exploit against the UNPATCHED :vuln image (must hijack)
# =============================================================================
CONTROL_HIJACK="skipped"
if docker image inspect "$IMG_VULN" >/dev/null 2>&1; then
  log "=== CONTROL: exploit against UNPATCHED :vuln image ==="
  if start_container "$IMG_VULN" "$CT_VULN"; then
    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_c.txt \
      >"$VERIFY_ART/control_response.txt" 2>&1 || true
    docker exec "$CT_VULN" cat /tmp/cookies_c.txt > "$VERIFY_ART/control_cookies.txt" 2>/dev/null || true
    CONTROL_U="$(u_value_from_jar "$VERIFY_ART/control_cookies.txt")"
    log "CONTROL: session *_u = ${CONTROL_U:-<none>} (admin is $ADMIN_UID)"
    if [ "${CONTROL_U:-}" = "$ADMIN_UID" ]; then
      CONTROL_HIJACK="yes"
    else
      CONTROL_HIJACK="no"
    fi
    docker rm -f "$CT_VULN" >/dev/null 2>&1 || true
  else
    log "control container unhealthy, skipping control"
    CONTROL_HIJACK="skipped"
  fi
else
  log "CONTROL: :vuln image not present, skipping control (TEST A/B are authoritative)"
fi

# =============================================================================
# 7. Verdict
# =============================================================================
docker rm -f "$CT_PATCHED" >/dev/null 2>&1 || true

log "VERDICT: exploit_blocked_on_patched=${TESTA_BLOCKED}; legit_login_works_on_patched=${TESTB_WORKS}; control_hijack_on_unpatched=${CONTROL_HIJACK}"

jq -n \
  --arg testa_u "${TESTA_U:-}" \
  --argjson testa_blocked "$([ "$TESTA_BLOCKED" = yes ] && echo true || echo false)" \
  --argjson testa_err "${TESTA_ERR:-0}" \
  --arg testb_u "${TESTB_U:-}" \
  --argjson testb_works "$([ "$TESTB_WORKS" = yes ] && echo true || echo false)" \
  --argjson testb_acp "${TESTB_ACP:-0}" \
  --arg control "$CONTROL_HIJACK" \
  '{patched_image: "phpbb-cve2026-48611:patched",
    test_a_exploit_blocked: $testa_blocked,
    test_a_session_u: $testa_u,
    test_a_error_block_present: ($testa_err | tonumber > 0),
    test_b_legit_login_works: $testb_works,
    test_b_session_u: $testb_u,
    test_b_acp_link_count: $testb_acp,
    control_hijack_on_unpatched: $control,
    fix_verified: ($testa_blocked and $testb_works),
    proof_artifacts: [
      "logs/verify_fix.log",
      "logs/verify_build_patched.log",
      "coding/verify_artifacts/testA_response.txt",
      "coding/verify_artifacts/testA_cookies.txt",
      "coding/verify_artifacts/testB_response.txt",
      "coding/verify_artifacts/testB_cookies.txt",
      "coding/verify_artifacts/testB_index_with_session.html",
      "coding/verify_artifacts/control_response.txt",
      "coding/verify_artifacts/control_cookies.txt"
    ]}' > "$SCRIPT_DIR/verify_result.json"

if [ "$TESTA_BLOCKED" = "yes" ] && [ "$TESTB_WORKS" = "yes" ]; then
  log "RESULT: PASS — CVE-2026-48611 fix verified (exploit blocked on patched 3.3.16, legitimate login-link login still works)"
  exit 0
else
  log "RESULT: FAIL — exploit_blocked=$TESTA_BLOCKED legit_works=$TESTB_WORKS"
  exit 1
fi
