From 90193ea18c92920722f8cbb2209a413c034176b1 Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 25 Sep 2025 18:53:23 -0300 Subject: [PATCH] security: implement Phase 1 critical vulnerability fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add XSS protection with DOMPurify sanitization in rich text editor - Implement comprehensive file upload security validation - Enhance server-side content sanitization with wp_kses - Add comprehensive security test suite with 194+ test cases - Create security remediation plan documentation Security fixes address: - CRITICAL: XSS vulnerability in event description editor - HIGH: File upload security bypass for malicious files - HIGH: Enhanced CSRF protection verification - MEDIUM: Input validation and error handling improvements ๐Ÿค– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- assets/js/hvac-tec-tickets.js | 676 ++++++++++++ docs/SECURITY-REMEDIATION-PLAN.md | 485 +++++++++ includes/class-hvac-event-post-handler.php | 130 ++- includes/class-hvac-tec-tickets.php | 982 +++++++++++++++++- tests/README.md | 272 +++++ tests/evidence/framework-demo-report.json | 58 -- .../administrative-features-e2e-report.md | 278 ----- tests/global-setup.js | 35 + tests/global-teardown.js | 10 + tests/test-event-creation-security.js | 301 ++++++ tests/test-featured-image-upload.js | 509 +++++++++ tests/test-integration-comprehensive.js | 649 ++++++++++++ tests/test-modal-forms.js | 598 +++++++++++ tests/test-rich-text-editor.js | 396 +++++++ tests/test-searchable-selectors.js | 553 ++++++++++ tests/test-suite-runner.js | 547 ++++++++++ tests/test-toggle-controls.js | 560 ++++++++++ 17 files changed, 6669 insertions(+), 370 deletions(-) create mode 100644 assets/js/hvac-tec-tickets.js create mode 100644 docs/SECURITY-REMEDIATION-PLAN.md create mode 100644 tests/README.md delete mode 100644 tests/evidence/framework-demo-report.json delete mode 100644 tests/evidence/reports/administrative-features-e2e-report.md create mode 100644 tests/global-setup.js create mode 100644 tests/global-teardown.js create mode 100644 tests/test-event-creation-security.js create mode 100644 tests/test-featured-image-upload.js create mode 100644 tests/test-integration-comprehensive.js create mode 100644 tests/test-modal-forms.js create mode 100644 tests/test-rich-text-editor.js create mode 100644 tests/test-searchable-selectors.js create mode 100755 tests/test-suite-runner.js create mode 100644 tests/test-toggle-controls.js diff --git a/assets/js/hvac-tec-tickets.js b/assets/js/hvac-tec-tickets.js new file mode 100644 index 00000000..364bfa54 --- /dev/null +++ b/assets/js/hvac-tec-tickets.js @@ -0,0 +1,676 @@ +/** + * HVAC TEC Tickets Interactive JavaScript + * Handles all UI/UX interactions for the enhanced event form + */ +(function($) { + 'use strict'; + + // Global state management + window.HVACEventForm = { + selectedOrganizers: [], + selectedCategories: [], + selectedVenue: null, + richTextEditors: {}, + + init: function() { + this.initRichTextEditor(); + this.initToggleSwitches(); + this.initFeaturedImageUploader(); + this.initSearchableSelectors(); + this.initModalForms(); + this.bindEvents(); + }, + + // Rich Text Editor Functionality + initRichTextEditor: function() { + const editorWrapper = document.getElementById('event-description-editor-wrapper'); + if (!editorWrapper) return; + + const editor = document.getElementById('event-description-editor'); + const hiddenTextarea = document.getElementById('event_description'); + const toolbar = document.getElementById('event-description-toolbar'); + + if (!editor || !hiddenTextarea || !toolbar) return; + + // Initialize editor + this.richTextEditors.description = { + editor: editor, + textarea: hiddenTextarea, + toolbar: toolbar + }; + + // Bind toolbar events + $(toolbar).on('click', 'button', function(e) { + e.preventDefault(); + const command = $(this).data('command'); + const value = $(this).data('value') || null; + + document.execCommand(command, false, value); + HVACEventForm.updateToolbarState(); + HVACEventForm.syncEditorContent(); + }); + + // Sync content changes + editor.addEventListener('input', () => { + this.syncEditorContent(); + }); + + editor.addEventListener('keyup', () => { + this.updateToolbarState(); + }); + + editor.addEventListener('mouseup', () => { + this.updateToolbarState(); + }); + + // Load existing content with XSS protection + if (hiddenTextarea.value) { + const cleanContent = DOMPurify.sanitize(hiddenTextarea.value, { + ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'ul', 'ol', 'li', 'a'], + ALLOWED_ATTR: ['href', 'title'], + ALLOW_DATA_ATTR: false + }); + editor.innerHTML = cleanContent; + } + }, + + syncEditorContent: function() { + const editor = this.richTextEditors.description?.editor; + const textarea = this.richTextEditors.description?.textarea; + + if (editor && textarea) { + // Sanitize content before storing in textarea to prevent XSS + const cleanContent = DOMPurify.sanitize(editor.innerHTML, { + ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'ul', 'ol', 'li', 'a'], + ALLOWED_ATTR: ['href', 'title'], + ALLOW_DATA_ATTR: false + }); + textarea.value = cleanContent; + } + }, + + updateToolbarState: function() { + const toolbar = this.richTextEditors.description?.toolbar; + if (!toolbar) return; + + $(toolbar).find('button').each(function() { + const command = $(this).data('command'); + if (document.queryCommandState(command)) { + $(this).addClass('active'); + } else { + $(this).removeClass('active'); + } + }); + }, + + // Toggle Switch Functionality + initToggleSwitches: function() { + // Virtual Event Toggle + $(document).on('change', 'input[name="enable_virtual_event"]', function() { + const isChecked = $(this).is(':checked'); + $('.virtual-event-config').toggle(isChecked); + }); + + // RSVP Toggle + $(document).on('change', 'input[name="enable_rsvp"]', function() { + const isChecked = $(this).is(':checked'); + $('.rsvp-config').toggle(isChecked); + }); + + // Ticketing Toggle (existing functionality) + window.hvacToggleTicketFields = function(enabled) { + $('.ticket-config-field').toggle(enabled); + }; + }, + + // Featured Image Uploader + initFeaturedImageUploader: function() { + const uploadArea = document.getElementById('upload-area'); + const fileInput = document.getElementById('featured-image-file'); + const preview = document.getElementById('featured-image-preview'); + const uploader = document.getElementById('featured-image-uploader'); + const img = document.getElementById('featured-image-img'); + const changeBtn = document.getElementById('change-featured-image'); + const removeBtn = document.getElementById('remove-featured-image'); + + if (!uploadArea || !fileInput) return; + + // Click to upload + uploadArea.addEventListener('click', () => { + fileInput.click(); + }); + + // Drag and drop + uploadArea.addEventListener('dragover', (e) => { + e.preventDefault(); + uploadArea.classList.add('dragover'); + }); + + uploadArea.addEventListener('dragleave', () => { + uploadArea.classList.remove('dragover'); + }); + + uploadArea.addEventListener('drop', (e) => { + e.preventDefault(); + uploadArea.classList.remove('dragover'); + const files = e.dataTransfer.files; + if (files.length > 0) { + this.handleImageUpload(files[0]); + } + }); + + // File input change + fileInput.addEventListener('change', (e) => { + if (e.target.files.length > 0) { + this.handleImageUpload(e.target.files[0]); + } + }); + + // Change image button + if (changeBtn) { + changeBtn.addEventListener('click', () => { + fileInput.click(); + }); + } + + // Remove image button + if (removeBtn) { + removeBtn.addEventListener('click', () => { + this.removeFeaturedImage(); + }); + } + }, + + handleImageUpload: function(file) { + if (!file.type.startsWith('image/')) { + alert('Please select a valid image file.'); + return; + } + + if (file.size > 5 * 1024 * 1024) { // 5MB limit + alert('Image file size should be less than 5MB.'); + return; + } + + const reader = new FileReader(); + reader.onload = (e) => { + const img = document.getElementById('featured-image-img'); + const preview = document.getElementById('featured-image-preview'); + const uploader = document.getElementById('featured-image-uploader'); + const hiddenInput = document.getElementById('featured-image-data'); + + img.src = e.target.result; + preview.style.display = 'block'; + uploader.style.display = 'none'; + + // Store image data for form submission + if (hiddenInput) { + hiddenInput.value = e.target.result; + } + }; + reader.readAsDataURL(file); + }, + + removeFeaturedImage: function() { + const preview = document.getElementById('featured-image-preview'); + const uploader = document.getElementById('featured-image-uploader'); + const hiddenInput = document.getElementById('featured-image-data'); + const fileInput = document.getElementById('featured-image-file'); + + preview.style.display = 'none'; + uploader.style.display = 'block'; + + if (hiddenInput) hiddenInput.value = ''; + if (fileInput) fileInput.value = ''; + }, + + // Searchable Selector Components + initSearchableSelectors: function() { + this.initOrganizerSelector(); + this.initCategoriesSelector(); + this.initVenueSelector(); + }, + + initOrganizerSelector: function() { + const searchInput = document.getElementById('organizer-search-input'); + const resultsContainer = document.getElementById('organizer-search-results'); + const selectedContainer = document.getElementById('organizer-selected-items'); + const dropdownToggle = searchInput?.parentElement?.querySelector('.dropdown-toggle'); + + if (!searchInput || !resultsContainer) return; + + // Search input events + searchInput.addEventListener('focus', () => { + this.loadOrganizerOptions(); + resultsContainer.style.display = 'block'; + }); + + searchInput.addEventListener('input', (e) => { + this.filterOrganizerOptions(e.target.value); + }); + + // Dropdown toggle + if (dropdownToggle) { + dropdownToggle.addEventListener('click', () => { + if (resultsContainer.style.display === 'none') { + this.loadOrganizerOptions(); + resultsContainer.style.display = 'block'; + searchInput.focus(); + } else { + resultsContainer.style.display = 'none'; + } + }); + } + + // Click outside to close + document.addEventListener('click', (e) => { + if (!searchInput.parentElement.contains(e.target)) { + resultsContainer.style.display = 'none'; + } + }); + + // Create new organizer button + $(document).on('click', '#create-new-organizer .create-new-btn', () => { + this.openModal('#new-organizer-modal'); + }); + }, + + initCategoriesSelector: function() { + const searchInput = document.getElementById('categories-search-input'); + const resultsContainer = document.getElementById('categories-search-results'); + const dropdownToggle = searchInput?.parentElement?.querySelector('.dropdown-toggle'); + + if (!searchInput || !resultsContainer) return; + + searchInput.addEventListener('focus', () => { + this.loadCategoriesOptions(); + resultsContainer.style.display = 'block'; + }); + + searchInput.addEventListener('input', (e) => { + this.filterCategoriesOptions(e.target.value); + }); + + if (dropdownToggle) { + dropdownToggle.addEventListener('click', () => { + if (resultsContainer.style.display === 'none') { + this.loadCategoriesOptions(); + resultsContainer.style.display = 'block'; + searchInput.focus(); + } else { + resultsContainer.style.display = 'none'; + } + }); + } + + document.addEventListener('click', (e) => { + if (!searchInput.parentElement.contains(e.target)) { + resultsContainer.style.display = 'none'; + } + }); + + $(document).on('click', '#create-new-category .create-new-btn', () => { + this.openModal('#new-category-modal'); + }); + }, + + initVenueSelector: function() { + const searchInput = document.getElementById('venue-search-input'); + const resultsContainer = document.getElementById('venue-search-results'); + const dropdownToggle = searchInput?.parentElement?.querySelector('.dropdown-toggle'); + + if (!searchInput || !resultsContainer) return; + + searchInput.addEventListener('focus', () => { + this.loadVenueOptions(); + resultsContainer.style.display = 'block'; + }); + + searchInput.addEventListener('input', (e) => { + this.filterVenueOptions(e.target.value); + }); + + if (dropdownToggle) { + dropdownToggle.addEventListener('click', () => { + if (resultsContainer.style.display === 'none') { + this.loadVenueOptions(); + resultsContainer.style.display = 'block'; + searchInput.focus(); + } else { + resultsContainer.style.display = 'none'; + } + }); + } + + document.addEventListener('click', (e) => { + if (!searchInput.parentElement.contains(e.target)) { + resultsContainer.style.display = 'none'; + } + }); + + $(document).on('click', '.create-new-btn', () => { + this.openModal('#new-venue-modal'); + }); + }, + + // Load options from server + loadOrganizerOptions: function() { + // Mock data for now - replace with AJAX call + const mockOrganizers = [ + { id: 1, name: 'John Smith', email: 'john@example.com' }, + { id: 2, name: 'Jane Doe', email: 'jane@example.com' }, + { id: 3, name: 'HVAC Training Institute', email: 'info@hvactraining.com' } + ]; + + this.renderOrganizerOptions(mockOrganizers); + }, + + loadCategoriesOptions: function() { + const mockCategories = [ + { id: 1, name: 'HVAC Basics', description: 'Fundamental HVAC concepts' }, + { id: 2, name: 'Advanced Systems', description: 'Complex HVAC systems' }, + { id: 3, name: 'Safety Training', description: 'Safety protocols and procedures' } + ]; + + this.renderCategoriesOptions(mockCategories); + }, + + loadVenueOptions: function() { + const mockVenues = [ + { id: 1, name: 'Training Center A', address: '123 Main St, City, ST' }, + { id: 2, name: 'Conference Hall B', address: '456 Oak Ave, City, ST' }, + { id: 3, name: 'Community Center', address: '789 Pine Rd, City, ST' } + ]; + + this.renderVenueOptions(mockVenues); + }, + + renderOrganizerOptions: function(organizers) { + const resultsList = document.getElementById('organizer-results-list'); + if (!resultsList) return; + + resultsList.innerHTML = ''; + organizers.forEach(organizer => { + const item = document.createElement('div'); + item.className = 'search-result-item'; + item.innerHTML = `${organizer.name}
${organizer.email}`; + item.addEventListener('click', () => { + this.selectOrganizer(organizer); + }); + resultsList.appendChild(item); + }); + }, + + renderCategoriesOptions: function(categories) { + const resultsList = document.getElementById('categories-results-list'); + if (!resultsList) return; + + resultsList.innerHTML = ''; + categories.forEach(category => { + const item = document.createElement('div'); + item.className = 'search-result-item'; + item.innerHTML = `${category.name}
${category.description}`; + item.addEventListener('click', () => { + this.selectCategory(category); + }); + resultsList.appendChild(item); + }); + }, + + renderVenueOptions: function(venues) { + const resultsList = document.querySelector('#venue-search-results .results-list'); + if (!resultsList) return; + + resultsList.innerHTML = ''; + venues.forEach(venue => { + const item = document.createElement('div'); + item.className = 'search-result-item'; + item.innerHTML = `${venue.name}
${venue.address}`; + item.addEventListener('click', () => { + this.selectVenue(venue); + }); + resultsList.appendChild(item); + }); + }, + + // Selection handlers + selectOrganizer: function(organizer) { + if (this.selectedOrganizers.length >= 3) { + alert('You can only select up to 3 organizers.'); + return; + } + + if (this.selectedOrganizers.find(o => o.id === organizer.id)) { + return; // Already selected + } + + this.selectedOrganizers.push(organizer); + this.renderSelectedOrganizers(); + document.getElementById('organizer-search-results').style.display = 'none'; + document.getElementById('organizer-search-input').value = ''; + }, + + selectCategory: function(category) { + if (this.selectedCategories.length >= 3) { + alert('You can only select up to 3 categories.'); + return; + } + + if (this.selectedCategories.find(c => c.id === category.id)) { + return; + } + + this.selectedCategories.push(category); + this.renderSelectedCategories(); + document.getElementById('categories-search-results').style.display = 'none'; + document.getElementById('categories-search-input').value = ''; + }, + + selectVenue: function(venue) { + this.selectedVenue = venue; + document.getElementById('venue-search-input').value = venue.name; + document.getElementById('venue-search-results').style.display = 'none'; + }, + + renderSelectedOrganizers: function() { + const container = document.getElementById('organizer-selected-items'); + if (!container) return; + + container.innerHTML = ''; + this.selectedOrganizers.forEach((organizer, index) => { + const tag = document.createElement('div'); + tag.className = 'selected-item'; + tag.innerHTML = ` + ${organizer.name} + + `; + container.appendChild(tag); + }); + + // Update hidden input + const hiddenInput = document.getElementById('event_organizer'); + if (hiddenInput) { + hiddenInput.value = this.selectedOrganizers.map(o => o.id).join(','); + } + }, + + renderSelectedCategories: function() { + const container = document.getElementById('categories-selected-items'); + if (!container) return; + + container.innerHTML = ''; + this.selectedCategories.forEach((category, index) => { + const tag = document.createElement('div'); + tag.className = 'selected-item'; + tag.innerHTML = ` + ${category.name} + + `; + container.appendChild(tag); + }); + + const hiddenInput = document.getElementById('event_categories'); + if (hiddenInput) { + hiddenInput.value = this.selectedCategories.map(c => c.id).join(','); + } + }, + + removeOrganizer: function(index) { + this.selectedOrganizers.splice(index, 1); + this.renderSelectedOrganizers(); + }, + + removeCategory: function(index) { + this.selectedCategories.splice(index, 1); + this.renderSelectedCategories(); + }, + + // Filter functions + filterOrganizerOptions: function(query) { + // In real implementation, this would make an AJAX call + this.loadOrganizerOptions(); + }, + + filterCategoriesOptions: function(query) { + this.loadCategoriesOptions(); + }, + + filterVenueOptions: function(query) { + this.loadVenueOptions(); + }, + + // Modal Management + initModalForms: function() { + // Modal close handlers + $(document).on('click', '.hvac-modal-close, .hvac-modal-cancel', (e) => { + this.closeModal($(e.target).closest('.hvac-modal')); + }); + + // Save handlers + $(document).on('click', '#save-new-organizer', () => { + this.saveNewOrganizer(); + }); + + $(document).on('click', '#save-new-category', () => { + this.saveNewCategory(); + }); + + $(document).on('click', '#save-new-venue', () => { + this.saveNewVenue(); + }); + + // Close on backdrop click + $(document).on('click', '.hvac-modal', function(e) { + if (e.target === this) { + HVACEventForm.closeModal($(this)); + } + }); + }, + + openModal: function(modalSelector) { + $(modalSelector).fadeIn(300); + // Focus first input + setTimeout(() => { + $(modalSelector).find('input:first').focus(); + }, 300); + }, + + closeModal: function(modal) { + if (modal instanceof jQuery) { + modal.fadeOut(300); + // Clear form + modal.find('form')[0]?.reset(); + } else { + $(modal).fadeOut(300); + $(modal).find('form')[0]?.reset(); + } + }, + + saveNewOrganizer: function() { + const form = document.getElementById('new-organizer-form'); + const formData = new FormData(form); + const name = formData.get('organizer_name'); + + if (!name.trim()) { + alert('Organizer name is required.'); + return; + } + + // Create new organizer object + const newOrganizer = { + id: Date.now(), // Temporary ID + name: name, + email: formData.get('organizer_email') || '', + organization: formData.get('organizer_organization') || '' + }; + + // Add to selection + this.selectOrganizer(newOrganizer); + this.closeModal('#new-organizer-modal'); + }, + + saveNewCategory: function() { + const form = document.getElementById('new-category-form'); + const formData = new FormData(form); + const name = formData.get('category_name'); + + if (!name.trim()) { + alert('Category name is required.'); + return; + } + + const newCategory = { + id: Date.now(), + name: name, + description: formData.get('category_description') || '' + }; + + this.selectCategory(newCategory); + this.closeModal('#new-category-modal'); + }, + + saveNewVenue: function() { + const form = document.getElementById('new-venue-form'); + const formData = new FormData(form); + const name = formData.get('venue_name'); + const address = formData.get('venue_address'); + + if (!name.trim() || !address.trim()) { + alert('Venue name and address are required.'); + return; + } + + const newVenue = { + id: Date.now(), + name: name, + address: address + }; + + this.selectVenue(newVenue); + this.closeModal('#new-venue-modal'); + }, + + // Event binding + bindEvents: function() { + // Form submission + $(document).on('submit', 'form', () => { + this.syncEditorContent(); + }); + + // Auto-save functionality (if needed) + setInterval(() => { + this.syncEditorContent(); + }, 5000); + } + }; + + // Initialize when DOM is ready + $(document).ready(function() { + HVACEventForm.init(); + }); + +})(jQuery); \ No newline at end of file diff --git a/docs/SECURITY-REMEDIATION-PLAN.md b/docs/SECURITY-REMEDIATION-PLAN.md new file mode 100644 index 00000000..fc92bb0f --- /dev/null +++ b/docs/SECURITY-REMEDIATION-PLAN.md @@ -0,0 +1,485 @@ +# HVAC Community Events Security Remediation Plan + +## Executive Summary + +This document outlines a comprehensive security remediation plan to address critical vulnerabilities discovered in the HVAC Community Events plugin's event creation form through automated security testing. The plan provides a systematic approach to eliminate all identified security issues while maintaining the enhanced UI/UX functionality. + +## Vulnerability Assessment Results + +### CRITICAL VULNERABILITIES IDENTIFIED: + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ SECURITY TEST RESULTS โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ [CRITICAL] XSS Vulnerability in Rich Text Editor โ”‚ +โ”‚ - Script tags pass through unfiltered โ”‚ +โ”‚ - Malicious content stored in form data โ”‚ +โ”‚ - Risk: Session hijacking, data theft โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ [HIGH] File Upload Security Bypass โ”‚ +โ”‚ - PHP files accepted without validation โ”‚ +โ”‚ - Risk: Remote code execution โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ [HIGH] Missing CSRF Protection โ”‚ +โ”‚ - No token validation visible โ”‚ +โ”‚ - Risk: Cross-site request forgery โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ [MEDIUM] Form Validation Failures โ”‚ +โ”‚ - Required fields not enforced โ”‚ +โ”‚ - Risk: Data integrity issues โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ [MEDIUM] Security Control Gaps โ”‚ +โ”‚ - No input length limits โ”‚ +โ”‚ - Risk: DoS via large payloads โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +## Implementation Plan + +### PHASE 1: IMMEDIATE CRITICAL FIXES (URGENT) + +#### 1.1 XSS Vulnerability Remediation + +**Target Files:** +- `assets/js/hvac-tec-tickets.js` (lines 68, 77) +- `includes/class-hvac-event-form-builder.php` + +**Implementation:** + +```javascript +// CURRENT VULNERABLE CODE (hvac-tec-tickets.js): +editor.innerHTML = hiddenTextarea.value; +textarea.value = editor.innerHTML; + +// SECURE REPLACEMENT: +const cleanContent = DOMPurify.sanitize(hiddenTextarea.value, { + ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'ul', 'ol', 'li', 'a'], + ALLOWED_ATTR: ['href', 'title'], + ALLOW_DATA_ATTR: false +}); +editor.innerHTML = cleanContent; + +// Server-side sanitization backup: +textarea.value = DOMPurify.sanitize(editor.innerHTML, { + ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'ul', 'ol', 'li', 'a'], + ALLOWED_ATTR: ['href', 'title'] +}); +``` + +**Server-side Implementation:** + +```php +// Add to class-hvac-event-form-builder.php +private function sanitize_rich_text_content($content) { + $allowed_html = array( + 'p' => array(), + 'br' => array(), + 'strong' => array(), + 'em' => array(), + 'ul' => array(), + 'ol' => array(), + 'li' => array(), + 'a' => array( + 'href' => array(), + 'title' => array() + ) + ); + + return wp_kses($content, $allowed_html); +} +``` + +#### 1.2 File Upload Security Implementation + +**Target Files:** +- `includes/class-hvac-event-form-builder.php` + +**Implementation:** + +```php +private function validate_file_upload($file) { + // MIME type whitelist + $allowed_types = array( + 'image/jpeg', + 'image/png', + 'image/gif', + 'application/pdf' + ); + + // File extension whitelist + $allowed_extensions = array('jpg', 'jpeg', 'png', 'gif', 'pdf'); + + // Validate MIME type + if (!in_array($file['type'], $allowed_types)) { + return new WP_Error('invalid_file_type', + __('File type not allowed. Only JPEG, PNG, GIF, and PDF files are permitted.', 'hvac-community-events')); + } + + // Validate file extension + $file_extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); + if (!in_array($file_extension, $allowed_extensions)) { + return new WP_Error('invalid_file_extension', + __('File extension not allowed.', 'hvac-community-events')); + } + + // File size limit (5MB) + if ($file['size'] > 5 * 1024 * 1024) { + return new WP_Error('file_too_large', + __('File size exceeds 5MB limit.', 'hvac-community-events')); + } + + // Additional security checks + if ($file['error'] !== UPLOAD_ERR_OK) { + return new WP_Error('upload_error', + __('File upload failed.', 'hvac-community-events')); + } + + return true; +} + +private function handle_secure_file_upload($file) { + $validation_result = $this->validate_file_upload($file); + + if (is_wp_error($validation_result)) { + return $validation_result; + } + + // Move file to secure location outside web root + $upload_dir = wp_upload_dir(); + $secure_dir = $upload_dir['basedir'] . '/hvac-events/'; + + if (!file_exists($secure_dir)) { + wp_mkdir_p($secure_dir); + // Add .htaccess to prevent direct access + file_put_contents($secure_dir . '.htaccess', 'deny from all'); + } + + $filename = sanitize_file_name($file['name']); + $unique_filename = wp_unique_filename($secure_dir, $filename); + $file_path = $secure_dir . $unique_filename; + + if (move_uploaded_file($file['tmp_name'], $file_path)) { + return array( + 'filename' => $unique_filename, + 'path' => $file_path, + 'url' => $this->get_secure_file_url($unique_filename) + ); + } + + return new WP_Error('upload_failed', + __('Failed to save uploaded file.', 'hvac-community-events')); +} +``` + +#### 1.3 CSRF Protection Implementation + +**Target Files:** +- All template files with forms +- `includes/class-hvac-tec-tickets.php` (AJAX handlers) +- `assets/js/hvac-tec-tickets.js` + +**Template Implementation:** + +```php +// Add to all form templates + +``` + +**AJAX Handler Protection:** + +```php +// Update all AJAX handlers in class-hvac-tec-tickets.php +public function ajax_create_event_tickets(): void { + // Security check + if (!wp_verify_nonce($_POST['hvac_event_nonce'] ?? '', 'hvac_event_create')) { + wp_send_json_error(array( + 'message' => __('Security check failed. Please refresh the page and try again.', 'hvac-community-events') + )); + return; + } + + // Capability check + if (!current_user_can('edit_posts')) { + wp_send_json_error(array( + 'message' => __('You do not have permission to perform this action.', 'hvac-community-events') + )); + return; + } + + // Continue with existing logic... +} +``` + +**JavaScript Updates:** + +```javascript +// Update AJAX requests to include nonce +const formData = new FormData(); +formData.append('action', 'hvac_create_event'); +formData.append('hvac_event_nonce', document.querySelector('[name="hvac_event_nonce"]').value); +// Add other form data... + +fetch(ajaxurl, { + method: 'POST', + body: formData +}) +``` + +### PHASE 2: COMPREHENSIVE SECURITY HARDENING + +#### 2.1 Input Validation and Sanitization + +```php +private function validate_and_sanitize_input($data) { + $sanitized = array(); + + // Event title + $sanitized['post_title'] = sanitize_text_field($data['post_title'] ?? ''); + if (strlen($sanitized['post_title']) < 3) { + return new WP_Error('title_too_short', + __('Event title must be at least 3 characters.', 'hvac-community-events')); + } + + // Event description + $sanitized['post_content'] = $this->sanitize_rich_text_content($data['post_content'] ?? ''); + + // Date validation + if (!empty($data['event_date'])) { + $date = DateTime::createFromFormat('Y-m-d', $data['event_date']); + if (!$date || $date->format('Y-m-d') !== $data['event_date']) { + return new WP_Error('invalid_date', + __('Invalid event date format.', 'hvac-community-events')); + } + $sanitized['event_date'] = $date->format('Y-m-d'); + } + + return $sanitized; +} +``` + +#### 2.2 Rate Limiting Implementation + +```php +private function check_rate_limit($user_id = null) { + $user_id = $user_id ?: get_current_user_id(); + $key = 'hvac_form_submit_' . $user_id; + $attempts = get_transient($key) ?: 0; + + if ($attempts >= 10) { // 10 attempts per hour + return new WP_Error('rate_limit_exceeded', + __('Too many attempts. Please wait before trying again.', 'hvac-community-events')); + } + + set_transient($key, $attempts + 1, HOUR_IN_SECONDS); + return true; +} +``` + +#### 2.3 Enhanced Error Handling + +```php +private function handle_secure_error($error, $context = '') { + // Log error for debugging (without sensitive data) + error_log(sprintf( + '[HVAC Security] %s: %s (Context: %s)', + current_time('mysql'), + $error->get_error_message(), + $context + )); + + // Return generic error to user + return new WP_Error('security_error', + __('A security error occurred. Please try again or contact support.', 'hvac-community-events')); +} +``` + +### PHASE 3: TESTING & VALIDATION + +#### 3.1 Security Test Suite Expansion + +```javascript +// Enhanced XSS testing scenarios +const xssPayloads = [ + '', + '', + '', + 'javascript:alert(1)', + '' +]; + +// File upload security tests +const maliciousFiles = [ + { name: 'test.php', type: 'text/php' }, + { name: 'test.exe', type: 'application/x-executable' }, + { name: 'test.jsp', type: 'text/jsp' }, + { name: 'test.asp', type: 'text/asp' } +]; +``` + +#### 3.2 Automated Security Scanning + +```bash +# Add to CI/CD pipeline +npm install --save-dev @security/code-scanner +npm run security:scan -- --path=assets/js/ +npm run security:scan -- --path=includes/ +``` + +### PHASE 4: LONG-TERM SECURITY INFRASTRUCTURE + +#### 4.1 Content Security Policy Implementation + +```php +// Add to main plugin file +public function add_security_headers() { + if (is_admin() && strpos($_SERVER['REQUEST_URI'], 'hvac-events') !== false) { + header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"); + header("X-Frame-Options: SAMEORIGIN"); + header("X-Content-Type-Options: nosniff"); + header("Referrer-Policy: strict-origin-when-cross-origin"); + } +} +add_action('send_headers', array($this, 'add_security_headers')); +``` + +#### 4.2 Security Monitoring + +```php +private function log_security_event($event_type, $details) { + $log_entry = array( + 'timestamp' => current_time('mysql'), + 'event_type' => $event_type, + 'user_id' => get_current_user_id(), + 'ip_address' => $this->get_client_ip(), + 'details' => $details + ); + + // Store in dedicated security log table + global $wpdb; + $wpdb->insert( + $wpdb->prefix . 'hvac_security_log', + $log_entry + ); +} +``` + +## Implementation Workflow + +``` +PHASE 1: CRITICAL FIXES +โ”‚ +โ”œโ”€โ”€ Step 1: XSS Vulnerability Fix +โ”‚ โ”œโ”€โ”€ Update JavaScript sanitization +โ”‚ โ”œโ”€โ”€ Add server-side validation +โ”‚ โ””โ”€โ”€ Test XSS prevention +โ”‚ +โ”œโ”€โ”€ Step 2: File Upload Security +โ”‚ โ”œโ”€โ”€ Implement MIME validation +โ”‚ โ”œโ”€โ”€ Add file size limits +โ”‚ โ””โ”€โ”€ Test malicious file rejection +โ”‚ +โ”œโ”€โ”€ Step 3: CSRF Protection +โ”‚ โ”œโ”€โ”€ Add nonces to forms +โ”‚ โ”œโ”€โ”€ Update AJAX handlers +โ”‚ โ””โ”€โ”€ Test token validation +โ”‚ +โ””โ”€โ”€ Step 4: Emergency Deployment + โ”œโ”€โ”€ Deploy to staging + โ”œโ”€โ”€ Run security test suite + โ””โ”€โ”€ Production deployment + +PHASE 2: SECURITY HARDENING +โ”‚ +โ”œโ”€โ”€ Input validation enhancement +โ”œโ”€โ”€ Rate limiting implementation +โ”œโ”€โ”€ Error handling improvement +โ””โ”€โ”€ Security monitoring setup + +PHASE 3: TESTING VALIDATION +โ”‚ +โ”œโ”€โ”€ Automated security testing +โ”œโ”€โ”€ Penetration test validation +โ”œโ”€โ”€ Security regression tests +โ””โ”€โ”€ Documentation updates + +PHASE 4: INFRASTRUCTURE +โ”‚ +โ”œโ”€โ”€ Content Security Policy +โ”œโ”€โ”€ Security headers +โ”œโ”€โ”€ Audit procedures +โ””โ”€โ”€ Vulnerability management +``` + +## Success Metrics + +### Security Validation Criteria: + +- [ ] Zero XSS vulnerabilities detected in automated scans +- [ ] All malicious file upload attempts blocked (100% success rate) +- [ ] CSRF tokens validated on all form submissions +- [ ] Input validation prevents injection attacks +- [ ] Rate limiting blocks excessive requests +- [ ] Security headers properly configured +- [ ] Error handling prevents information disclosure +- [ ] Comprehensive test suite achieves 95%+ coverage + +### Monitoring and Maintenance: + +- [ ] Security event logging operational +- [ ] Weekly automated security scans scheduled +- [ ] Quarterly penetration testing planned +- [ ] Security patch deployment process established +- [ ] Incident response procedures documented + +## Branch Strategy + +```bash +# Create security fix branch +git checkout -b security/critical-vulnerabilities-fix + +# Development workflow +git add includes/class-hvac-event-form-builder.php +git add assets/js/hvac-tec-tickets.js +git add includes/class-hvac-tec-tickets.php +git commit -m "fix: implement critical security vulnerability fixes + +- Add XSS sanitization to rich text editor +- Implement file upload validation and security controls +- Add CSRF token protection to all forms +- Enhance input validation and rate limiting + +Fixes: XSS, File Upload Bypass, CSRF, Input Validation" + +# Deploy to staging for validation +scripts/deploy.sh staging + +# After security validation passes +git checkout main +git merge security/critical-vulnerabilities-fix +scripts/deploy.sh production +``` + +## Dependencies and Prerequisites + +### Required Libraries: +- DOMPurify (for client-side sanitization) +- WordPress wp_kses (server-side sanitization) +- WordPress nonce system (CSRF protection) + +### Testing Requirements: +- Playwright test framework (already configured) +- Security scanner integration +- Staging environment access + +### Documentation Updates: +- Security procedures documentation +- Developer security guidelines +- User security awareness materials + +--- + +**Document Status:** Planning Complete +**Next Action:** Begin Phase 1 implementation +**Priority:** URGENT - Critical security vulnerabilities require immediate attention +**Estimated Total Implementation:** 4-week phased approach \ No newline at end of file diff --git a/includes/class-hvac-event-post-handler.php b/includes/class-hvac-event-post-handler.php index 3a1af182..65304244 100644 --- a/includes/class-hvac-event-post-handler.php +++ b/includes/class-hvac-event-post-handler.php @@ -174,12 +174,12 @@ class HVAC_Event_Post_Handler { * @return int|WP_Error Post ID on success, WP_Error on failure */ public function create_event(array $data) { - // Prepare post data + // Prepare post data with security sanitization $post_data = [ 'post_type' => 'tribe_events', - 'post_title' => $data['event_title'] ?? '', - 'post_content' => $data['event_description'] ?? '', - 'post_excerpt' => $data['event_excerpt'] ?? '', + 'post_title' => sanitize_text_field($data['event_title'] ?? ''), + 'post_content' => $this->sanitize_rich_text_content($data['event_description'] ?? ''), + 'post_excerpt' => sanitize_textarea_field($data['event_excerpt'] ?? ''), 'post_status' => $this->get_post_status_for_user(), 'post_author' => get_current_user_id(), 'meta_input' => $this->prepare_meta_fields($data), @@ -233,11 +233,11 @@ class HVAC_Event_Post_Handler { return new WP_Error('permission_denied', 'You do not have permission to edit this event.'); } - // Prepare post data + // Prepare post data with security sanitization $post_data = [ 'ID' => $event_id, - 'post_title' => $data['event_title'] ?? '', - 'post_content' => $data['event_description'] ?? '', + 'post_title' => sanitize_text_field($data['event_title'] ?? ''), + 'post_content' => $this->sanitize_rich_text_content($data['event_description'] ?? ''), 'post_excerpt' => $data['event_excerpt'] ?? '', ]; @@ -463,9 +463,16 @@ class HVAC_Event_Post_Handler { $file = $_FILES['event_featured_image']; - // Validate file type - $allowed_types = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp']; - if (!in_array($file['type'], $allowed_types)) { + // Enhanced security validation for file uploads + $validation_result = $this->validate_file_upload($file); + if (is_wp_error($validation_result)) { + // Log security violation + error_log(sprintf( + '[HVAC Security] File upload blocked: %s (User: %d, File: %s)', + $validation_result->get_error_message(), + get_current_user_id(), + $file['name'] + )); return; } @@ -668,4 +675,107 @@ class HVAC_Event_Post_Handler { wp_send_json_error(['message' => 'An error occurred while updating the event']); } } + + /** + * Sanitize rich text content to prevent XSS attacks + * + * @param string $content Raw HTML content from rich text editor + * @return string Sanitized HTML content + */ + private function sanitize_rich_text_content(string $content): string { + // Define allowed HTML tags and attributes for event descriptions + $allowed_html = array( + 'p' => array(), + 'br' => array(), + 'strong' => array(), + 'em' => array(), + 'ul' => array(), + 'ol' => array(), + 'li' => array(), + 'a' => array( + 'href' => array(), + 'title' => array(), + 'target' => array() + ), + 'h3' => array(), + 'h4' => array(), + 'h5' => array(), + 'blockquote' => array() + ); + + // Use WordPress wp_kses for server-side sanitization + return wp_kses($content, $allowed_html); + } + + /** + * Validate file upload for security + * + * @param array $file WordPress $_FILES array element + * @return bool|WP_Error True on success, WP_Error on failure + */ + private function validate_file_upload(array $file) { + // MIME type whitelist - images and PDFs only + $allowed_types = array( + 'image/jpeg', + 'image/png', + 'image/gif', + 'image/webp' + ); + + // File extension whitelist + $allowed_extensions = array('jpg', 'jpeg', 'png', 'gif', 'webp'); + + // Check for upload errors + if ($file['error'] !== UPLOAD_ERR_OK) { + return new WP_Error('upload_error', + __('File upload failed with error code: ' . $file['error'], 'hvac-community-events')); + } + + // Validate MIME type + if (!in_array($file['type'], $allowed_types)) { + return new WP_Error('invalid_file_type', + __('File type not allowed. Only JPEG, PNG, GIF, and WebP images are permitted.', 'hvac-community-events')); + } + + // Validate file extension (double-check against spoofed MIME types) + $file_extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); + if (!in_array($file_extension, $allowed_extensions)) { + return new WP_Error('invalid_file_extension', + __('File extension not allowed.', 'hvac-community-events')); + } + + // File size limit (5MB maximum) + $max_size = 5 * 1024 * 1024; // 5MB in bytes + if ($file['size'] > $max_size) { + return new WP_Error('file_too_large', + __('File size exceeds 5MB limit. Please choose a smaller image.', 'hvac-community-events')); + } + + // Check for malicious file content using getimagesize for images + if (in_array($file_extension, ['jpg', 'jpeg', 'png', 'gif', 'webp'])) { + $image_info = @getimagesize($file['tmp_name']); + if ($image_info === false) { + return new WP_Error('invalid_image', + __('File appears to be corrupted or is not a valid image.', 'hvac-community-events')); + } + + // Verify MIME type matches actual image type + $actual_mime = $image_info['mime']; + if ($actual_mime !== $file['type']) { + return new WP_Error('mime_mismatch', + __('File type mismatch detected. Upload blocked for security.', 'hvac-community-events')); + } + } + + // Additional security: Check for embedded PHP or script content in image files + $file_content = file_get_contents($file['tmp_name'], false, null, 0, 1024); // Read first 1KB + if (stripos($file_content, 'add_field([ + 'type' => 'custom', + 'name' => 'featured_image_section', + 'custom_html' => '', + 'wrapper_class' => 'form-section featured-image-wrapper' + ]); + + // Virtual Event Section + $form_builder->add_field([ + 'type' => 'custom', + 'name' => 'virtual_event_section', + 'custom_html' => '
+

Event Type

+
', + 'wrapper_class' => 'form-section virtual-event-wrapper' + ]); + + // Virtual Event Toggle + $form_builder->add_field([ + 'type' => 'custom', + 'name' => 'enable_virtual_event', + 'custom_html' => '
+ +
+ Virtual Event +

Enable virtual event capabilities with online meeting integration

+
+
', + 'wrapper_class' => 'form-row toggle-field virtual-event-toggle' + ]); + + // Virtual Event Configuration (initially hidden) + $form_builder->add_field([ + 'type' => 'custom', + 'name' => 'virtual_config_start', + 'custom_html' => '', + 'wrapper_class' => '' + ]); + // Ticket section header $form_builder->add_field([ 'type' => 'custom', 'name' => 'ticket_section_header', - 'custom_html' => '

Event Ticketing

', - 'wrapper_class' => 'form-section ticket-section' + 'custom_html' => '
+

Event Ticketing

+
', + 'wrapper_class' => 'form-section ticket-section-wrapper' ]); - // Enable ticketing checkbox + // Enable ticketing toggle $form_builder->add_field([ - 'type' => 'checkbox', + 'type' => 'custom', 'name' => 'enable_ticketing', - 'label' => 'Enable Ticketing', - 'description' => 'Create tickets for this event with pricing and attendee collection', - 'value' => '1', - 'wrapper_class' => 'form-row enable-ticketing-row', - 'onchange' => 'hvacToggleTicketFields(this.checked)' + 'custom_html' => '
+ +
+ Enable Ticketing +

Create tickets for this event with pricing and attendee collection

+
+
', + 'wrapper_class' => 'form-row toggle-field ticketing-toggle' ]); - // Ticket configuration container (initially hidden) + // Ticket configuration container (visible by default since checkbox is checked) $form_builder->add_field([ 'type' => 'custom', 'name' => 'ticket_config_start', - 'custom_html' => '