VaultPress - Version 1.8.6

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 Icon 128x128 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 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
6
- Stable tag: 1.8.4
7
  License: GPLv2
8
 
9
- VaultPress is a subscription service offering realtime backup, automated security scanning, and support from WordPress experts.
10
 
11
  == Description ==
12
 
13
- [VaultPress](http://vaultpress.com/?utm_source=plugin-readme&utm_medium=description&utm_campaign=1.0) is a real-time backup and security scanning service designed and built by [Automattic](http://automattic.com/), the same company that operates 25+ million sites on WordPress.com.
14
 
15
- The VaultPress plugin provides the required functionality to backup and synchronize every post, comment, media file, revision and dashboard settings on our servers. To start safeguarding your site, you need to sign up for a VaultPress subscription.
16
 
17
- [wpvideo TxdSIdpO]
18
-
19
- For more information, check out [VaultPress.com](http://vaultpress.com/).
20
 
21
  == Installation ==
22
 
23
- 1. Search for VaultPress in the WordPress.org plugin directory and click install. Or, upload the files to your `wp-content/vaultpress/` folder.
24
- 2. Visit `wp-admin/plugins.php` and activate the VaultPress plugin.
25
- 3. Head to `wp-admin/admin.php?page=vaultpress` and enter your site&rsquo;s registration key. You can purchase your registration key at [VaultPress.com](http://vaultpress.com/plugin/?utm_source=plugin-readme&utm_medium=installation&utm_campaign=1.0)
26
 
27
- You can find more detailed instructions at [http://vaultpress.com/](http://help.vaultpress.com/install-vaultpress/?utm_source=plugin-readme&utm_medium=description&utm_campaign=1.0)
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 VaultPress plan? =
34
-
35
- All plans include Daily or Realtime Backups, Downloadable Archives for Restoring, Vitality Statistics, and the Activity Log.
36
 
37
- The Lite plan provides Daily Backups, a 30-day backup archive and automated restores.
38
 
39
- The Basic plan provides Realtime Backups to protect your changes as they happen and support services.
40
 
41
- The Premium plan provides priority recovery and support services, along with site migration assistance. The Premium plan provides automated security scanning of Core, Theme, and Plugin files.
42
 
43
- Update-to-date pricing and features can always be found on the [Plans &amp; Pricing](http://vaultpress.com/plugin/?utm_source=plugin-readme&utm_medium=installation&utm_campaign=1.0) page.
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: blue url(images/vp-toolbar-icon-trans.png) no-repeat 0 2px;
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&amp;utm_medium=plugin-description&amp;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&amp;utm_medium=plugin-description&amp;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&amp;utm_medium=plugin-description&amp;utm_campaign=1.0" rel="nofollow">Need some help?</a>
6
- * Version: 1.8.5
7
  * Author: Automattic
8
  * Author URI: http://vaultpress.com/?utm_source=author-uri&amp;utm_medium=plugin-description&amp;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.5';
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->register_via_jetpack();
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 register_via_jetpack() {
2487
  if ( !class_exists('Jetpack') )
2488
  return false;
2489
 
2490
  Jetpack::load_xml_rpc_client();
2491
- $xml = new Jetpack_IXR_Client( array( 'user_id' => get_current_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&amp;utm_medium=plugin-description&amp;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&amp;utm_medium=plugin-description&amp;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&amp;utm_medium=plugin-description&amp;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&amp;utm_medium=plugin-description&amp;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
- reset( $signature->patterns );
132
- while ( $is_vulnerable && list( , $pattern ) = each( $signature->patterns ) ) {
133
- if ( ! $match = preg_grep( '#' . addcslashes( $pattern, '#' ) . '#im', $file_content ) ) {
134
- $is_vulnerable = false;
135
- break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  }
137
- $matches += $match;
138
  }
139
  } else {
140
  $is_vulnerable = false;
141
  }
142
- $debug_data = array( 'matches' => $matches );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 );