name = 'securitywatcher'; $this->tab = 'administration'; $this->version = '1.0.0'; $this->author = 'Project-Pro'; $this->need_instance = 0; $this->bootstrap = true; parent::__construct(); $this->displayName = $this->l('Security Watcher'); $this->description = $this->l('Monitors module installations and file changes, sends email alerts.'); } public function install() { return parent::install() && $this->registerHook('actionModuleInstallAfter') && $this->registerHook('actionModuleUninstallAfter') && $this->registerHook('actionModuleInstallBefore') && $this->registerHook('actionObjectModuleAddAfter') && $this->createSnapshotTable() && $this->takeSnapshot(); } public function uninstall() { return parent::uninstall(); } private function createSnapshotTable() { $sql = 'CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'securitywatcher_snapshot` ( `id` INT AUTO_INCREMENT PRIMARY KEY, `path` VARCHAR(512) NOT NULL, `hash` VARCHAR(64) NOT NULL, `size` INT NOT NULL, `mtime` INT NOT NULL, `snapshot_date` DATETIME NOT NULL, INDEX (`path`) ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8'; return Db::getInstance()->execute($sql); } public function takeSnapshot() { Db::getInstance()->execute('TRUNCATE TABLE `' . _DB_PREFIX_ . 'securitywatcher_snapshot`'); $dirs = [ _PS_MODULE_DIR_, _PS_ROOT_DIR_ . '/themes/', ]; foreach ($dirs as $dir) { $this->scanDir($dir); } return true; } private function scanDir($dir) { $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST ); $batch = []; $count = 0; foreach ($iterator as $file) { if ($file->isFile() && preg_match('/\.(php|js|tpl|html|css)$/i', $file->getFilename())) { $path = str_replace(_PS_ROOT_DIR_, '', $file->getPathname()); $batch[] = '(' . "'" . pSQL($path) . "'," . "'" . pSQL(md5_file($file->getPathname())) . "'," . (int)$file->getSize() . ',' . (int)$file->getMTime() . ',' . "NOW()" . ')'; $count++; if ($count % 500 === 0) { $this->insertBatch($batch); $batch = []; } } } if (!empty($batch)) { $this->insertBatch($batch); } } private function insertBatch($batch) { $sql = 'INSERT INTO `' . _DB_PREFIX_ . 'securitywatcher_snapshot` (path, hash, size, mtime, snapshot_date) VALUES ' . implode(',', $batch); Db::getInstance()->execute($sql); } public function hookActionModuleInstallBefore($params) { $this->sendModuleAlert('INSTALLATION ATTEMPT', $params); } public function hookActionModuleInstallAfter($params) { $this->sendModuleAlert('MODULE INSTALLED', $params); } public function hookActionModuleUninstallAfter($params) { $this->sendModuleAlert('MODULE UNINSTALLED', $params); } public function hookActionObjectModuleAddAfter($params) { if (isset($params['object'])) { $this->sendModuleAlert('MODULE ADDED TO DB', $params); } } private function sendModuleAlert($action, $params) { $moduleName = 'unknown'; if (isset($params['module']) && is_object($params['module'])) { $moduleName = $params['module']->name; } elseif (isset($params['object']) && is_object($params['object'])) { $moduleName = $params['object']->name; } $employee = Context::getContext()->employee; $employeeInfo = 'Not logged in / CLI'; $employeeEmail = 'N/A'; if ($employee && $employee->id) { $employeeInfo = $employee->firstname . ' ' . $employee->lastname . ' (ID: ' . $employee->id . ')'; $employeeEmail = $employee->email; } $ip = Tools::getRemoteAddr(); $userAgent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'N/A'; $shopUrl = Tools::getShopDomainSsl(true); $subject = "[SECURITY] {$action}: {$moduleName} @ " . Configuration::get('PS_SHOP_NAME'); $body = "
| Module | {$moduleName} |
| Action | {$action} |
| Employee | {$employeeInfo} |
| Employee Email | {$employeeEmail} |
| IP Address | {$ip} |
| User Agent | {$userAgent} |
| Date | " . date('Y-m-d H:i:s') . " |
| Shop | {$shopUrl} |
Date: " . date('Y-m-d H:i:s') . "
"; if (!empty($changes['new'])) { $body .= "