PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.5.0
Defender Security – Malware Scanner, Login Security & Firewall v5.5.0
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 / class-known-vulnerability.php

class-known-vulnerability.php in Defender Security – Malware Scanner, Login Security & Firewall 5.5.0, at src/behavior/scan/class-known-vulnerability.php

302 lines 8.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles core integrity scan.
4 *
5 * @package WP_Defender\Behavior\Scan
6 */
7
8 namespace WP_Defender\Behavior\Scan;
9
10 use WP_Error;
11 use Countable;
12 use WP_Defender\Traits\IO;
13 use Calotes\Base\Component;
14 use WP_Defender\Model\Scan;
15 use WP_Defender\Traits\Theme;
16 use WP_Defender\Traits\Plugin;
17 use WP_Defender\Model\Scan_Item;
18 use WP_Defender\Behavior\WPMUDEV;
19 use WP_Defender\Helper\Analytics\Scan as Scan_Analytics;
20
21 /**
22 * Represents a component responsible for checking known vulnerabilities.
23 */
24 class Known_Vulnerability extends Component {
25
26 use IO;
27 use Plugin;
28 use Theme;
29
30 /**
31 * Holds the Scan model or null if not set.
32 *
33 * @var Scan|null
34 */
35 private ?Scan $scan;
36
37 /**
38 * Holds the WPMUDEV object or null if not set.
39 *
40 * @var WPMUDEV|null
41 */
42 private ?WPMUDEV $wpmudev;
43
44 /**
45 * Initializes the Known_Vulnerability class with the WPMUDEV and Scan instances.
46 *
47 * @param WPMUDEV $wpmudev The WPMUDEV object.
48 * @param Scan $scan The Scan model.
49 */
50 public function __construct( WPMUDEV $wpmudev, Scan $scan ) {
51 $this->wpmudev = $wpmudev;
52 $this->scan = $scan;
53 }
54
55 /**
56 * Performs the vulnerability check.
57 *
58 * @return bool
59 */
60 public function vuln_check(): bool {
61 global $wp_version;
62 $info_cached = array();
63 $data = array(
64 'plugins' => wp_json_encode( $this->gather_fact( 'plugin', $info_cached ) ),
65 'themes' => wp_json_encode( $this->gather_fact( 'theme', $info_cached ) ),
66 'wordpress' => $wp_version,
67 );
68
69 $ret = $this->make_check( $data );
70 if ( is_wp_error( $ret ) ) {
71 $this->handle_error( $ret );
72
73 return true;
74 }
75
76 $ignored_issues = $this->get_ignored_issues();
77 $this->process_results( $ret, $info_cached, $ignored_issues );
78 $this->scan->calculate_percent( 100, 5 );
79 $this->add_ignored_issues( $ignored_issues );
80
81 return true;
82 }
83
84 /**
85 * Gathers information about installed plugins or themes.
86 *
87 * @param string $type The type of information to gather (plugin or theme).
88 * @param array $info_cached The cached information.
89 *
90 * @return array
91 */
92 private function gather_fact( string $type, &$info_cached ): array {
93 $items = array();
94 $model = Scan::get_last();
95
96 if ( 'plugin' === $type ) {
97 $get_items = $this->get_plugins();
98 } elseif ( 'theme' === $type ) {
99 $get_items = $this->get_themes();
100 } else {
101 return $items;
102 }
103
104 foreach ( $get_items as $slug => $item ) {
105 if ( is_object( $model ) && $model->is_issue_ignored( $slug ) ) {
106 continue;
107 }
108
109 $base_slug = explode( '/', $slug );
110 $base_slug = array_shift( $base_slug );
111 $items[ $base_slug ] = $item['Version'];
112 $info_cached[ $base_slug ] = array( $item['Name'], $item['Version'], $slug );
113 }
114
115 return $items;
116 }
117
118 /**
119 * Makes a request to the WPMUDEV API to check for known vulnerabilities.
120 *
121 * @param mixed $data The data to send in the request.
122 *
123 * @return array|WP_Error
124 */
125 protected function make_check( $data ) {
126 return $this->wpmudev->make_wpmu_request( WPMUDEV::API_SCAN_KNOWN_VULN, $data, array( 'method' => 'POST' ) );
127 }
128
129 /**
130 * Handles the error by tracking the failure event, logging the error message, and updating the scan status.
131 *
132 * @param WP_Error $error The error object containing the error message.
133 */
134 private function handle_error( WP_Error $error ) {
135 $error_message = $error->get_error_message();
136 $scan_analytics = wd_di()->get( Scan_Analytics::class );
137
138 $scan_analytics->track_feature(
139 Scan_Analytics::EVENT_SCAN_FAILED,
140 array(
141 Scan_Analytics::EVENT_SCAN_FAILED_PROP => Scan_Analytics::EVENT_SCAN_FAILED_ERROR,
142 'Error_Reason' => $error_message,
143 )
144 );
145
146 $this->log( $error_message, \WP_Defender\Controller\Scan::SCAN_LOG );
147 $this->scan->status = Scan::STATUS_ERROR;
148 $this->scan->save();
149 }
150
151 /**
152 * Retrieves the list of ignored issues.
153 *
154 * @return array The list of ignored issues.
155 */
156 private function get_ignored_issues() {
157 $last = Scan::get_last();
158
159 return is_object( $last ) ? $last->get_issues(
160 Scan_Item::TYPE_VULNERABILITY,
161 Scan_Item::STATUS_IGNORE
162 ) : array();
163 }
164
165 /**
166 * Processes the results of the vulnerability check and saves them in the Scan model.
167 *
168 * @param array $results The results of the vulnerability check.
169 * @param array $info_cached The cached information.
170 * @param array $ignored_issues The ignored issues.
171 */
172 private function process_results( $results, $info_cached, $ignored_issues ) {
173 $this->process_result( $results['plugins'], $info_cached, 'plugin', $ignored_issues );
174 $this->process_result( $results['themes'], $info_cached, 'theme', $ignored_issues );
175 $this->process_result( $results['wordpress'], array(), 'wp_core', $ignored_issues );
176 }
177
178 /**
179 * Processes the result of the vulnerability check and saves them in the Scan model.
180 *
181 * @param mixed $result The result of the vulnerability check.
182 * @param array $info The cached information.
183 * @param string $type The type of the vulnerability check.
184 * @param array $ignored_issues The ignored issues.
185 */
186 private function process_result( $result, $info, $type, $ignored_issues ) {
187 if ( empty( $result ) ) {
188 return;
189 }
190
191 $this->log( sprintf( 'Checking %s:', $type ) );
192 $model = $this->scan;
193
194 foreach ( $result as $base_slug => $bugs ) {
195 if ( 'wp_core' === $type ) {
196 global $wp_version;
197 $is_exist = false;
198 if ( ! empty( $ignored_issues ) ) {
199 foreach ( $ignored_issues as $issue ) {
200 if ( $wp_version === $issue->raw_data['version'] ) {
201 $is_exist = true;
202 }
203 }
204 }
205
206 if ( ! $is_exist ) {
207 $raw_data = array(
208 'type' => $type,
209 'slug' => '',
210 'base_slug' => '',
211 'version' => $wp_version,
212 'name' => 'WordPress Core',
213 'bugs' => array(
214 array(
215 'vuln_type' => $bugs['vuln_type'],
216 'title' => $bugs['title'],
217 'ref' => $bugs['references'],
218 'fixed_in' => $bugs['fixed_in'],
219 'cvss_score' => $bugs['cvss']['score'] ?? '',
220 ),
221 ),
222 'new_structure' => '3.4.0',
223 );
224
225 $model->add_item( Scan_Item::TYPE_VULNERABILITY, $raw_data );
226 }
227 } elseif ( 'plugin' === $type || 'theme' === $type ) {
228 if ( empty( $info[ $base_slug ] ) ) {
229 continue;
230 }
231
232 if ( isset( $bugs['confirmed'] ) && ( is_array( $bugs['confirmed'] ) || $bugs['confirmed'] instanceof Countable ? count( $bugs['confirmed'] ) : 0 ) ) {
233 $wp_org_plugin_details = array(
234 'is_existed' => false,
235 'last_version' => '0',
236 );
237 if ( 'plugin' === $type ) {
238 if ( ! $this->is_likely_wporg_slug( $base_slug ) ) {
239 continue;
240 }
241 // Additional conditions for wp.org plugin.
242 $results = $this->handle_wp_org_response_by( $base_slug );
243 if ( $results['success'] && isset( $results['body']['version'] ) ) {
244 $wp_org_plugin_details = array(
245 'is_existed' => true,
246 'last_version' => $results['body']['version'],
247 );
248 }
249 // If there is not a plugin version, likely the plugin is closed. We'd not display Update button.
250 }
251
252 [ $name, $current_version, $slug ] = $info[ $base_slug ];
253 $raw_data = array(
254 'type' => $type,
255 'slug' => $slug,
256 'base_slug' => $base_slug,
257 'version' => $current_version,
258 'name' => $name,
259 'bugs' => array(),
260 );
261
262 $confirmed_bugs = (array) $bugs['confirmed'];
263 // @since 3.4.0 Restructure the view in relation to the new CVSS Score field.
264 $raw_data['new_structure'] = '3.4.0';
265 $this->log( sprintf( '%s has %d known bugs', $slug, count( $confirmed_bugs ) ) );
266
267 foreach ( $confirmed_bugs as $bug ) {
268 if ( 'plugin' === $type && $wp_org_plugin_details['is_existed']
269 && version_compare( $wp_org_plugin_details['last_version'], $bug['fixed_in'], '<=' )
270 ) {
271 // This is another plugin but with the same slug.
272 continue;
273 }
274 $raw_data['bugs'][] = array(
275 'vuln_type' => $bug['vuln_type'],
276 'title' => $bug['title'],
277 'ref' => $bug['references'],
278 'fixed_in' => $bug['fixed_in'],
279 'cvss_score' => $bug['cvss']['score'] ?? '',
280 );
281 }
282
283 $model->add_item( Scan_Item::TYPE_VULNERABILITY, $raw_data );
284 }
285 }
286 }
287 }
288
289 /**
290 * Adds ignored vulnerability issues to the scan.
291 *
292 * @param array $ignored_issues The array of ignored vulnerability issues to add.
293 */
294 private function add_ignored_issues( $ignored_issues ) {
295 if ( ! empty( $ignored_issues ) ) {
296 foreach ( $ignored_issues as $issue ) {
297 $this->scan->add_item( Scan_Item::TYPE_VULNERABILITY, $issue->raw_data, Scan_Item::STATUS_IGNORE );
298 }
299 }
300 }
301 }
302