Changeset 3479541
- Timestamp:
- 03/10/2026 08:52:04 PM (3 weeks ago)
- Location:
- vulntitan
- Files:
-
- 12 edited
- 5 copied
-
tags/2.0.3 (copied) (copied from vulntitan/trunk)
-
tags/2.0.3/CHANGELOG.md (copied) (copied from vulntitan/trunk/CHANGELOG.md) (1 diff)
-
tags/2.0.3/assets/js/malware-scanner.js (modified) (2 diffs)
-
tags/2.0.3/assets/js/malware-scanner.min.js (modified) (2 diffs)
-
tags/2.0.3/includes/Admin/Admin.php (modified) (1 diff)
-
tags/2.0.3/includes/Admin/Ajax.php (modified) (2 diffs)
-
tags/2.0.3/includes/Services/MalwareScanner.php (copied) (copied from vulntitan/trunk/includes/Services/MalwareScanner.php) (5 diffs)
-
tags/2.0.3/readme.txt (copied) (copied from vulntitan/trunk/readme.txt) (2 diffs)
-
tags/2.0.3/vulntitan.php (copied) (copied from vulntitan/trunk/vulntitan.php) (2 diffs)
-
trunk/CHANGELOG.md (modified) (1 diff)
-
trunk/assets/js/malware-scanner.js (modified) (2 diffs)
-
trunk/assets/js/malware-scanner.min.js (modified) (2 diffs)
-
trunk/includes/Admin/Admin.php (modified) (1 diff)
-
trunk/includes/Admin/Ajax.php (modified) (2 diffs)
-
trunk/includes/Services/MalwareScanner.php (modified) (5 diffs)
-
trunk/readme.txt (modified) (2 diffs)
-
trunk/vulntitan.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
vulntitan/tags/2.0.3/CHANGELOG.md
r3479449 r3479541 5 5 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 6 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 8 ## [2.0.3] - 2026-03-10 9 ### Fixed 10 - Reduced false positives for benign decode-only utilities (e.g., base64 + gzuncompress). 11 - Reduced false positives for safe `data:image/svg+xml;base64` payloads. 12 - Disabled auto-fix for low-risk malware findings to prevent accidental code removal. 7 13 8 14 ## [2.0.2] - 2026-03-10 -
vulntitan/tags/2.0.3/assets/js/malware-scanner.js
r3469591 r3479541 195 195 const applyFixLabel = VulnTitan.i18n.malware_fix_apply || 'Apply Fix'; 196 196 const fixedLabel = VulnTitan.i18n.malware_fixed || 'Fixed'; 197 const fixDisabled = finding.fixed || !lineNumber; 198 const fixStatus = finding.fix_message ? `<span class="vulntitan-malware-fix-feedback" style="font-size:11px; color:${finding.fix_error ? '#fca5a5' : '#86efac'};">${escapeHtml(finding.fix_message)}</span>` : '<span class="vulntitan-malware-fix-feedback" style="font-size:11px; color:#94a3b8;"></span>'; 199 200 return ` 201 <div style="border:1px solid #3b2020; border-radius:6px; background:#161017; padding:8px 10px;"> 202 <div style="display:flex; align-items:center; justify-content:space-between; gap:8px; flex-wrap:wrap;"> 203 <span style="font-size:12px; color:#fda4af; font-weight:600;"> 204 #${findingIndex + 1} • ${escapeHtml(lineLabel)} 205 </span> 206 <span style="display:inline-block; padding:2px 8px; border:1px solid ${severityColor}; border-radius:999px; font-size:11px; color:${severityColor}; font-weight:700; letter-spacing:0.03em;"> 207 ${escapeHtml(severityLabel)} 208 </span> 209 </div> 210 ${patterns ? `<div style="margin-top:6px; font-size:11px; color:#cbd5e1;">${escapeHtml(patterns)}</div>` : ''} 211 ${renderFindingPreviewLines(finding)} 212 <div class="vulntitan-malware-fix-actions" style="margin-top:8px; display:flex; align-items:center; gap:8px; flex-wrap:wrap;"> 197 const fixAllowed = finding.fixable === true; 198 const fixUnavailableLabel = VulnTitan.i18n.malware_fix_unavailable || 'Auto-fix not available for low-risk findings.'; 199 const fixDisabled = finding.fixed || !lineNumber || !fixAllowed; 200 const fixStatus = finding.fix_message 201 ? `<span class="vulntitan-malware-fix-feedback" style="font-size:11px; color:${finding.fix_error ? '#fca5a5' : '#86efac'};">${escapeHtml(finding.fix_message)}</span>` 202 : (fixAllowed 203 ? '<span class="vulntitan-malware-fix-feedback" style="font-size:11px; color:#94a3b8;"></span>' 204 : `<span class="vulntitan-malware-fix-feedback" style="font-size:11px; color:#94a3b8;">${escapeHtml(fixUnavailableLabel)}</span>`); 205 const fixButton = fixAllowed ? ` 213 206 <button 214 207 type="button" … … 221 214 ${escapeHtml(finding.fixed ? fixedLabel : applyFixLabel)} 222 215 </button> 216 ` : ''; 217 218 return ` 219 <div style="border:1px solid #3b2020; border-radius:6px; background:#161017; padding:8px 10px;"> 220 <div style="display:flex; align-items:center; justify-content:space-between; gap:8px; flex-wrap:wrap;"> 221 <span style="font-size:12px; color:#fda4af; font-weight:600;"> 222 #${findingIndex + 1} • ${escapeHtml(lineLabel)} 223 </span> 224 <span style="display:inline-block; padding:2px 8px; border:1px solid ${severityColor}; border-radius:999px; font-size:11px; color:${severityColor}; font-weight:700; letter-spacing:0.03em;"> 225 ${escapeHtml(severityLabel)} 226 </span> 227 </div> 228 ${patterns ? `<div style="margin-top:6px; font-size:11px; color:#cbd5e1;">${escapeHtml(patterns)}</div>` : ''} 229 ${renderFindingPreviewLines(finding)} 230 <div class="vulntitan-malware-fix-actions" style="margin-top:8px; display:flex; align-items:center; gap:8px; flex-wrap:wrap;"> 231 ${fixButton} 223 232 ${fixStatus} 224 233 </div> -
vulntitan/tags/2.0.3/assets/js/malware-scanner.min.js
r3469591 r3479541 195 195 const applyFixLabel = VulnTitan.i18n.malware_fix_apply || 'Apply Fix'; 196 196 const fixedLabel = VulnTitan.i18n.malware_fixed || 'Fixed'; 197 const fixDisabled = finding.fixed || !lineNumber; 198 const fixStatus = finding.fix_message ? `<span class="vulntitan-malware-fix-feedback" style="font-size:11px; color:${finding.fix_error ? '#fca5a5' : '#86efac'};">${escapeHtml(finding.fix_message)}</span>` : '<span class="vulntitan-malware-fix-feedback" style="font-size:11px; color:#94a3b8;"></span>'; 199 200 return ` 201 <div style="border:1px solid #3b2020; border-radius:6px; background:#161017; padding:8px 10px;"> 202 <div style="display:flex; align-items:center; justify-content:space-between; gap:8px; flex-wrap:wrap;"> 203 <span style="font-size:12px; color:#fda4af; font-weight:600;"> 204 #${findingIndex + 1} • ${escapeHtml(lineLabel)} 205 </span> 206 <span style="display:inline-block; padding:2px 8px; border:1px solid ${severityColor}; border-radius:999px; font-size:11px; color:${severityColor}; font-weight:700; letter-spacing:0.03em;"> 207 ${escapeHtml(severityLabel)} 208 </span> 209 </div> 210 ${patterns ? `<div style="margin-top:6px; font-size:11px; color:#cbd5e1;">${escapeHtml(patterns)}</div>` : ''} 211 ${renderFindingPreviewLines(finding)} 212 <div class="vulntitan-malware-fix-actions" style="margin-top:8px; display:flex; align-items:center; gap:8px; flex-wrap:wrap;"> 197 const fixAllowed = finding.fixable === true; 198 const fixUnavailableLabel = VulnTitan.i18n.malware_fix_unavailable || 'Auto-fix not available for low-risk findings.'; 199 const fixDisabled = finding.fixed || !lineNumber || !fixAllowed; 200 const fixStatus = finding.fix_message 201 ? `<span class="vulntitan-malware-fix-feedback" style="font-size:11px; color:${finding.fix_error ? '#fca5a5' : '#86efac'};">${escapeHtml(finding.fix_message)}</span>` 202 : (fixAllowed 203 ? '<span class="vulntitan-malware-fix-feedback" style="font-size:11px; color:#94a3b8;"></span>' 204 : `<span class="vulntitan-malware-fix-feedback" style="font-size:11px; color:#94a3b8;">${escapeHtml(fixUnavailableLabel)}</span>`); 205 const fixButton = fixAllowed ? ` 213 206 <button 214 207 type="button" … … 221 214 ${escapeHtml(finding.fixed ? fixedLabel : applyFixLabel)} 222 215 </button> 216 ` : ''; 217 218 return ` 219 <div style="border:1px solid #3b2020; border-radius:6px; background:#161017; padding:8px 10px;"> 220 <div style="display:flex; align-items:center; justify-content:space-between; gap:8px; flex-wrap:wrap;"> 221 <span style="font-size:12px; color:#fda4af; font-weight:600;"> 222 #${findingIndex + 1} • ${escapeHtml(lineLabel)} 223 </span> 224 <span style="display:inline-block; padding:2px 8px; border:1px solid ${severityColor}; border-radius:999px; font-size:11px; color:${severityColor}; font-weight:700; letter-spacing:0.03em;"> 225 ${escapeHtml(severityLabel)} 226 </span> 227 </div> 228 ${patterns ? `<div style="margin-top:6px; font-size:11px; color:#cbd5e1;">${escapeHtml(patterns)}</div>` : ''} 229 ${renderFindingPreviewLines(finding)} 230 <div class="vulntitan-malware-fix-actions" style="margin-top:8px; display:flex; align-items:center; gap:8px; flex-wrap:wrap;"> 231 ${fixButton} 223 232 ${fixStatus} 224 233 </div> -
vulntitan/tags/2.0.3/includes/Admin/Admin.php
r3469591 r3479541 241 241 'malware_fix_confirm' => esc_html__('Apply safe fix for this suspicious line? A backup will be created.', 'vulntitan'), 242 242 'malware_fix_no_line' => esc_html__('This finding has no exact line, so auto-fix is not available.', 'vulntitan'), 243 'malware_fix_unavailable' => esc_html__('Auto-fix is disabled for low-risk findings.', 'vulntitan'), 243 244 'malware_fix_success' => esc_html__('Fix applied.', 'vulntitan'), 244 245 'malware_fix_error' => esc_html__('Auto-fix failed.', 'vulntitan'), -
vulntitan/tags/2.0.3/includes/Admin/Ajax.php
r3469591 r3479541 229 229 } 230 230 231 if (!$this->isFixablePatternList($patterns)) { 232 wp_send_json_error(['message' => esc_html__('Auto-fix is disabled for low-risk findings.', 'vulntitan')]); 233 } 234 231 235 $relativePath = ltrim(str_replace('\\', '/', $filePath), '/'); 232 236 $absolutePath = ABSPATH . $relativePath; … … 421 425 422 426 return sprintf('/* VulnTitan quarantine: suspicious code removed at line %d%s */', $lineNumber, $suffix); 427 } 428 429 protected function isFixablePatternList(string $patterns): bool 430 { 431 $items = array_filter(array_map('trim', explode(',', strtolower($patterns)))); 432 if (empty($items)) { 433 return false; 434 } 435 436 $fixablePatterns = [ 437 'eval', 'assert', 'create_function', 'preg_replace_eval', 438 'system', 'exec', 'shell_exec', 'passthru', 'popen', 'proc_open', 439 'include_user_input', 'file_put_contents', 'fwrite', 'move_uploaded_file', 440 'js_function_ctor', 'js_eval_accessor', 'js_timer_string', 441 'eval_base64_chain', 'decode_execution_chain', 'user_input_execution_chain', 442 'encoded_file_write_chain', 'upload_input_chain', 443 ]; 444 445 foreach ($items as $pattern) { 446 if (in_array($pattern, $fixablePatterns, true)) { 447 return true; 448 } 449 } 450 451 return false; 423 452 } 424 453 -
vulntitan/tags/2.0.3/includes/Services/MalwareScanner.php
r3479449 r3479541 151 151 $hasExecution = !empty(array_intersect($executionPatterns, $matchedPatterns)); 152 152 $hasFileWrite = !empty(array_intersect(['file_put_contents', 'fwrite', 'move_uploaded_file'], $matchedPatterns)); 153 $hasBenignDataImage = $this->isBenignDataImageLine($lineClean) ;153 $hasBenignDataImage = $this->isBenignDataImageLine($lineClean) || $this->isBenignSvgDataUriLine($lineClean); 154 154 $hasSerializedBlob = $this->isLikelySerializedDataLine($lineClean); 155 $hasInput = in_array('superglobal_input', $matchedPatterns, true) || in_array('include_user_input', $matchedPatterns, true); 156 $hasBase64Blob = in_array('base64_blob', $matchedPatterns, true); 155 157 156 158 // Reduce false positives for base64-decoded signatures/keys/certs. … … 166 168 } 167 169 170 // Reduce false positives for decode-only utilities without execution or file writes. 171 if ( 172 !$hasBase64Blob 173 && !$hasExecution 174 && !$hasFileWrite 175 && !$hasInput 176 && !empty($matchedPatterns) 177 && empty(array_diff($matchedPatterns, $decodePatterns)) 178 ) { 179 $score = max(0, $score - 1); 180 } 181 168 182 // Combo detection 169 183 if (in_array('eval', $matchedPatterns) && in_array('base64_decode', $matchedPatterns)) { … … 264 278 } 265 279 280 protected function isBenignSvgDataUriLine(string $line): bool 281 { 282 if (!preg_match('/data:image\\/svg\\+xml;base64,([A-Za-z0-9+\\/=]+)/i', $line, $matches)) { 283 return false; 284 } 285 286 $payload = $matches[1] ?? ''; 287 if ($payload === '' || strlen($payload) > 20000) { 288 return false; 289 } 290 291 $decoded = base64_decode($payload, true); 292 if ($decoded === false) { 293 return false; 294 } 295 296 $lower = strtolower($decoded); 297 if (strpos($lower, '<svg') === false) { 298 return false; 299 } 300 301 if (preg_match('/<script|\\bon\\w+\\s*=|foreignobject|<iframe|<object|<embed|javascript:/i', $decoded)) { 302 return false; 303 } 304 305 return true; 306 } 307 266 308 protected function isLikelySerializedDataLine(string $line): bool 267 309 { … … 301 343 'patterns' => $issue['patterns'], 302 344 'code' => $code, 345 'fixable' => $this->isFixableFinding($issue['patterns'] ?? [], $severity), 303 346 'preview' => $issue['preview'] ?? [], 304 347 ]; … … 359 402 return 'VulnTitan'; 360 403 } 404 405 protected function isFixableFinding(array $patterns, string $severity): bool 406 { 407 if (strtolower($severity) === 'low') { 408 return false; 409 } 410 411 $fixablePatterns = [ 412 'eval', 'assert', 'create_function', 'preg_replace_eval', 413 'system', 'exec', 'shell_exec', 'passthru', 'popen', 'proc_open', 414 'include_user_input', 'file_put_contents', 'fwrite', 'move_uploaded_file', 415 'js_function_ctor', 'js_eval_accessor', 'js_timer_string', 416 'eval_base64_chain', 'decode_execution_chain', 'user_input_execution_chain', 417 'encoded_file_write_chain', 'upload_input_chain', 418 ]; 419 420 foreach ($patterns as $pattern) { 421 if (in_array($pattern, $fixablePatterns, true)) { 422 return true; 423 } 424 } 425 426 return false; 427 } 361 428 } -
vulntitan/tags/2.0.3/readme.txt
r3479449 r3479541 4 4 Tested up to: 6.9 5 5 Requires PHP: 7.4 6 Stable tag: 2.0. 26 Stable tag: 2.0.3 7 7 License: GPLv2 8 8 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 129 129 == Changelog == 130 130 131 = v2.0.3 - 10 Mar, 2026 = 132 * Reduced false positives for benign decode-only utilities (e.g., base64 + gzuncompress). 133 * Reduced false positives for safe data:image/svg+xml;base64 payloads. 134 * Disabled auto-fix for low-risk malware findings to prevent accidental code removal. 135 131 136 = v2.0.2 - 10 Mar, 2026 = 132 137 * Reduced malware scanner false positives for base64-decoded signature and key material. -
vulntitan/tags/2.0.3/vulntitan.php
r3479449 r3479541 4 4 * Plugin URI: https://vulntitan.com/vulntitan/ 5 5 * Description: VulnTitan is a lightweight WordPress security plugin with vulnerability scanning, malware detection, file integrity monitoring, and a built-in firewall with WAF payload rules and login protection. 6 * Version: 2.0. 26 * Version: 2.0.3 7 7 * Author: Jaroslav Svetlik 8 8 * Author URI: https://vulntitan.com … … 30 30 31 31 // Define plugin constants 32 define('VULNTITAN_PLUGIN_VERSION', VULNTITAN_DEVELOPMENT ? uniqid() : '2.0. 2');32 define('VULNTITAN_PLUGIN_VERSION', VULNTITAN_DEVELOPMENT ? uniqid() : '2.0.3'); 33 33 define('VULNTITAN_PLUGIN_BASENAME', plugin_basename(__FILE__)); 34 34 define('VULNTITAN_PLUGIN_DIR', untrailingslashit(plugin_dir_path(__FILE__))); -
vulntitan/trunk/CHANGELOG.md
r3479449 r3479541 5 5 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 6 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 8 ## [2.0.3] - 2026-03-10 9 ### Fixed 10 - Reduced false positives for benign decode-only utilities (e.g., base64 + gzuncompress). 11 - Reduced false positives for safe `data:image/svg+xml;base64` payloads. 12 - Disabled auto-fix for low-risk malware findings to prevent accidental code removal. 7 13 8 14 ## [2.0.2] - 2026-03-10 -
vulntitan/trunk/assets/js/malware-scanner.js
r3469591 r3479541 195 195 const applyFixLabel = VulnTitan.i18n.malware_fix_apply || 'Apply Fix'; 196 196 const fixedLabel = VulnTitan.i18n.malware_fixed || 'Fixed'; 197 const fixDisabled = finding.fixed || !lineNumber; 198 const fixStatus = finding.fix_message ? `<span class="vulntitan-malware-fix-feedback" style="font-size:11px; color:${finding.fix_error ? '#fca5a5' : '#86efac'};">${escapeHtml(finding.fix_message)}</span>` : '<span class="vulntitan-malware-fix-feedback" style="font-size:11px; color:#94a3b8;"></span>'; 199 200 return ` 201 <div style="border:1px solid #3b2020; border-radius:6px; background:#161017; padding:8px 10px;"> 202 <div style="display:flex; align-items:center; justify-content:space-between; gap:8px; flex-wrap:wrap;"> 203 <span style="font-size:12px; color:#fda4af; font-weight:600;"> 204 #${findingIndex + 1} • ${escapeHtml(lineLabel)} 205 </span> 206 <span style="display:inline-block; padding:2px 8px; border:1px solid ${severityColor}; border-radius:999px; font-size:11px; color:${severityColor}; font-weight:700; letter-spacing:0.03em;"> 207 ${escapeHtml(severityLabel)} 208 </span> 209 </div> 210 ${patterns ? `<div style="margin-top:6px; font-size:11px; color:#cbd5e1;">${escapeHtml(patterns)}</div>` : ''} 211 ${renderFindingPreviewLines(finding)} 212 <div class="vulntitan-malware-fix-actions" style="margin-top:8px; display:flex; align-items:center; gap:8px; flex-wrap:wrap;"> 197 const fixAllowed = finding.fixable === true; 198 const fixUnavailableLabel = VulnTitan.i18n.malware_fix_unavailable || 'Auto-fix not available for low-risk findings.'; 199 const fixDisabled = finding.fixed || !lineNumber || !fixAllowed; 200 const fixStatus = finding.fix_message 201 ? `<span class="vulntitan-malware-fix-feedback" style="font-size:11px; color:${finding.fix_error ? '#fca5a5' : '#86efac'};">${escapeHtml(finding.fix_message)}</span>` 202 : (fixAllowed 203 ? '<span class="vulntitan-malware-fix-feedback" style="font-size:11px; color:#94a3b8;"></span>' 204 : `<span class="vulntitan-malware-fix-feedback" style="font-size:11px; color:#94a3b8;">${escapeHtml(fixUnavailableLabel)}</span>`); 205 const fixButton = fixAllowed ? ` 213 206 <button 214 207 type="button" … … 221 214 ${escapeHtml(finding.fixed ? fixedLabel : applyFixLabel)} 222 215 </button> 216 ` : ''; 217 218 return ` 219 <div style="border:1px solid #3b2020; border-radius:6px; background:#161017; padding:8px 10px;"> 220 <div style="display:flex; align-items:center; justify-content:space-between; gap:8px; flex-wrap:wrap;"> 221 <span style="font-size:12px; color:#fda4af; font-weight:600;"> 222 #${findingIndex + 1} • ${escapeHtml(lineLabel)} 223 </span> 224 <span style="display:inline-block; padding:2px 8px; border:1px solid ${severityColor}; border-radius:999px; font-size:11px; color:${severityColor}; font-weight:700; letter-spacing:0.03em;"> 225 ${escapeHtml(severityLabel)} 226 </span> 227 </div> 228 ${patterns ? `<div style="margin-top:6px; font-size:11px; color:#cbd5e1;">${escapeHtml(patterns)}</div>` : ''} 229 ${renderFindingPreviewLines(finding)} 230 <div class="vulntitan-malware-fix-actions" style="margin-top:8px; display:flex; align-items:center; gap:8px; flex-wrap:wrap;"> 231 ${fixButton} 223 232 ${fixStatus} 224 233 </div> -
vulntitan/trunk/assets/js/malware-scanner.min.js
r3469591 r3479541 195 195 const applyFixLabel = VulnTitan.i18n.malware_fix_apply || 'Apply Fix'; 196 196 const fixedLabel = VulnTitan.i18n.malware_fixed || 'Fixed'; 197 const fixDisabled = finding.fixed || !lineNumber; 198 const fixStatus = finding.fix_message ? `<span class="vulntitan-malware-fix-feedback" style="font-size:11px; color:${finding.fix_error ? '#fca5a5' : '#86efac'};">${escapeHtml(finding.fix_message)}</span>` : '<span class="vulntitan-malware-fix-feedback" style="font-size:11px; color:#94a3b8;"></span>'; 199 200 return ` 201 <div style="border:1px solid #3b2020; border-radius:6px; background:#161017; padding:8px 10px;"> 202 <div style="display:flex; align-items:center; justify-content:space-between; gap:8px; flex-wrap:wrap;"> 203 <span style="font-size:12px; color:#fda4af; font-weight:600;"> 204 #${findingIndex + 1} • ${escapeHtml(lineLabel)} 205 </span> 206 <span style="display:inline-block; padding:2px 8px; border:1px solid ${severityColor}; border-radius:999px; font-size:11px; color:${severityColor}; font-weight:700; letter-spacing:0.03em;"> 207 ${escapeHtml(severityLabel)} 208 </span> 209 </div> 210 ${patterns ? `<div style="margin-top:6px; font-size:11px; color:#cbd5e1;">${escapeHtml(patterns)}</div>` : ''} 211 ${renderFindingPreviewLines(finding)} 212 <div class="vulntitan-malware-fix-actions" style="margin-top:8px; display:flex; align-items:center; gap:8px; flex-wrap:wrap;"> 197 const fixAllowed = finding.fixable === true; 198 const fixUnavailableLabel = VulnTitan.i18n.malware_fix_unavailable || 'Auto-fix not available for low-risk findings.'; 199 const fixDisabled = finding.fixed || !lineNumber || !fixAllowed; 200 const fixStatus = finding.fix_message 201 ? `<span class="vulntitan-malware-fix-feedback" style="font-size:11px; color:${finding.fix_error ? '#fca5a5' : '#86efac'};">${escapeHtml(finding.fix_message)}</span>` 202 : (fixAllowed 203 ? '<span class="vulntitan-malware-fix-feedback" style="font-size:11px; color:#94a3b8;"></span>' 204 : `<span class="vulntitan-malware-fix-feedback" style="font-size:11px; color:#94a3b8;">${escapeHtml(fixUnavailableLabel)}</span>`); 205 const fixButton = fixAllowed ? ` 213 206 <button 214 207 type="button" … … 221 214 ${escapeHtml(finding.fixed ? fixedLabel : applyFixLabel)} 222 215 </button> 216 ` : ''; 217 218 return ` 219 <div style="border:1px solid #3b2020; border-radius:6px; background:#161017; padding:8px 10px;"> 220 <div style="display:flex; align-items:center; justify-content:space-between; gap:8px; flex-wrap:wrap;"> 221 <span style="font-size:12px; color:#fda4af; font-weight:600;"> 222 #${findingIndex + 1} • ${escapeHtml(lineLabel)} 223 </span> 224 <span style="display:inline-block; padding:2px 8px; border:1px solid ${severityColor}; border-radius:999px; font-size:11px; color:${severityColor}; font-weight:700; letter-spacing:0.03em;"> 225 ${escapeHtml(severityLabel)} 226 </span> 227 </div> 228 ${patterns ? `<div style="margin-top:6px; font-size:11px; color:#cbd5e1;">${escapeHtml(patterns)}</div>` : ''} 229 ${renderFindingPreviewLines(finding)} 230 <div class="vulntitan-malware-fix-actions" style="margin-top:8px; display:flex; align-items:center; gap:8px; flex-wrap:wrap;"> 231 ${fixButton} 223 232 ${fixStatus} 224 233 </div> -
vulntitan/trunk/includes/Admin/Admin.php
r3469591 r3479541 241 241 'malware_fix_confirm' => esc_html__('Apply safe fix for this suspicious line? A backup will be created.', 'vulntitan'), 242 242 'malware_fix_no_line' => esc_html__('This finding has no exact line, so auto-fix is not available.', 'vulntitan'), 243 'malware_fix_unavailable' => esc_html__('Auto-fix is disabled for low-risk findings.', 'vulntitan'), 243 244 'malware_fix_success' => esc_html__('Fix applied.', 'vulntitan'), 244 245 'malware_fix_error' => esc_html__('Auto-fix failed.', 'vulntitan'), -
vulntitan/trunk/includes/Admin/Ajax.php
r3469591 r3479541 229 229 } 230 230 231 if (!$this->isFixablePatternList($patterns)) { 232 wp_send_json_error(['message' => esc_html__('Auto-fix is disabled for low-risk findings.', 'vulntitan')]); 233 } 234 231 235 $relativePath = ltrim(str_replace('\\', '/', $filePath), '/'); 232 236 $absolutePath = ABSPATH . $relativePath; … … 421 425 422 426 return sprintf('/* VulnTitan quarantine: suspicious code removed at line %d%s */', $lineNumber, $suffix); 427 } 428 429 protected function isFixablePatternList(string $patterns): bool 430 { 431 $items = array_filter(array_map('trim', explode(',', strtolower($patterns)))); 432 if (empty($items)) { 433 return false; 434 } 435 436 $fixablePatterns = [ 437 'eval', 'assert', 'create_function', 'preg_replace_eval', 438 'system', 'exec', 'shell_exec', 'passthru', 'popen', 'proc_open', 439 'include_user_input', 'file_put_contents', 'fwrite', 'move_uploaded_file', 440 'js_function_ctor', 'js_eval_accessor', 'js_timer_string', 441 'eval_base64_chain', 'decode_execution_chain', 'user_input_execution_chain', 442 'encoded_file_write_chain', 'upload_input_chain', 443 ]; 444 445 foreach ($items as $pattern) { 446 if (in_array($pattern, $fixablePatterns, true)) { 447 return true; 448 } 449 } 450 451 return false; 423 452 } 424 453 -
vulntitan/trunk/includes/Services/MalwareScanner.php
r3479449 r3479541 151 151 $hasExecution = !empty(array_intersect($executionPatterns, $matchedPatterns)); 152 152 $hasFileWrite = !empty(array_intersect(['file_put_contents', 'fwrite', 'move_uploaded_file'], $matchedPatterns)); 153 $hasBenignDataImage = $this->isBenignDataImageLine($lineClean) ;153 $hasBenignDataImage = $this->isBenignDataImageLine($lineClean) || $this->isBenignSvgDataUriLine($lineClean); 154 154 $hasSerializedBlob = $this->isLikelySerializedDataLine($lineClean); 155 $hasInput = in_array('superglobal_input', $matchedPatterns, true) || in_array('include_user_input', $matchedPatterns, true); 156 $hasBase64Blob = in_array('base64_blob', $matchedPatterns, true); 155 157 156 158 // Reduce false positives for base64-decoded signatures/keys/certs. … … 166 168 } 167 169 170 // Reduce false positives for decode-only utilities without execution or file writes. 171 if ( 172 !$hasBase64Blob 173 && !$hasExecution 174 && !$hasFileWrite 175 && !$hasInput 176 && !empty($matchedPatterns) 177 && empty(array_diff($matchedPatterns, $decodePatterns)) 178 ) { 179 $score = max(0, $score - 1); 180 } 181 168 182 // Combo detection 169 183 if (in_array('eval', $matchedPatterns) && in_array('base64_decode', $matchedPatterns)) { … … 264 278 } 265 279 280 protected function isBenignSvgDataUriLine(string $line): bool 281 { 282 if (!preg_match('/data:image\\/svg\\+xml;base64,([A-Za-z0-9+\\/=]+)/i', $line, $matches)) { 283 return false; 284 } 285 286 $payload = $matches[1] ?? ''; 287 if ($payload === '' || strlen($payload) > 20000) { 288 return false; 289 } 290 291 $decoded = base64_decode($payload, true); 292 if ($decoded === false) { 293 return false; 294 } 295 296 $lower = strtolower($decoded); 297 if (strpos($lower, '<svg') === false) { 298 return false; 299 } 300 301 if (preg_match('/<script|\\bon\\w+\\s*=|foreignobject|<iframe|<object|<embed|javascript:/i', $decoded)) { 302 return false; 303 } 304 305 return true; 306 } 307 266 308 protected function isLikelySerializedDataLine(string $line): bool 267 309 { … … 301 343 'patterns' => $issue['patterns'], 302 344 'code' => $code, 345 'fixable' => $this->isFixableFinding($issue['patterns'] ?? [], $severity), 303 346 'preview' => $issue['preview'] ?? [], 304 347 ]; … … 359 402 return 'VulnTitan'; 360 403 } 404 405 protected function isFixableFinding(array $patterns, string $severity): bool 406 { 407 if (strtolower($severity) === 'low') { 408 return false; 409 } 410 411 $fixablePatterns = [ 412 'eval', 'assert', 'create_function', 'preg_replace_eval', 413 'system', 'exec', 'shell_exec', 'passthru', 'popen', 'proc_open', 414 'include_user_input', 'file_put_contents', 'fwrite', 'move_uploaded_file', 415 'js_function_ctor', 'js_eval_accessor', 'js_timer_string', 416 'eval_base64_chain', 'decode_execution_chain', 'user_input_execution_chain', 417 'encoded_file_write_chain', 'upload_input_chain', 418 ]; 419 420 foreach ($patterns as $pattern) { 421 if (in_array($pattern, $fixablePatterns, true)) { 422 return true; 423 } 424 } 425 426 return false; 427 } 361 428 } -
vulntitan/trunk/readme.txt
r3479449 r3479541 4 4 Tested up to: 6.9 5 5 Requires PHP: 7.4 6 Stable tag: 2.0. 26 Stable tag: 2.0.3 7 7 License: GPLv2 8 8 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 129 129 == Changelog == 130 130 131 = v2.0.3 - 10 Mar, 2026 = 132 * Reduced false positives for benign decode-only utilities (e.g., base64 + gzuncompress). 133 * Reduced false positives for safe data:image/svg+xml;base64 payloads. 134 * Disabled auto-fix for low-risk malware findings to prevent accidental code removal. 135 131 136 = v2.0.2 - 10 Mar, 2026 = 132 137 * Reduced malware scanner false positives for base64-decoded signature and key material. -
vulntitan/trunk/vulntitan.php
r3479449 r3479541 4 4 * Plugin URI: https://vulntitan.com/vulntitan/ 5 5 * Description: VulnTitan is a lightweight WordPress security plugin with vulnerability scanning, malware detection, file integrity monitoring, and a built-in firewall with WAF payload rules and login protection. 6 * Version: 2.0. 26 * Version: 2.0.3 7 7 * Author: Jaroslav Svetlik 8 8 * Author URI: https://vulntitan.com … … 30 30 31 31 // Define plugin constants 32 define('VULNTITAN_PLUGIN_VERSION', VULNTITAN_DEVELOPMENT ? uniqid() : '2.0. 2');32 define('VULNTITAN_PLUGIN_VERSION', VULNTITAN_DEVELOPMENT ? uniqid() : '2.0.3'); 33 33 define('VULNTITAN_PLUGIN_BASENAME', plugin_basename(__FILE__)); 34 34 define('VULNTITAN_PLUGIN_DIR', untrailingslashit(plugin_dir_path(__FILE__)));
Note: See TracChangeset
for help on using the changeset viewer.