PluginProbe
Atomic Edge Security – Firewall, Malware Scan and Login Security / 2.2.2
Atomic Edge Security – Firewall, Malware Scan and Login Security v2.2.2
trunk 2.0.0 2.1.0 2.2.0 2.2.1 2.2.2 2.3.0 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.4.8 2.4.9 2.5.0 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 All 29 releases
atomic-edge-security / includes / class-atomicedge-cli.php

class-atomicedge-cli.php in Atomic Edge Security – Firewall, Malware Scan and Login Security 2.2.2, at includes/class-atomicedge-cli.php

337 lines 8.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * AtomicEdge WP-CLI Commands
4 *
5 * Provides command-line interface for scanner operations.
6 *
7 * @package AtomicEdge
8 */
9
10 // Prevent direct access.
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 // Only load if WP-CLI is available.
16 if ( ! defined( 'WP_CLI' ) || ! WP_CLI ) {
17 return;
18 }
19
20 /**
21 * AtomicEdge Security Scanner CLI Commands.
22 *
23 * @package AtomicEdge
24 */
25 class AtomicEdge_CLI {
26
27 /**
28 * Scanner instance.
29 *
30 * @var AtomicEdge_Scanner
31 */
32 private $scanner;
33
34 /**
35 * Constructor.
36 */
37 public function __construct() {
38 $this->scanner = new AtomicEdge_Scanner();
39 }
40
41 /**
42 * Run a full malware scan.
43 *
44 * ## OPTIONS
45 *
46 * [--format=<format>]
47 * : Output format.
48 * ---
49 * default: table
50 * options:
51 * - table
52 * - json
53 * - csv
54 * ---
55 *
56 * [--severity=<severity>]
57 * : Filter by minimum severity level.
58 * ---
59 * default: low
60 * options:
61 * - critical
62 * - high
63 * - medium
64 * - low
65 * ---
66 *
67 * [--type=<type>]
68 * : Type of scan to run.
69 * ---
70 * default: full
71 * options:
72 * - full
73 * - core
74 * - suspicious
75 * ---
76 *
77 * ## EXAMPLES
78 *
79 * # Run a full scan
80 * $ wp atomicedge scan
81 *
82 * # Run suspicious files scan only with JSON output
83 * $ wp atomicedge scan --type=suspicious --format=json
84 *
85 * # Run scan and show only critical/high severity
86 * $ wp atomicedge scan --severity=high
87 *
88 * @param array $args Positional arguments.
89 * @param array $assoc_args Associative arguments.
90 * @return void
91 */
92 public function scan( $args, $assoc_args ) {
93 $format = isset( $assoc_args['format'] ) ? $assoc_args['format'] : 'table';
94 $severity = isset( $assoc_args['severity'] ) ? $assoc_args['severity'] : 'low';
95 $type = isset( $assoc_args['type'] ) ? $assoc_args['type'] : 'full';
96
97 WP_CLI::log( 'Starting Atomic Edge security scan...' );
98 WP_CLI::log( '' );
99
100 $results = array();
101 $start_time = microtime( true );
102
103 switch ( $type ) {
104 case 'core':
105 WP_CLI::log( 'Scanning WordPress core files...' );
106 $core_results = $this->scanner->scan_core_files();
107 if ( false !== $core_results ) {
108 $results = $core_results;
109 }
110 break;
111
112 case 'suspicious':
113 WP_CLI::log( 'Scanning for suspicious patterns...' );
114 $suspicious_results = $this->scanner->scan_suspicious_files();
115 if ( false !== $suspicious_results ) {
116 $results = $suspicious_results;
117 }
118 break;
119
120 case 'full':
121 default:
122 WP_CLI::log( 'Running full scan (core + suspicious patterns)...' );
123 $full_results = $this->scanner->run_full_scan();
124 if ( false !== $full_results ) {
125 $results = array_merge(
126 isset( $full_results['core_files'] ) ? $full_results['core_files'] : array(),
127 isset( $full_results['suspicious'] ) ? $full_results['suspicious'] : array()
128 );
129 }
130 break;
131 }
132
133 $elapsed = round( microtime( true ) - $start_time, 2 );
134 WP_CLI::log( '' );
135 WP_CLI::log( sprintf( 'Scan completed in %s seconds.', $elapsed ) );
136 WP_CLI::log( '' );
137
138 // Filter by severity.
139 $severity_order = array( 'critical' => 4, 'high' => 3, 'medium' => 2, 'low' => 1 );
140 $min_severity = isset( $severity_order[ $severity ] ) ? $severity_order[ $severity ] : 1;
141
142 $filtered_results = array_filter(
143 $results,
144 function ( $item ) use ( $severity_order, $min_severity ) {
145 $item_severity = isset( $item['severity'] ) ? $item['severity'] : 'low';
146 $item_level = isset( $severity_order[ $item_severity ] ) ? $severity_order[ $item_severity ] : 1;
147 return $item_level >= $min_severity;
148 }
149 );
150
151 if ( empty( $filtered_results ) ) {
152 WP_CLI::success( 'No issues found!' );
153 return;
154 }
155
156 // Count by severity.
157 $counts = array( 'critical' => 0, 'high' => 0, 'medium' => 0, 'low' => 0 );
158 foreach ( $filtered_results as $item ) {
159 $sev = isset( $item['severity'] ) ? $item['severity'] : 'low';
160 if ( isset( $counts[ $sev ] ) ) {
161 ++$counts[ $sev ];
162 }
163 }
164
165 WP_CLI::log( sprintf(
166 'Found %d issues: %d critical, %d high, %d medium, %d low',
167 count( $filtered_results ),
168 $counts['critical'],
169 $counts['high'],
170 $counts['medium'],
171 $counts['low']
172 ) );
173 WP_CLI::log( '' );
174
175 // Format output.
176 $output_items = array();
177 foreach ( $filtered_results as $item ) {
178 $output_items[] = array(
179 'File' => isset( $item['file'] ) ? $item['file'] : 'unknown',
180 'Severity' => isset( $item['severity'] ) ? strtoupper( $item['severity'] ) : 'LOW',
181 'Type' => isset( $item['type'] ) ? $item['type'] : 'unknown',
182 'Details' => isset( $item['pattern'] ) ? $item['pattern'] : ( isset( $item['reason'] ) ? $item['reason'] : '' ),
183 );
184 }
185
186 WP_CLI\Utils\format_items( $format, $output_items, array( 'File', 'Severity', 'Type', 'Details' ) );
187
188 // Exit with error code if critical issues found.
189 if ( $counts['critical'] > 0 ) {
190 WP_CLI::error( 'Critical issues found!', false );
191 }
192 }
193
194 /**
195 * Show scanner statistics and configuration.
196 *
197 * ## EXAMPLES
198 *
199 * # Show scanner stats
200 * $ wp atomicedge stats
201 *
202 * @param array $args Positional arguments.
203 * @param array $assoc_args Associative arguments.
204 * @return void
205 */
206 public function stats( $args, $assoc_args ) {
207 $stats = $this->scanner->get_scan_statistics();
208
209 WP_CLI::log( 'Atomic Edge Scanner Statistics' );
210 WP_CLI::log( '==============================' );
211 WP_CLI::log( '' );
212 WP_CLI::log( sprintf( 'Total Patterns: %d', $stats['total_patterns'] ) );
213 WP_CLI::log( '' );
214 WP_CLI::log( 'Patterns by Category:' );
215
216 foreach ( $stats['categories'] as $category => $count ) {
217 WP_CLI::log( sprintf( ' - %s: %d', $category, $count ) );
218 }
219
220 WP_CLI::log( '' );
221 WP_CLI::log( 'Scan Areas:' );
222 foreach ( $stats['scan_areas'] as $area ) {
223 WP_CLI::log( sprintf( ' - %s', $area ) );
224 }
225
226 // Show whitelist stats if available.
227 if ( isset( $stats['whitelisted_paths'] ) ) {
228 WP_CLI::log( '' );
229 WP_CLI::log( sprintf( 'Whitelisted Paths: %d', $stats['whitelisted_paths'] ) );
230 }
231
232 $last_scan = $this->scanner->get_last_scan_time();
233 if ( $last_scan ) {
234 WP_CLI::log( '' );
235 WP_CLI::log( sprintf( 'Last Scan: %s', $last_scan ) );
236 }
237 }
238
239 /**
240 * Test scanner patterns against a specific file.
241 *
242 * ## OPTIONS
243 *
244 * <file>
245 * : Path to the file to test.
246 *
247 * [--show-content]
248 * : Show matching content snippets.
249 *
250 * ## EXAMPLES
251 *
252 * # Test a specific file
253 * $ wp atomicedge test-file /path/to/file.php
254 *
255 * @param array $args Positional arguments.
256 * @param array $assoc_args Associative arguments.
257 * @return void
258 */
259 public function test_file( $args, $assoc_args ) {
260 if ( empty( $args[0] ) ) {
261 WP_CLI::error( 'Please specify a file path.' );
262 return;
263 }
264
265 $file = $args[0];
266
267 if ( ! file_exists( $file ) ) {
268 WP_CLI::error( 'File not found: ' . $file );
269 return;
270 }
271
272 $show_content = isset( $assoc_args['show-content'] );
273
274 WP_CLI::log( sprintf( 'Testing file: %s', $file ) );
275 WP_CLI::log( '' );
276
277 // Read file content.
278 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
279 $content = file_get_contents( $file );
280
281 if ( false === $content ) {
282 WP_CLI::error( 'Could not read file.' );
283 return;
284 }
285
286 // Test against patterns using reflection to access private method.
287 $reflection = new ReflectionClass( $this->scanner );
288 $method = $reflection->getMethod( 'get_malware_patterns' );
289 $method->setAccessible( true );
290 $patterns = $method->invoke( $this->scanner );
291
292 $matches = array();
293
294 foreach ( $patterns as $group_name => $group_patterns ) {
295 foreach ( $group_patterns as $pattern => $description ) {
296 // Use # as delimiter to avoid issues with / in patterns.
297 if ( preg_match( '#' . $pattern . '#i', $content, $match ) ) {
298 $matches[] = array(
299 'Category' => $group_name,
300 'Description' => $description,
301 'Match' => $show_content ? substr( $match[0], 0, 50 ) : '[hidden]',
302 );
303 }
304 }
305 }
306
307 if ( empty( $matches ) ) {
308 WP_CLI::success( 'No suspicious patterns found in this file.' );
309 return;
310 }
311
312 WP_CLI::warning( sprintf( 'Found %d pattern matches:', count( $matches ) ) );
313 WP_CLI::log( '' );
314
315 WP_CLI\Utils\format_items( 'table', $matches, array( 'Category', 'Description', 'Match' ) );
316
317 // Check if file should be whitelisted.
318 // Get root path using scanner's method via reflection to maintain consistency.
319 $root_method = $reflection->getMethod( 'get_wp_root_path' );
320 $root_method->setAccessible( true );
321 $root_path = $root_method->invoke( $this->scanner );
322 $relative_path = ltrim( str_replace( $root_path, '', wp_normalize_path( $file ) ), '/' );
323
324 $whitelist_method = $reflection->getMethod( 'is_whitelisted_path' );
325 $whitelist_method->setAccessible( true );
326 $is_whitelisted = $whitelist_method->invoke( $this->scanner, $relative_path );
327
328 if ( $is_whitelisted ) {
329 WP_CLI::log( '' );
330 WP_CLI::log( 'Note: This file path is WHITELISTED and would be skipped in a normal scan.' );
331 }
332 }
333 }
334
335 // Register commands.
336 WP_CLI::add_command( 'atomic-edge-security', 'AtomicEdge_CLI' );
337