PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.4.1
Defender Security – Malware Scanner, Login Security & Firewall v5.4.1
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / src / behavior / scan-item / class-plugin-integrity.php

class-plugin-integrity.php in Defender Security – Malware Scanner, Login Security & Firewall 5.4.1, at src/behavior/scan-item/class-plugin-integrity.php

233 lines 6.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles plugin integrity scan item.
4 *
5 * @package WP_Defender\Behavior\Scan_Item
6 */
7
8 namespace WP_Defender\Behavior\Scan_Item;
9
10 use WP_Error;
11 use Calotes\Base\File;
12 use WP_Defender\Traits\IO;
13 use WP_Defender\Model\Scan;
14 use WP_Defender\Traits\Plugin;
15 use WP_Defender\Traits\Formats;
16 use Calotes\Component\Behavior;
17 use WP_Defender\Model\Scan_Item;
18 use WP_Defender\Traits\File_Operations;
19 use WP_Defender\Component\Quarantine as Quarantine_Component;
20
21 /**
22 * Class Plugin_Integrity
23 * This class represents a plugin integrity behavior.
24 */
25 class Plugin_Integrity extends Behavior {
26
27 use Formats;
28 use IO;
29 use Plugin;
30 use File_Operations;
31
32 /**
33 * Return general data so we can output on frontend.
34 *
35 * @return array
36 */
37 public function to_array(): array {
38 $data = $this->owner->raw_data;
39 $file = $data['file'];
40 list ( $file_created_at, $file_size, $deleted ) = $this->get_file_meta( $file );
41
42 $is_quarantinable = $this->is_quarantinable( $this->owner->raw_data['file'] );
43
44 $quarantine_data = class_exists( 'WP_Defender\Component\Quarantine' ) ?
45 wd_di()->get( Quarantine_Component::class )->scan_item_data( $this->owner ) :
46 array( 'is_quarantinable' => $is_quarantinable );
47
48 return array_merge(
49 array(
50 'id' => $this->owner->id,
51 'type' => Scan_Item::TYPE_PLUGIN_CHECK,
52 'file_name' => pathinfo( $file, PATHINFO_BASENAME ),
53 'full_path' => $file,
54 'date_added' => $file_created_at,
55 'size' => $file_size,
56 'deleted' => $deleted,
57 'scenario' => $data['type'],
58 'short_desc' => $this->get_short_description(),
59 ),
60 $quarantine_data
61 );
62 }
63
64 /**
65 * We will get the origin code by looking into svn repo.
66 *
67 * @return false|string|WP_Error
68 */
69 private function get_origin_code() {
70 $file = $this->owner->raw_data['file'];
71
72 $directory_name = $this->get_plugin_directory_name( $file );
73 $plugin_header = $this->get_plugin_headers( $file );
74 $plugin_header = reset( $plugin_header );
75 $file_path = $this->get_plugin_relative_path( $file );
76
77 $source_file_url = $this->get_file_url(
78 $directory_name,
79 $plugin_header['Version'],
80 $file_path
81 );
82
83 return $this->get_url_content( $source_file_url );
84 }
85
86 /**
87 * Restore the file with its origin content.
88 *
89 * @return void|array|WP_Error
90 */
91 public function resolve() {
92 global $wp_filesystem;
93 // Initialize the WP filesystem, no more using 'file-put-contents' function.
94 if ( empty( $wp_filesystem ) ) {
95 require_once ABSPATH . '/wp-admin/includes/file.php';
96 WP_Filesystem();
97 }
98 $data = $this->owner->raw_data;
99 if ( 'modified' !== $data['type'] ) {
100 // Should not be here unless case changed.
101 return;
102 }
103
104 $origin = $this->get_origin_code();
105 if ( false === $origin || is_wp_error( $origin ) ) {
106 return;
107 }
108
109 $path = $data['file'];
110 $ret = $wp_filesystem->put_contents( $path, $origin );
111 if ( $ret ) {
112 $scan = Scan::get_last();
113 $scan->remove_issue( $this->owner->id );
114 $this->log( sprintf( '%s is deleted', $path ), \WP_Defender\Controller\Scan::SCAN_LOG );
115
116 do_action( 'wpdef_fixed_scan_issue', 'plugin_integrity', 'resolve' );
117
118 return array( 'message' => esc_html__( 'This item has been resolved.', 'defender-security' ) );
119 } else {
120 return new WP_Error(
121 'defender_permissions_denied',
122 esc_html__(
123 'Permissions Denied. Defender does not have the needed permissions to edit the file. Please change file permissions to 640 or contact your hosting provider so they could change them for you.',
124 'defender-security'
125 )
126 );
127 }
128 }
129
130 /**
131 * Ignore the suspicious file for the current scan.
132 *
133 * @return array An array with a message indicating successful ignore.
134 */
135 public function ignore(): array {
136 $scan = Scan::get_last();
137 $scan->ignore_issue( $this->owner->id );
138
139 return array( 'message' => esc_html__( 'The suspicious file has been successfully ignored.', 'defender-security' ) );
140 }
141
142 /**
143 * Allow the suspicious file for the current scan.
144 *
145 * @return array An array with a message indicating successful restoration.
146 */
147 public function unignore(): array {
148 $scan = Scan::get_last();
149 $scan->unignore_issue( $this->owner->id );
150
151 return array( 'message' => esc_html__( 'The suspicious file has been successfully restored.', 'defender-security' ) );
152 }
153
154 /**
155 * Delete the file or whole folder.
156 *
157 * @return array|WP_Error
158 */
159 public function delete() {
160 $data = $this->owner->raw_data;
161 $scan = Scan::get_last();
162 $file = $data['file'];
163 if ( ! file_exists( $file ) || ! is_readable( $file ) ) {
164 $scan->remove_issue( $this->owner->id );
165 $this->log(
166 sprintf( '%s is not readable and will be removed from the scan result', $file ),
167 \WP_Defender\Controller\Scan::SCAN_LOG
168 );
169
170 return array( 'message' => esc_html__( 'This item has been deleted.', 'defender-security' ) );
171 } elseif ( 'unversion' === $data['type'] && $this->delete_infected_file( $file ) ) {
172 return $this->after_delete( $file, $scan, 'plugin_integrity' );
173 } elseif ( 'dir' === $data['type'] && $this->delete_dir( $file ) ) {
174 return $this->after_delete( $file, $scan, 'plugin_integrity' );
175 }
176
177 return $this->get_permission_error( $file );
178 }
179
180 /**
181 * Return the source code depending on the type of the issue.
182 *
183 * @return array
184 */
185 public function pull_src(): array {
186 global $wp_filesystem;
187 // Initialize the WP filesystem, no more using 'file-put-contents' function.
188 if ( empty( $wp_filesystem ) ) {
189 require_once ABSPATH . '/wp-admin/includes/file.php';
190 WP_Filesystem();
191 }
192 $data = $this->owner->raw_data;
193 if ( ! file_exists( $data['file'] ) && ! is_dir( $data['file'] ) ) {
194 return array(
195 'code' => '',
196 'origin' => '',
197 );
198 }
199 switch ( $data['type'] ) {
200 case 'unversion':
201 return array( 'code' => $wp_filesystem->get_contents( $data['file'] ) );
202 case 'dir':
203 $dir_tree = new File( $data['file'], true, true, array(), array(), false );
204
205 return array( 'code' => implode( PHP_EOL, $dir_tree->get_dir_tree() ) );
206 case 'modified':
207 default:
208 return array(
209 'code' => $wp_filesystem->get_contents( $data['file'] ),
210 'origin' => $this->get_origin_code(),
211 );
212 }
213 }
214
215 /**
216 * Return a short description of the WordPress plugin file based on its type.
217 *
218 * @return string The short description of the file.
219 */
220 private function get_short_description(): string {
221 $data = $this->owner->raw_data;
222 if ( 'unversion' === $data['type'] ) {
223 return esc_html__( 'Unknown file in the WordPress plugin', 'defender-security' );
224 } elseif ( 'dir' === $data['type'] ) {
225 return esc_html__( 'This directory does not belong to the WordPress plugin', 'defender-security' );
226 } elseif ( 'modified' === $data['type'] ) {
227 return esc_html__( 'This plugin file appears modified', 'defender-security' );
228 }
229
230 return '';
231 }
232 }
233