Version Description
- 26 January 2016 =
- Compatibility updates
- Security hotfixes
- Improved performance for security scanner
- Misc small bugfixes
Download this release
Release Info
Developer | thingalon |
Plugin | VaultPress |
Version | 1.8.6 |
Comparing to | |
See all releases |
Code changes from version 1.8.5 to 1.8.6
- class.vaultpress-cli.php +26 -0
- class.vaultpress-hotfixes.php +43 -5
- readme.txt +21 -19
- styles.css +1 -1
- vaultpress.php +24 -6
- vp-scanner.php +203 -14
class.vaultpress-cli.php
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
WP_CLI::add_command( 'vaultpress', 'VaultPress_CLI' );
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Filter spam comments.
|
7 |
+
*/
|
8 |
+
class VaultPress_CLI extends WP_CLI_Command {
|
9 |
+
/**
|
10 |
+
* Automatically registers VaultPress via the Jetpack plugin (if already signed up).
|
11 |
+
*
|
12 |
+
* ## EXAMPLES
|
13 |
+
*
|
14 |
+
* wp vaultpress register_via_jetpack
|
15 |
+
*
|
16 |
+
* @alias comment-check
|
17 |
+
*/
|
18 |
+
public function register_via_jetpack() {
|
19 |
+
$result = VaultPress::init()->register_via_jetpack( true );
|
20 |
+
if ( is_wp_error( $result ) ) {
|
21 |
+
WP_CLI::error( 'Failed to register VaultPress: ' . $result->get_error_message() );
|
22 |
+
} else {
|
23 |
+
WP_CLI::line( 'Successfully registered VaultPress via Jetpack.' );
|
24 |
+
}
|
25 |
+
}
|
26 |
+
}
|
class.vaultpress-hotfixes.php
CHANGED
@@ -56,18 +56,20 @@ class VaultPress_Hotfixes {
|
|
56 |
// WooThemes < 3.8.3, foxypress, asset-manager, wordpress-member-private-conversation.
|
57 |
$end_execution = false;
|
58 |
if ( isset( $_SERVER['SCRIPT_FILENAME'] ) )
|
59 |
-
foreach ( array( 'preview-shortcode-external.php', 'uploadify.php', 'doupload.php', 'cef-upload.php', 'upload.php' ) as $vulnerable_script )
|
60 |
if ( $vulnerable_script == basename( $_SERVER['SCRIPT_FILENAME'] ) ) {
|
61 |
-
switch( $vulnerable_script ) {
|
62 |
case 'upload.php':
|
63 |
$pma_config_file = realpath( dirname( $_SERVER['SCRIPT_FILENAME'] ) . DIRECTORY_SEPARATOR . 'paam-config-ajax.php' );
|
64 |
-
if ( !in_array( $pma_config_file, get_included_files() ) )
|
65 |
break;
|
|
|
66 |
default:
|
67 |
$end_execution = true;
|
68 |
break 2;
|
69 |
}
|
70 |
}
|
|
|
71 |
if ( $end_execution )
|
72 |
die( 'Disabled for security reasons' );
|
73 |
|
@@ -119,6 +121,41 @@ class VaultPress_Hotfixes {
|
|
119 |
|
120 |
// Protect Akismet < 3.1.5 from stored XSS in admin page
|
121 |
add_filter( 'init', array( $this, 'protect_akismet_comment_xss' ), 50 );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
122 |
}
|
123 |
|
124 |
function protect_jetpack_402_from_oembed_xss() {
|
@@ -1077,11 +1114,12 @@ class VaultPress_kses {
|
|
1077 |
$string2 = strtolower($string2);
|
1078 |
|
1079 |
$allowed = false;
|
1080 |
-
foreach ( (array) $allowed_protocols as $one_protocol )
|
1081 |
-
if ( strtolower($one_protocol) == $string2 ) {
|
1082 |
$allowed = true;
|
1083 |
break;
|
1084 |
}
|
|
|
1085 |
|
1086 |
if ($allowed)
|
1087 |
return "$string2:";
|
56 |
// WooThemes < 3.8.3, foxypress, asset-manager, wordpress-member-private-conversation.
|
57 |
$end_execution = false;
|
58 |
if ( isset( $_SERVER['SCRIPT_FILENAME'] ) )
|
59 |
+
foreach ( array( 'preview-shortcode-external.php', 'uploadify.php', 'doupload.php', 'cef-upload.php', 'upload.php' ) as $vulnerable_script ) {
|
60 |
if ( $vulnerable_script == basename( $_SERVER['SCRIPT_FILENAME'] ) ) {
|
61 |
+
switch ( $vulnerable_script ) {
|
62 |
case 'upload.php':
|
63 |
$pma_config_file = realpath( dirname( $_SERVER['SCRIPT_FILENAME'] ) . DIRECTORY_SEPARATOR . 'paam-config-ajax.php' );
|
64 |
+
if ( false === $pma_config_file || ! in_array( $pma_config_file, get_included_files() ) ) {
|
65 |
break;
|
66 |
+
}
|
67 |
default:
|
68 |
$end_execution = true;
|
69 |
break 2;
|
70 |
}
|
71 |
}
|
72 |
+
}
|
73 |
if ( $end_execution )
|
74 |
die( 'Disabled for security reasons' );
|
75 |
|
121 |
|
122 |
// Protect Akismet < 3.1.5 from stored XSS in admin page
|
123 |
add_filter( 'init', array( $this, 'protect_akismet_comment_xss' ), 50 );
|
124 |
+
|
125 |
+
if ( version_compare( $wp_version, '4.7.1', '<=' ) ) {
|
126 |
+
// Protect WordPress 4.4 - 4.7.1 against WP REST type abuse
|
127 |
+
if ( version_compare( $wp_version, '4.4', '>=' ) ) {
|
128 |
+
add_filter( 'rest_pre_dispatch', array( $this, 'protect_rest_type_juggling' ), 10, 3 );
|
129 |
+
}
|
130 |
+
|
131 |
+
// Protect WordPress 4.0 - 4.7.1 against faulty youtube embeds
|
132 |
+
if ( version_compare( $wp_version, '4.0', '>=' ) ) {
|
133 |
+
$this->protect_youtube_embeds();
|
134 |
+
}
|
135 |
+
}
|
136 |
+
|
137 |
+
}
|
138 |
+
|
139 |
+
function protect_rest_type_juggling( $replace, $server, $request ) {
|
140 |
+
if ( isset( $request['id'] ) ) {
|
141 |
+
$request['id'] = intval( $request['id'] );
|
142 |
+
}
|
143 |
+
|
144 |
+
return $replace;
|
145 |
+
}
|
146 |
+
|
147 |
+
function protect_youtube_embeds() {
|
148 |
+
if ( ! apply_filters( 'load_default_embeds', true ) ) {
|
149 |
+
return;
|
150 |
+
}
|
151 |
+
|
152 |
+
wp_embed_unregister_handler( 'youtube_embed_url' );
|
153 |
+
wp_embed_register_handler( 'youtube_embed_url', '#https?://(www.)?youtube\.com/(?:v|embed)/([^/]+)#i', array( $this, 'safe_embed_handler_youtube' ), 9, 4 );
|
154 |
+
}
|
155 |
+
|
156 |
+
function safe_embed_handler_youtube( $matches, $attr, $url, $rawattr ) {
|
157 |
+
$matches[2] = urlencode( $matches[2] );
|
158 |
+
return( wp_embed_handler_youtube( $matches, $attr, $url, $rawattr ) );
|
159 |
}
|
160 |
|
161 |
function protect_jetpack_402_from_oembed_xss() {
|
1114 |
$string2 = strtolower($string2);
|
1115 |
|
1116 |
$allowed = false;
|
1117 |
+
foreach ( (array) $allowed_protocols as $one_protocol ) {
|
1118 |
+
if ( strtolower( $one_protocol ) == $string2 ) {
|
1119 |
$allowed = true;
|
1120 |
break;
|
1121 |
}
|
1122 |
+
}
|
1123 |
|
1124 |
if ($allowed)
|
1125 |
return "$string2:";
|
readme.txt
CHANGED
@@ -2,45 +2,41 @@
|
|
2 |
Contributors: automattic, apokalyptik, briancolinger, josephscott, shaunandrews, xknown, thingalon
|
3 |
Tags: security, malware, virus, archive, back up, back ups, backup, backups, scanning, restore, wordpress backup, site backup, website backup
|
4 |
Requires at least: 3.2
|
5 |
-
Tested up to: 4.
|
6 |
-
Stable tag: 1.8.
|
7 |
License: GPLv2
|
8 |
|
9 |
-
VaultPress is a subscription service offering
|
10 |
|
11 |
== Description ==
|
12 |
|
13 |
-
[VaultPress](http://vaultpress.com
|
14 |
|
15 |
-
|
16 |
|
17 |
-
[
|
18 |
-
|
19 |
-
For more information, check out [VaultPress.com](http://vaultpress.com/).
|
20 |
|
21 |
== Installation ==
|
22 |
|
23 |
-
1.
|
24 |
-
2.
|
25 |
-
3.
|
26 |
|
27 |
-
|
28 |
|
29 |
== Frequently Asked Questions ==
|
30 |
|
31 |
View our full list of FAQs at [http://help.vaultpress.com/faq/](http://help.vaultpress.com/faq/?utm_source=plugin-readme&utm_medium=faq&utm_campaign=1.0)
|
32 |
|
33 |
-
= What’s included in each
|
34 |
-
|
35 |
-
All plans include Daily or Realtime Backups, Downloadable Archives for Restoring, Vitality Statistics, and the Activity Log.
|
36 |
|
37 |
-
|
38 |
|
39 |
-
The
|
40 |
|
41 |
-
The Premium
|
42 |
|
43 |
-
|
44 |
|
45 |
= How many sites can I protect with VaultPress? =
|
46 |
|
@@ -51,6 +47,12 @@ A VaultPress subscription is for a single WordPress site. You can purchase addit
|
|
51 |
Yes, VaultPress supports Multisite installs. Each site will require its own subscription.
|
52 |
|
53 |
== Changelog ==
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
= 1.8.5 - 7 August 2016 =
|
55 |
* Delete plugin option when plugin is deleted via admin area.
|
56 |
* Fix horizontal scroll bar on the fresh installation settings page at high resolutions.
|
2 |
Contributors: automattic, apokalyptik, briancolinger, josephscott, shaunandrews, xknown, thingalon
|
3 |
Tags: security, malware, virus, archive, back up, back ups, backup, backups, scanning, restore, wordpress backup, site backup, website backup
|
4 |
Requires at least: 3.2
|
5 |
+
Tested up to: 4.7.2
|
6 |
+
Stable tag: 1.8.6
|
7 |
License: GPLv2
|
8 |
|
9 |
+
VaultPress is a subscription service offering real-time backup, automated security scanning, and support from WordPress experts.
|
10 |
|
11 |
== Description ==
|
12 |
|
13 |
+
[VaultPress](http://vaultpress.com/plans) is a real-time backup and security scanning service designed and built by [Automattic](http://automattic.com/), the same company that operates (and backs up!) millions of sites on WordPress.com.
|
14 |
|
15 |
+
VaultPress is now powered by Jetpack and effortlessly backs up every post, comment, media file, revision, and dashboard setting on your site to our servers. With VaultPress you're protected against hackers, malware, accidental damage, and host outages.
|
16 |
|
17 |
+
To subscribe visit [VaultPress.com](http://vaultpress.com/plans).
|
|
|
|
|
18 |
|
19 |
== Installation ==
|
20 |
|
21 |
+
1. [Visit our plans page](http://vaultpress.com/plans) and choose the subscription best suited to your needs.
|
22 |
+
2. Follow the on-screen instructions to check-out and pay.
|
23 |
+
3. We will automatically install and configure VaultPress for you.
|
24 |
|
25 |
+
If you run into any difficulties please [contact us](https://vaultpress.com/contact/)
|
26 |
|
27 |
== Frequently Asked Questions ==
|
28 |
|
29 |
View our full list of FAQs at [http://help.vaultpress.com/faq/](http://help.vaultpress.com/faq/?utm_source=plugin-readme&utm_medium=faq&utm_campaign=1.0)
|
30 |
|
31 |
+
= What’s included in each plan? =
|
|
|
|
|
32 |
|
33 |
+
All plans include automated daily backups (unlimited storage space) of your entire site, 1-click restores, stats, priority support, brute force attack protection, uptime monitoring, spam protection, site migration, and an activity log.
|
34 |
|
35 |
+
The Personal and Premium plans are limited to a 30-day backup archive while Professional is unlimited.
|
36 |
|
37 |
+
The Premium and Professional plans also offer automated security scanning against malware and infiltrations with the Professional plan also offering automated threat resolution.
|
38 |
|
39 |
+
[Visit our site](https://vaultpress.com/contact/) for more detail and up-to-date information.
|
40 |
|
41 |
= How many sites can I protect with VaultPress? =
|
42 |
|
47 |
Yes, VaultPress supports Multisite installs. Each site will require its own subscription.
|
48 |
|
49 |
== Changelog ==
|
50 |
+
= 1.8.6 - 26 January 2016 =
|
51 |
+
* Compatibility updates
|
52 |
+
* Security hotfixes
|
53 |
+
* Improved performance for security scanner
|
54 |
+
* Misc small bugfixes
|
55 |
+
|
56 |
= 1.8.5 - 7 August 2016 =
|
57 |
* Delete plugin option when plugin is deleted via admin area.
|
58 |
* Fix horizontal scroll bar on the fresh installation settings page at high resolutions.
|
styles.css
CHANGED
@@ -343,7 +343,7 @@ Header
|
|
343 |
left: 8px;
|
344 |
width: 16px;
|
345 |
height: 21px;
|
346 |
-
background:
|
347 |
background-size: 16px;
|
348 |
}
|
349 |
|
343 |
left: 8px;
|
344 |
width: 16px;
|
345 |
height: 21px;
|
346 |
+
background: transparent url(images/vp-toolbar-icon-trans.png) no-repeat 0 2px;
|
347 |
background-size: 16px;
|
348 |
}
|
349 |
|
vaultpress.php
CHANGED
@@ -3,7 +3,7 @@
|
|
3 |
* Plugin Name: VaultPress
|
4 |
* Plugin URI: http://vaultpress.com/?utm_source=plugin-uri&utm_medium=plugin-description&utm_campaign=1.0
|
5 |
* Description: Protect your content, themes, plugins, and settings with <strong>realtime backup</strong> and <strong>automated security scanning</strong> from <a href="http://vaultpress.com/?utm_source=wp-admin&utm_medium=plugin-description&utm_campaign=1.0" rel="nofollow">VaultPress</a>. Activate, enter your registration key, and never worry again. <a href="http://vaultpress.com/help/?utm_source=wp-admin&utm_medium=plugin-description&utm_campaign=1.0" rel="nofollow">Need some help?</a>
|
6 |
-
* Version: 1.8.
|
7 |
* Author: Automattic
|
8 |
* Author URI: http://vaultpress.com/?utm_source=author-uri&utm_medium=plugin-description&utm_campaign=1.0
|
9 |
* License: GPL2+
|
@@ -18,7 +18,7 @@ class VaultPress {
|
|
18 |
var $option_name = 'vaultpress';
|
19 |
var $auto_register_option = 'vaultpress_auto_register';
|
20 |
var $db_version = 4;
|
21 |
-
var $plugin_version = '1.8.
|
22 |
|
23 |
function __construct() {
|
24 |
register_activation_hook( __FILE__, array( $this, 'activate' ) );
|
@@ -96,6 +96,10 @@ class VaultPress {
|
|
96 |
|
97 |
// force a connection check after an activation
|
98 |
$this->clear_connection();
|
|
|
|
|
|
|
|
|
99 |
}
|
100 |
|
101 |
function deactivate() {
|
@@ -425,7 +429,7 @@ class VaultPress {
|
|
425 |
|
426 |
// if registering via Jetpack, get a key...
|
427 |
if ( isset( $_POST['key_source'] ) && 'jetpack' === $_POST['key_source'] ) {
|
428 |
-
$registration_key = $this->
|
429 |
if ( is_wp_error( $registration_key ) ) {
|
430 |
$this->update_option( 'connection_error_code', -2 );
|
431 |
$this->update_option(
|
@@ -2483,19 +2487,28 @@ JS;
|
|
2483 |
return new WP_Error( $xml->getErrorCode(), $xml->getErrorMessage() );
|
2484 |
}
|
2485 |
|
2486 |
-
function
|
2487 |
if ( !class_exists('Jetpack') )
|
2488 |
return false;
|
2489 |
|
2490 |
Jetpack::load_xml_rpc_client();
|
2491 |
-
$xml = new Jetpack_IXR_Client( array( 'user_id' =>
|
2492 |
-
$xml->query( 'vaultpress.registerSite' );
|
2493 |
if ( ! $xml->isError() ) {
|
2494 |
return $xml->getResponse();
|
2495 |
}
|
2496 |
|
2497 |
return new WP_Error( $xml->getErrorCode(), $xml->getErrorMessage() );
|
2498 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2499 |
}
|
2500 |
|
2501 |
$vaultpress = VaultPress::init();
|
@@ -2537,4 +2550,9 @@ if ( isset( $_GET['vaultpress'] ) && $_GET['vaultpress'] ) {
|
|
2537 |
require_once( dirname( __FILE__ ) . '/class.vaultpress-hotfixes.php' );
|
2538 |
$hotfixes = new VaultPress_Hotfixes();
|
2539 |
|
|
|
|
|
|
|
|
|
|
|
2540 |
include_once( dirname( __FILE__ ) . '/cron-tasks.php' );
|
3 |
* Plugin Name: VaultPress
|
4 |
* Plugin URI: http://vaultpress.com/?utm_source=plugin-uri&utm_medium=plugin-description&utm_campaign=1.0
|
5 |
* Description: Protect your content, themes, plugins, and settings with <strong>realtime backup</strong> and <strong>automated security scanning</strong> from <a href="http://vaultpress.com/?utm_source=wp-admin&utm_medium=plugin-description&utm_campaign=1.0" rel="nofollow">VaultPress</a>. Activate, enter your registration key, and never worry again. <a href="http://vaultpress.com/help/?utm_source=wp-admin&utm_medium=plugin-description&utm_campaign=1.0" rel="nofollow">Need some help?</a>
|
6 |
+
* Version: 1.8.6
|
7 |
* Author: Automattic
|
8 |
* Author URI: http://vaultpress.com/?utm_source=author-uri&utm_medium=plugin-description&utm_campaign=1.0
|
9 |
* License: GPL2+
|
18 |
var $option_name = 'vaultpress';
|
19 |
var $auto_register_option = 'vaultpress_auto_register';
|
20 |
var $db_version = 4;
|
21 |
+
var $plugin_version = '1.8.6';
|
22 |
|
23 |
function __construct() {
|
24 |
register_activation_hook( __FILE__, array( $this, 'activate' ) );
|
96 |
|
97 |
// force a connection check after an activation
|
98 |
$this->clear_connection();
|
99 |
+
|
100 |
+
if ( get_option( 'vaultpress_auto_connect' ) ) {
|
101 |
+
$this->register_via_jetpack( true );
|
102 |
+
}
|
103 |
}
|
104 |
|
105 |
function deactivate() {
|
429 |
|
430 |
// if registering via Jetpack, get a key...
|
431 |
if ( isset( $_POST['key_source'] ) && 'jetpack' === $_POST['key_source'] ) {
|
432 |
+
$registration_key = $this->get_key_via_jetpack();
|
433 |
if ( is_wp_error( $registration_key ) ) {
|
434 |
$this->update_option( 'connection_error_code', -2 );
|
435 |
$this->update_option(
|
2487 |
return new WP_Error( $xml->getErrorCode(), $xml->getErrorMessage() );
|
2488 |
}
|
2489 |
|
2490 |
+
function get_key_via_jetpack( $already_purchased = false ) {
|
2491 |
if ( !class_exists('Jetpack') )
|
2492 |
return false;
|
2493 |
|
2494 |
Jetpack::load_xml_rpc_client();
|
2495 |
+
$xml = new Jetpack_IXR_Client( array( 'user_id' => Jetpack_Options::get_option( 'master_user' ) ) );
|
2496 |
+
$xml->query( 'vaultpress.registerSite', $already_purchased );
|
2497 |
if ( ! $xml->isError() ) {
|
2498 |
return $xml->getResponse();
|
2499 |
}
|
2500 |
|
2501 |
return new WP_Error( $xml->getErrorCode(), $xml->getErrorMessage() );
|
2502 |
}
|
2503 |
+
|
2504 |
+
function register_via_jetpack( $already_purchased = false ) {
|
2505 |
+
$registration_key = $this->get_key_via_jetpack( $already_purchased );
|
2506 |
+
if ( is_wp_error( $registration_key ) ) {
|
2507 |
+
return $registration_key;
|
2508 |
+
}
|
2509 |
+
|
2510 |
+
return self::register( $registration_key );
|
2511 |
+
}
|
2512 |
}
|
2513 |
|
2514 |
$vaultpress = VaultPress::init();
|
2550 |
require_once( dirname( __FILE__ ) . '/class.vaultpress-hotfixes.php' );
|
2551 |
$hotfixes = new VaultPress_Hotfixes();
|
2552 |
|
2553 |
+
// Add a helper method to WP CLI for auto-registerion via Jetpack
|
2554 |
+
if ( defined( 'WP_CLI' ) && WP_CLI ) {
|
2555 |
+
require_once( dirname( __FILE__ ) . '/class.vaultpress-cli.php' );
|
2556 |
+
}
|
2557 |
+
|
2558 |
include_once( dirname( __FILE__ ) . '/cron-tasks.php' );
|
vp-scanner.php
CHANGED
@@ -92,6 +92,129 @@ function vp_is_interesting_file($file) {
|
|
92 |
return preg_match( $scan_only_regex, $file );
|
93 |
}
|
94 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
95 |
/**
|
96 |
* Scans a file with the registered signatures. To report a security notice for a specified signature, all its regular
|
97 |
* expressions should result in a match.
|
@@ -99,47 +222,113 @@ function vp_is_interesting_file($file) {
|
|
99 |
* @param null $tmp_file used if the file to be scanned doesn't exist or if the filename doesn't match vp_is_interesting_file().
|
100 |
* @return array|bool false if no matched signature is found. A list of matched signatures otherwise.
|
101 |
*/
|
102 |
-
function vp_scan_file($file, $tmp_file = null) {
|
103 |
$real_file = vp_get_real_file_path( $file, $tmp_file );
|
104 |
$file_size = file_exists( $real_file ) ? @filesize( $real_file ) : 0;
|
105 |
-
if ( !is_readable( $real_file ) || !$file_size || $file_size > apply_filters( 'scan_max_file_size', 3 * 1024 * 1024 ) ) // don't scan empty or files larger than 3MB.
|
106 |
return false;
|
|
|
107 |
|
108 |
$file_content = null;
|
|
|
109 |
$skip_file = apply_filters_ref_array( 'pre_scan_file', array ( false, $file, $real_file, &$file_content ) );
|
110 |
-
if ( false !== $skip_file ) // maybe detect malware without regular expressions.
|
111 |
return $skip_file;
|
|
|
112 |
|
113 |
-
if ( !vp_is_interesting_file( $file ) ) // only scan relevant files.
|
114 |
return false;
|
|
|
115 |
|
116 |
-
if ( !isset( $GLOBALS['vp_signatures'] ) )
|
117 |
$GLOBALS['vp_signatures'] = array();
|
|
|
118 |
|
119 |
$found = array ();
|
120 |
foreach ( $GLOBALS['vp_signatures'] as $signature ) {
|
121 |
-
if ( !is_object( $signature ) || !isset( $signature->patterns ) )
|
122 |
continue;
|
|
|
123 |
// if there is no filename_regex, we assume it's the same of vp_is_interesting_file().
|
124 |
if ( empty( $signature->filename_regex ) || preg_match( '#' . addcslashes( $signature->filename_regex, '#' ) . '#i', $file ) ) {
|
125 |
-
if ( null === $file_content || !is_array( $file_content ) )
|
126 |
$file_content = file( $real_file );
|
127 |
|
|
|
|
|
|
|
|
|
|
|
128 |
$is_vulnerable = true;
|
129 |
$matches = array ();
|
130 |
if ( is_array( $file_content ) && ( $signature->patterns ) && is_array( $signature->patterns ) ) {
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
$
|
135 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
136 |
}
|
137 |
-
$matches += $match;
|
138 |
}
|
139 |
} else {
|
140 |
$is_vulnerable = false;
|
141 |
}
|
142 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
143 |
// Additional checking needed?
|
144 |
if ( method_exists( $signature, 'get_detailed_scanner' ) && $scanner = $signature->get_detailed_scanner() )
|
145 |
$is_vulnerable = $scanner->scan( $is_vulnerable, $file, $real_file, $file_content, $debug_data );
|
92 |
return preg_match( $scan_only_regex, $file );
|
93 |
}
|
94 |
|
95 |
+
/**
|
96 |
+
* Uses the PHP tokenizer to split a file into 3 arrays: PHP code with no comments,
|
97 |
+
* PHP code with comments, and HTML/JS code. Helper wrapper around split_to_php_html()
|
98 |
+
*
|
99 |
+
* @param string $file The file path to read and parse
|
100 |
+
* @return array An array with 3 arrays of lines
|
101 |
+
*/
|
102 |
+
function split_file_to_php_html( $file ) {
|
103 |
+
$source = file_get_contents( $file );
|
104 |
+
return split_to_php_html( $source );
|
105 |
+
}
|
106 |
+
|
107 |
+
/**
|
108 |
+
* Uses the PHP tokenizer to split a string into 3 arrays: PHP code with no comments,
|
109 |
+
* PHP code with comments, and HTML/JS code.
|
110 |
+
*
|
111 |
+
* @param string $file The file path to read and parse
|
112 |
+
* @return array An array with 3 arrays of lines
|
113 |
+
*/
|
114 |
+
function split_to_php_html( $source ) {
|
115 |
+
$tokens = token_get_all( $source );
|
116 |
+
|
117 |
+
$ret = array( 'php' => array(), 'php-with-comments' => array(), 'html' => array() );
|
118 |
+
$current_line = 0;
|
119 |
+
$mode = 'html'; // need to see an open tag to switch to PHP mode
|
120 |
+
|
121 |
+
foreach ( $tokens as $token ) {
|
122 |
+
if ( ! is_array( $token ) ) {
|
123 |
+
// single character, can't switch our mode; just add it and continue
|
124 |
+
// if it's PHP, should go into both versions; mode 'php' will do that
|
125 |
+
add_text_to_parsed( $ret, $mode, $current_line, $token );
|
126 |
+
$current_line += substr_count( $token, "\n" );
|
127 |
+
} else {
|
128 |
+
// more complex tokens is the interesting case
|
129 |
+
list( $id, $text, $line ) = $token;
|
130 |
+
|
131 |
+
if ( 'php' === $mode ) {
|
132 |
+
// we're in PHP code
|
133 |
+
|
134 |
+
// might be a comment
|
135 |
+
if ( T_COMMENT === $id || T_DOC_COMMENT === $id ) {
|
136 |
+
// add it to the PHP with comments array only
|
137 |
+
add_text_to_parsed( $ret, 'php-with-comments', $current_line, $text );
|
138 |
+
|
139 |
+
// special case for lines like: " // comment\n":
|
140 |
+
// if we're adding a comment with a newline, and the 'php' array current line
|
141 |
+
// has no trailing newline, add one
|
142 |
+
if ( substr_count( $text, "\n" ) >= 1 && isset( $ret['php'][ $current_line ] ) && 0 === substr_count( $ret['php'][ $current_line ], "\n" ) ) {
|
143 |
+
$ret['php'][ $current_line ] .= "\n";
|
144 |
+
}
|
145 |
+
|
146 |
+
// make sure to count newlines in comments
|
147 |
+
$current_line += substr_count( $text, "\n" );
|
148 |
+
continue;
|
149 |
+
}
|
150 |
+
|
151 |
+
// otherwise add it to both the PHP array and the with comments array
|
152 |
+
add_text_to_parsed( $ret, $mode, $current_line, $text );
|
153 |
+
|
154 |
+
// then see if we're breaking out
|
155 |
+
if ( T_CLOSE_TAG === $id ) {
|
156 |
+
$mode = 'html';
|
157 |
+
}
|
158 |
+
} else if ( 'html' === $mode ) {
|
159 |
+
// we're in HTML code
|
160 |
+
|
161 |
+
// if we see an open tag, switch to PHP
|
162 |
+
if ( T_OPEN_TAG === $id || T_OPEN_TAG_WITH_ECHO === $id ) {
|
163 |
+
$mode = 'php';
|
164 |
+
}
|
165 |
+
|
166 |
+
// add to the HTML array (or PHP if it was an open tag)
|
167 |
+
// if it is PHP, this will add it to both arrays, which is what we want
|
168 |
+
add_text_to_parsed( $ret, $mode, $current_line, $text );
|
169 |
+
}
|
170 |
+
$current_line += substr_count( $text, "\n" );
|
171 |
+
}
|
172 |
+
}
|
173 |
+
|
174 |
+
return $ret;
|
175 |
+
}
|
176 |
+
|
177 |
+
/**
|
178 |
+
* Helper function for split_file_to_php_html; adds a chunk of text to the arrays we'll return.
|
179 |
+
* @param array $parsed The array containing all the languages we'll return
|
180 |
+
* @param string $prefix The prefix for the languages we want to add this text to
|
181 |
+
* @param int $line_number The line number that this text goes on
|
182 |
+
* @param string $text The text to add
|
183 |
+
*/
|
184 |
+
function add_text_to_parsed( &$parsed, $prefix, $start_line_number, $all_text ) {
|
185 |
+
$line_number = $start_line_number;
|
186 |
+
|
187 |
+
// whitespace tokens may span multiple lines; we need to split them up so that the indentation goes on the next line
|
188 |
+
$fragments = explode( "\n", $all_text );
|
189 |
+
foreach ( $fragments as $i => $fragment ) {
|
190 |
+
// each line needs to end with a newline to match the behavior of file()
|
191 |
+
if ( $i < count( $fragments ) - 1 ) {
|
192 |
+
$text = $fragment . "\n";
|
193 |
+
} else {
|
194 |
+
$text = $fragment;
|
195 |
+
}
|
196 |
+
|
197 |
+
if ( '' === $text ) {
|
198 |
+
// check for the empty string explicitly, rather than using empty()
|
199 |
+
// otherwise things like a '0' token will get skipped, because PHP is stupid
|
200 |
+
continue;
|
201 |
+
}
|
202 |
+
|
203 |
+
if ( ! isset( $parsed[ $prefix ][ $line_number ] ) ) {
|
204 |
+
$parsed[ $prefix ][ $line_number ] = '';
|
205 |
+
}
|
206 |
+
$parsed[ $prefix ][ $line_number ] .= $text;
|
207 |
+
if ( 'php' == $prefix ) {
|
208 |
+
if ( ! isset( $parsed[ 'php-with-comments' ][ $line_number ] ) ) {
|
209 |
+
$parsed[ 'php-with-comments' ][ $line_number ] = '';
|
210 |
+
}
|
211 |
+
$parsed[ 'php-with-comments' ][ $line_number ] .= $text;
|
212 |
+
}
|
213 |
+
|
214 |
+
// the caller will also update their line number based on the number of \n characters in the text
|
215 |
+
$line_number++;
|
216 |
+
}
|
217 |
+
}
|
218 |
/**
|
219 |
* Scans a file with the registered signatures. To report a security notice for a specified signature, all its regular
|
220 |
* expressions should result in a match.
|
222 |
* @param null $tmp_file used if the file to be scanned doesn't exist or if the filename doesn't match vp_is_interesting_file().
|
223 |
* @return array|bool false if no matched signature is found. A list of matched signatures otherwise.
|
224 |
*/
|
225 |
+
function vp_scan_file( $file, $tmp_file = null, $use_parser = false ) {
|
226 |
$real_file = vp_get_real_file_path( $file, $tmp_file );
|
227 |
$file_size = file_exists( $real_file ) ? @filesize( $real_file ) : 0;
|
228 |
+
if ( !is_readable( $real_file ) || !$file_size || $file_size > apply_filters( 'scan_max_file_size', 3 * 1024 * 1024 ) ) { // don't scan empty or files larger than 3MB.
|
229 |
return false;
|
230 |
+
}
|
231 |
|
232 |
$file_content = null;
|
233 |
+
$file_parsed = null;
|
234 |
$skip_file = apply_filters_ref_array( 'pre_scan_file', array ( false, $file, $real_file, &$file_content ) );
|
235 |
+
if ( false !== $skip_file ) { // maybe detect malware without regular expressions.
|
236 |
return $skip_file;
|
237 |
+
}
|
238 |
|
239 |
+
if ( !vp_is_interesting_file( $file ) ) { // only scan relevant files.
|
240 |
return false;
|
241 |
+
}
|
242 |
|
243 |
+
if ( !isset( $GLOBALS['vp_signatures'] ) ) {
|
244 |
$GLOBALS['vp_signatures'] = array();
|
245 |
+
}
|
246 |
|
247 |
$found = array ();
|
248 |
foreach ( $GLOBALS['vp_signatures'] as $signature ) {
|
249 |
+
if ( !is_object( $signature ) || !isset( $signature->patterns ) ) {
|
250 |
continue;
|
251 |
+
}
|
252 |
// if there is no filename_regex, we assume it's the same of vp_is_interesting_file().
|
253 |
if ( empty( $signature->filename_regex ) || preg_match( '#' . addcslashes( $signature->filename_regex, '#' ) . '#i', $file ) ) {
|
254 |
+
if ( null === $file_content || !is_array( $file_content ) ) {
|
255 |
$file_content = file( $real_file );
|
256 |
|
257 |
+
if ( $use_parser ) {
|
258 |
+
$file_parsed = split_file_to_php_html( $real_file );
|
259 |
+
}
|
260 |
+
}
|
261 |
+
|
262 |
$is_vulnerable = true;
|
263 |
$matches = array ();
|
264 |
if ( is_array( $file_content ) && ( $signature->patterns ) && is_array( $signature->patterns ) ) {
|
265 |
+
if ( ! $use_parser ) {
|
266 |
+
reset( $signature->patterns );
|
267 |
+
while ( $is_vulnerable && list( , $pattern ) = each( $signature->patterns ) ) {
|
268 |
+
if ( ! $match = preg_grep( '#' . addcslashes( $pattern, '#' ) . '#im', $file_content ) ) {
|
269 |
+
$is_vulnerable = false;
|
270 |
+
break;
|
271 |
+
}
|
272 |
+
$matches += $match;
|
273 |
+
}
|
274 |
+
} else {
|
275 |
+
// use the language specified in the signature if it has one
|
276 |
+
if ( ! empty( $signature->target_language ) && array_key_exists( $signature->target_language, $file_parsed ) ) {
|
277 |
+
$code = $file_parsed[ $signature->target_language ];
|
278 |
+
} else {
|
279 |
+
$code = $file_content;
|
280 |
+
}
|
281 |
+
// same code as the '! $use_parser' branch above
|
282 |
+
reset( $signature->patterns );
|
283 |
+
while ( $is_vulnerable && list( , $pattern ) = each( $signature->patterns ) ) {
|
284 |
+
if ( ! $match = preg_grep( '#' . addcslashes( $pattern, '#' ) . '#im', $code ) ) {
|
285 |
+
$is_vulnerable = false;
|
286 |
+
break;
|
287 |
+
}
|
288 |
+
$matches += $match;
|
289 |
}
|
|
|
290 |
}
|
291 |
} else {
|
292 |
$is_vulnerable = false;
|
293 |
}
|
294 |
+
|
295 |
+
// convert the matched line to an array of details showing context around the lines
|
296 |
+
$lines = array();
|
297 |
+
if ( $use_parser ) {
|
298 |
+
$lines_parsed = array();
|
299 |
+
$line_indices_parsed = array_keys( $code );
|
300 |
+
}
|
301 |
+
foreach ( $matches as $line => $text ) {
|
302 |
+
$lines = array_merge( $lines, range( $line - 1, $line + 1 ) );
|
303 |
+
if ( $use_parser ) {
|
304 |
+
$idx = array_search( $line, $line_indices_parsed );
|
305 |
+
|
306 |
+
// we might be looking at the first or last line; for the non-parsed case, array_intersect_key
|
307 |
+
// handles this transparently below; for the parsed case, since we have another layer of
|
308 |
+
// indirection, we have to handle that case here
|
309 |
+
$idx_around = array();
|
310 |
+
if ( isset( $line_indices_parsed[ $idx - 1 ] ) ) {
|
311 |
+
$idx_around[] = $line_indices_parsed[ $idx - 1 ];
|
312 |
+
}
|
313 |
+
$idx_around[] = $line_indices_parsed[ $idx ];
|
314 |
+
if ( isset( $line_indices_parsed[ $idx + 1 ] ) ) {
|
315 |
+
$idx_around[] = $line_indices_parsed[ $idx + 1 ];
|
316 |
+
}
|
317 |
+
$lines_parsed = array_merge( $lines_parsed, $idx_around );
|
318 |
+
}
|
319 |
+
}
|
320 |
+
$details = array_intersect_key( $file_content, array_flip( $lines ) );
|
321 |
+
if ( $use_parser ) {
|
322 |
+
$details_parsed = array_intersect_key( $code, array_flip( $lines_parsed ) );
|
323 |
+
}
|
324 |
+
|
325 |
+
// provide both 'matches' and 'details', as some places want 'matches'
|
326 |
+
// this matches the old behavior, which would add 'details' to some items, without replacing 'matches'
|
327 |
+
$debug_data = array( 'matches' => $matches, 'details' => $details );
|
328 |
+
if ( $use_parser ) {
|
329 |
+
$debug_data['details_parsed'] = $details_parsed;
|
330 |
+
}
|
331 |
+
|
332 |
// Additional checking needed?
|
333 |
if ( method_exists( $signature, 'get_detailed_scanner' ) && $scanner = $signature->get_detailed_scanner() )
|
334 |
$is_vulnerable = $scanner->scan( $is_vulnerable, $file, $real_file, $file_content, $debug_data );
|