Security Best Practices
2.5.x ✅ 4.0.x ✅
This document provides comprehensive security best practices for XOOPS module developers. Following these guidelines will help ensure that your modules are secure and do not introduce vulnerabilities into XOOPS installations.
Security Principles
Section titled “Security Principles”Every XOOPS developer should follow these fundamental security principles:
- Defense in Depth: Implement multiple layers of security controls
- Least Privilege: Provide only the minimum necessary access rights
- Input Validation: Never trust user input
- Secure by Default: Security should be the default configuration
- Keep It Simple: Complex systems are harder to secure
Related Documentation
Section titled “Related Documentation”- CSRF-Protection - Token system and XoopsSecurity class
- Input-Sanitization - MyTextSanitizer and validation
- SQL-Injection-Prevention - Database security practices
Quick Reference Checklist
Section titled “Quick Reference Checklist”Before releasing your module, verify:
- All forms include XOOPS tokens
- All user input is validated and sanitized
- All output is properly escaped
- All database queries use parameterized statements
- File uploads are properly validated
- Authentication and authorization checks are in place
- Error handling does not reveal sensitive information
- Sensitive configuration is protected
- Third-party libraries are up to date
- Security testing has been performed
Authentication and Authorization
Section titled “Authentication and Authorization”Checking User Authentication
Section titled “Checking User Authentication”// Check if user is logged inif (!is_object($GLOBALS['xoopsUser'])) { redirect_header(XOOPS_URL, 3, _NOPERM); exit();}Checking User Permissions
Section titled “Checking User Permissions”// Check if user has permission to access this moduleif (!$GLOBALS['xoopsUser']->isAdmin($xoopsModule->mid())) { redirect_header(XOOPS_URL, 3, _NOPERM); exit();}
// Check specific permission$moduleHandler = xoops_getHandler('module');$module = $moduleHandler->getByDirname('mymodule');$moduleperm_handler = xoops_getHandler('groupperm');$groups = $GLOBALS['xoopsUser']->getGroups();
if (!$moduleperm_handler->checkRight('mymodule_view', $item_id, $groups, $module->getVar('mid'))) { redirect_header(XOOPS_URL, 3, _NOPERM); exit();}Setting Up Module Permissions
Section titled “Setting Up Module Permissions”// Create permission in install/update function$gpermHandler = xoops_getHandler('groupperm');$gpermHandler->deleteByModule($module->getVar('mid'), 'mymodule_view');
// Add permission for all groups$groups = [XOOPS_GROUP_ADMIN, XOOPS_GROUP_USERS, XOOPS_GROUP_ANONYMOUS];foreach ($groups as $group_id) { $gpermHandler->addRight('mymodule_view', 1, $group_id, $module->getVar('mid'));}Session Security
Section titled “Session Security”Session Handling Best Practices
Section titled “Session Handling Best Practices”- Do not store sensitive information in the session
- Regenerate session IDs after login/privilege changes
- Validate session data before using it
// Regenerate session ID after loginsession_regenerate_id(true);
// Validate session dataif (isset($_SESSION['mymodule_user_id'])) { $user_id = (int)$_SESSION['mymodule_user_id']; // Verify user exists in database}Preventing Session Fixation
Section titled “Preventing Session Fixation”// After successful loginsession_regenerate_id(true);$_SESSION['mymodule_user_ip'] = $_SERVER['REMOTE_ADDR'];
// On subsequent requestsif ($_SESSION['mymodule_user_ip'] !== $_SERVER['REMOTE_ADDR']) { // Possible session hijacking attempt session_destroy(); redirect_header('index.php', 3, 'Session error'); exit();}File Upload Security
Section titled “File Upload Security”Validating File Uploads
Section titled “Validating File Uploads”// Check if file was uploaded properlyif (!isset($_FILES['userfile']) || $_FILES['userfile']['error'] != UPLOAD_ERR_OK) { redirect_header('index.php', 3, 'File upload error'); exit();}
// Check file sizeif ($_FILES['userfile']['size'] > 1000000) { // 1MB limit redirect_header('index.php', 3, 'File too large'); exit();}
// Check file type$allowed_types = ['image/jpeg', 'image/png', 'image/gif'];if (!in_array($_FILES['userfile']['type'], $allowed_types)) { redirect_header('index.php', 3, 'Invalid file type'); exit();}
// Validate file extension$filename = $_FILES['userfile']['name'];$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));$allowed_extensions = ['jpg', 'jpeg', 'png', 'gif'];if (!in_array($ext, $allowed_extensions)) { redirect_header('index.php', 3, 'Invalid file extension'); exit();}Using XOOPS Uploader
Section titled “Using XOOPS Uploader”include_once XOOPS_ROOT_PATH . '/class/uploader.php';
$allowed_mimetypes = ['image/gif', 'image/jpeg', 'image/png'];$maxsize = 1000000; // 1MB$maxwidth = 1024;$maxheight = 768;$upload_dir = XOOPS_ROOT_PATH . '/uploads/mymodule';
$uploader = new XoopsMediaUploader( $upload_dir, $allowed_mimetypes, $maxsize, $maxwidth, $maxheight);
if ($uploader->fetchMedia('userfile')) { $uploader->setPrefix('mymodule_');
if ($uploader->upload()) { $filename = $uploader->getSavedFileName(); // Save filename to database } else { echo $uploader->getErrors(); }} else { echo $uploader->getErrors();}Storing Uploaded Files Securely
Section titled “Storing Uploaded Files Securely”// Define upload directory outside web root$upload_dir = XOOPS_VAR_PATH . '/uploads/mymodule';
// Create directory if it doesn't existif (!is_dir($upload_dir)) { mkdir($upload_dir, 0755, true);}
// Move uploaded filemove_uploaded_file($_FILES['userfile']['tmp_name'], $upload_dir . '/' . $safe_filename);Error Handling and Logging
Section titled “Error Handling and Logging”Secure Error Handling
Section titled “Secure Error Handling”try { $result = someFunction(); if (!$result) { throw new Exception('Operation failed'); }} catch (Exception $e) { // Log the error xoops_error($e->getMessage());
// Display a generic error message to the user redirect_header('index.php', 3, 'An error occurred. Please try again later.'); exit();}Logging Security Events
Section titled “Logging Security Events”// Log security eventsxoops_loadLanguage('logger', 'mymodule');$GLOBALS['xoopsLogger']->addExtra('Security', 'Failed login attempt for user: ' . $username);Configuration Security
Section titled “Configuration Security”Storing Sensitive Configuration
Section titled “Storing Sensitive Configuration”// Define configuration path outside web root$config_path = XOOPS_VAR_PATH . '/configs/mymodule/config.php';
// Load configurationif (file_exists($config_path)) { include $config_path;} else { // Handle missing configuration}Protecting Configuration Files
Section titled “Protecting Configuration Files”Use .htaccess to protect configuration files:
# In .htaccess<Files "config.php"> Order Allow,Deny Deny from all</Files>Third-Party Libraries
Section titled “Third-Party Libraries”Selecting Libraries
Section titled “Selecting Libraries”- Choose actively maintained libraries
- Check for security vulnerabilities
- Verify the library’s license is compatible with XOOPS
Updating Libraries
Section titled “Updating Libraries”// Check library versionif (version_compare(LIBRARY_VERSION, '1.2.3', '<')) { xoops_error('Please update the library to version 1.2.3 or higher');}Isolating Libraries
Section titled “Isolating Libraries”// Load library in a controlled wayfunction loadLibrary($file){ $allowed = ['parser.php', 'formatter.php'];
if (!in_array($file, $allowed)) { return false; }
include_once XOOPS_ROOT_PATH . '/modules/mymodule/libraries/' . $file; return true;}Security Testing
Section titled “Security Testing”Manual Testing Checklist
Section titled “Manual Testing Checklist”- Test all forms with invalid input
- Attempt to bypass authentication and authorization
- Test file upload functionality with malicious files
- Check for XSS vulnerabilities in all output
- Test for SQL injection in all database queries
Automated Testing
Section titled “Automated Testing”Use automated tools to scan for vulnerabilities:
- Static code analysis tools
- Web application scanners
- Dependency checkers for third-party libraries
Output Escaping
Section titled “Output Escaping”HTML Context
Section titled “HTML Context”// For regular HTML contentecho htmlspecialchars($variable, ENT_QUOTES, 'UTF-8');
// Using MyTextSanitizer$myts = MyTextSanitizer::getInstance();echo $myts->htmlSpecialChars($variable);JavaScript Context
Section titled “JavaScript Context”// For data used in JavaScriptecho json_encode($variable);
// For inline JavaScriptecho 'var data = ' . json_encode($variable) . ';';URL Context
Section titled “URL Context”// For data used in URLsecho htmlspecialchars(urlencode($variable), ENT_QUOTES, 'UTF-8');Template Variables
Section titled “Template Variables”// Assign variables to Smarty template$GLOBALS['xoopsTpl']->assign('title', htmlspecialchars($title, ENT_QUOTES, 'UTF-8'));
// For HTML content that should be displayed as-is$GLOBALS['xoopsTpl']->assign('content', $myts->displayTarea($content, 1, 1, 1, 1, 1));Resources
Section titled “Resources”#security #best-practices #xoops #module-development #authentication #authorization