isFile() ) { continue; } $allowedExtensions = [ 'php', 'phtml', 'php5', 'php7', 'php8', 'phar', 'inc', 'html', 'htm', 'sphp', ]; $extension = strtolower( $file->getExtension() ); if ( ! in_array( $extension, $allowedExtensions, true ) ) { continue; } $this->scanFile( $file->getPathname() ); } return $this->results; } /** * Scan files located directly in the root directory. * * @param string $directory Directory to scan. * @return array */ public function scanRootPhpFilesfol( string $directory ): array { foreach ( new DirectoryIterator( $directory ) as $file ) { if ( $file->isDot() || ! $file->isFile() ) { continue; } $extension = strtolower( $file->getExtension() ); if ( ! in_array( $extension, [ 'php', 'phtml', 'html', 'php5', 'php7', 'php8', 'phar', 'inc', 'sphp', ], true ) ) { continue; } $this->scanFile( $file->getPathname() ); } return $this->results; } /** * Scan a single file for suspicious content. * * @param string $file File path. * @return void */ private function scanFile( string $file ): void { $content = @file_get_contents( $file ); if ( $content === false ) { return; } /* * Check known suspicious signatures. */ foreach ( $this->signatures as $pattern ) { if ( preg_match( $pattern, $content ) ) { $this->results[] = [ 'file' => $file, 'reason' => $pattern, 'severity' => 'Medium', ]; break; } } /* * Detect large Base64 blobs. */ if ( preg_match( '/[A-Za-z0-9+\/]{800,}={0,2}/', $content ) ) { $this->results[] = [ 'file' => $file, 'reason' => 'Large Base64 blob', 'severity' => 'High', ]; } /* * Find embedded Base64 strings. */ preg_match_all( '/["\']([A-Za-z0-9+\/=]{100,})["\']/', $content, $matches ); if ( empty( $matches[1] ) ) { return; } /* * Decode and inspect Base64 payloads. */ foreach ( $matches[1] as $encoded ) { $decoded = base64_decode( $encoded, true ); if ( $decoded === false ) { continue; } $suspicious = preg_match( '/<\?(php|=)?/i', $decoded ) || preg_match( '/\beval\s*\(/i', $decoded ) || preg_match( '/\bassert\s*\(/i', $decoded ) || preg_match( '/\bbase64_decode\s*\(/i', $decoded ) || preg_match( '/\bgzinflate\s*\(/i', $decoded ) || preg_match( '/\bgzuncompress\s*\(/i', $decoded ) || preg_match( '/\bstr_rot13\s*\(/i', $decoded ) || preg_match( '/\bshell_exec\s*\(/i', $decoded ) || preg_match( '/\bsystem\s*\(/i', $decoded ) || preg_match( '/\bpassthru\s*\(/i', $decoded ) || preg_match( '/\bexec\s*\(/i', $decoded ) || preg_match( '/\bpopen\s*\(/i', $decoded ) || preg_match( '/\bproc_open\s*\(/i', $decoded ) || preg_match( '/\bcreate_function\s*\(/i', $decoded ) || preg_match( '/\bcall_user_func(?:_array)?\s*\(/i', $decoded ); if ( $suspicious ) { $this->results[] = [ 'file' => $file, 'reason' => 'Encoded PHP payload', 'severity' => 'High', ]; break; } } } /** * Check file permissions. * * @param string $directory Directory to check. * @return array */ public function checkPermissions( string $directory ): array { $warnings = []; $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $directory ) ); foreach ( $iterator as $file ) { if ( ! $file->isFile() ) { continue; } $perm = substr( sprintf( '%o', fileperms( $file ) ), -3 ); if ( $perm === '777' ) { $warnings[] = [ 'file' => $file->getPathname(), 'permission' => $perm, ]; } } return $warnings; } /** * Verify file integrity using SHA-256. * * @param string $file File path. * @param string $expectedHash Expected SHA-256 hash. * @return bool */ public function verifyIntegrity( string $file, string $expectedHash ): bool { return hash_file( 'sha256', $file ) === $expectedHash; } }