Plugin Directory

Changeset 1970866


Ignore:
Timestamp:
11/08/2018 11:31:33 AM (7 years ago)
Author:
tnash
Message:

Updating to WP Fingerprint v2.1

Location:
wp-fingerprint
Files:
30 added
1 deleted
7 edited
5 copied

Legend:

Unmodified
Added
Removed
  • wp-fingerprint/tags/2.1/README.txt

    r1853983 r1970866  
    33Tags: security, plugins, checksums
    44Requires at least: 4.9
    5 Tested up to: 4.9.4
     5Tested up to: 4.9.8
    66Requires PHP: 5.4
    77License: GPLv3
    8 Stable tag: 1.0.1
     8Stable tag: 2.1.0
    99== Description ==
    1010WP Fingerprint adds an additional layer of security to your WordPress website, working to check your plugins for signs of hack or exploit. WP Fingerprint works by collecting checksums of your plugins and comparing it with the checksums collected by WP Fingerprint. If the plugin detects any abnormalities it will let you know so you can take immediate action.
    11 This plugin transmits and stores checksums on WP Fingerprint servers (all hosted in EU and run by 34SP.com) to work for details see https://wpfingerprint.com/how-it-works/ for the data we collect and store.
     11This plugin transmits and stores checksums on WP Fingerprint servers(all hosted in EU and run by 34SP.com) & WordPress.org to work for details see https://wpfingerprint.com/how-it-works/ for the data we collect and store.
    1212== Installation ==
    1313As normal click activate to activate the plugin and go make a cup of tea while it works away in the background. Allow an hour after installation for WPFingerprint to complete its first set of checks.
     
    2626
    2727== Changelog ==
     282.1 - Remove notice in admin section, refactored the primary checker, added ability to diff files if source allows, added WP-CLI commands for report and Diff - 16th October 2018
     292.0.4 - Show the source with a human friendly name, clear down logs so they are not showing spurious data
     302.0.3 - Added a "what does this mean notification", created clear logs wp-cli command  - 26th September 2018
     312.0.2 - Fixed issue where incorrectly announcing that checksums did not match - 24th September 2018
     322.0.1 - Fixed bug where Notification counter wasn't showing
     332.0.0 - Rewritten WP Fingerprint see blog post for full details - 19th September 2018
    28341.0.1 - 6th April 2018
    2935Bug fix - Removed notice, when plugin is not in the results array
  • wp-fingerprint/tags/2.1/fingerprint-command.php

    r1829106 r1970866  
    22class Fingerprint_Command extends WP_CLI_Command {
    33
    4     function run( $args, $assoc_args )
     4    /*
     5     * Check Plugin against Checksums
     6     * @alias run
     7     */
     8    function check( $args, $assoc_args )
    59    {
    6         $path = plugin_dir_path( __FILE__ );
    7         $runner = new WPFingerprint_Runner($path);
    8         if(array_key_exists('force',$assoc_args))
     10        $wpfingerprint = new WPFingerprint_Plugin();
     11        if(isset( $wpfingerprint ))
    912        {
     13            $plugin = false;
     14            if(isset($args[0])){
     15                $plugin = $args[0];
     16            }
     17            $diffs = $wpfingerprint->runner($plugin);
     18            $message = 'WP Fingerprint has found: ';
     19            $message .= strval($diffs['new_issues']).' New Issues ';
     20            $message .= 'has updated '.strval($diffs['updated_issues']).' issues';
     21            $message .= ' and removed '.strval($diffs['removed_isues']).' issues';
     22            WP_CLI::success( $message );
     23        }else{
     24            WP_CLI::error( 'Error Running WP Fingerprint' );
     25        }
    1026
    11             $runner->plugins->remove_plugin_checksums();
     27    }
     28    /*
     29     * Create Checksum for a given plugin
     30     *
     31     */
     32    function generate( $args, $assoc_args )
     33    {
     34        $wpfingerprint = new WPFingerprint_Plugin();
     35        if(empty($args)) $args = array();
     36        $generate = $wpfingerprint->generate($args[0]);
     37        if(!empty($generate) || !is_array($generate)){
     38            WP_CLI::error( 'Generate Plugin failed' );
    1239        }
    13         $runner->run();
     40        else{
     41            return json_encode($generate);
     42        }
     43    }
     44    /*
     45     * Show any plugins that have currently failed.
     46     *
     47     */
     48    function report( $args, $assoc_args )
     49    {
     50        $format = 'table';
     51        if(isset($assoc_args['format']))
     52        {
     53            $format_options = array(
     54                'table','csv','yaml','json'
     55            );
     56            if(in_array( $assoc_args['format'], $format_options ))
     57            {
     58                $format = $assoc_args['format'];
     59            }
     60        }
     61        $plugin = false;
     62        if(!empty($args))
     63        {
     64            $plugin = $args[0];
     65        }
     66        $wpfingerprint = new WPFingerprint_Plugin();
     67        $report = $wpfingerprint->runner->report($plugin);
     68        WP_CLI\Utils\format_items( $format, $report, 'plugin,file,checksum_local, checksum_remote, last_checked' );
    1469    }
    1570
     71    function diff( $args, $assoc_args )
     72    {
     73        if(!empty($args))
     74        {
     75            $plugin = $args[0];
     76            $file = $args[1];
     77        }
     78        if(!isset($plugin))
     79        {
     80            WP_CLI::error( 'Need to specify a plugin' );
     81            die;
     82        }
     83        if(!isset($file))
     84        {
     85            WP_CLI::error( 'Need to specify a file' );
     86            die;
     87        }
     88        $wpfingerprint = new WPFingerprint_Plugin();
     89        $version = $wpfingerprint->runner->plugins->get_plugin_version($plugin);
     90        $wpfingerprint->runner->load('transport-wporg');
     91        $transport_name = 'WPFingerprint_Transport_Wporg';
     92        $transport_name = new $transport_name;
     93        $local_file = $wpfingerprint->runner->plugins->read_file_contents($plugin,$file);
     94        if(!$local_file)
     95        {
     96            WP_CLI::error( 'Local file could not be found' );
     97            die;
     98        }
     99        $remote_file = $transport_name->get_plugin_file($plugin,$version,$file);
     100        if(!$remote_file)
     101        {
     102            WP_CLI::error( 'Remote file could not be found' );
     103            die;
     104        }
     105        $diff_file = $wpfingerprint->runner->diff->check_file_diff($local_file,$remote_file);
     106        $format = 'table';
     107        if(isset($assoc_args['format']))
     108        {
     109            $format_options = array(
     110                'table','csv','yaml','json'
     111            );
     112            if(in_array( $assoc_args['format'], $format_options ))
     113            {
     114                $format = $assoc_args['format'];
     115            }
     116        }
     117        $report = $wpfingerprint->runner->diff->show_diffs($diff_file);
     118        WP_CLI\Utils\format_items( $format, $report, 'line,local,remote' );
     119    }
     120    /*
     121     * Delete the logs
     122     *
     123     */
     124    function clear( $args, $assoc_args )
     125    {
     126        $wpfingerprint = new WPFingerprint_Plugin();
     127        $wpfingerprint->runner->model_checksums->clear();
     128        WP_CLI::success( 'Wiped Logs' );
     129    }
    16130}
    17131WP_CLI::add_command( 'fingerprint', 'Fingerprint_Command' );
  • wp-fingerprint/tags/2.1/inc/class-wpfingerprint-plugins.php

    r1829106 r1970866  
    11<?php
    22class WPFingerprint_Plugins{
     3   
    34
    45    private $plugins;
     
    67    function __construct()
    78    {
    8         $this->plugins = array();
     9
    910    }
    1011
    1112    function get()
    1213    {
    13         if(empty($this->plugins)) {
     14
     15        if(empty($this->plugins) && !is_array($this->plugins)) {
    1416            if ( ! function_exists( 'get_plugins' ) ) {
    1517                require_once ABSPATH . 'wp-admin/includes/plugin.php';
     
    5456    }
    5557
     58    function get_all_plugin_names() {
     59        $names = array();
     60        foreach ( get_plugins() as $file => $details ) {
     61            $names[] = $this->get_plugin_name( $file );
     62        }
     63        return $names;
     64    }
     65
     66    function get_some_plugins_names($plugin)
     67    {
     68        $plugins = array();
     69        if(!is_array($plugin))
     70        {
     71            return $plugins[] = $plugin;
     72        }
     73        else{
     74            //Explicit assumption this is a flat array
     75            return $plugin;
     76        }
     77    }
     78
     79    function get_plugin_files( $path ) {
     80        $folder = dirname( $this->get_absolute_path( $path ) );
     81        if ( WP_PLUGIN_DIR === $folder ) {
     82            return (array) $path;
     83        }
     84        return $this->get_files( trailingslashit( $folder ) );
     85    }
     86
     87    function get_files( $path ) {
     88        $filtered_files = array();
     89        try {
     90            $files = new RecursiveIteratorIterator(
     91                new RecursiveDirectoryIterator( $path,
     92                    RecursiveDirectoryIterator::SKIP_DOTS ),
     93                RecursiveIteratorIterator::CHILD_FIRST
     94            );
     95            foreach ( $files as $file_info ) {
     96                $pathname = self::normalize_directory_separators( substr( $file_info->getPathname(), strlen( $path ) ) );
     97                if ( $file_info->isFile()) {
     98                    $filtered_files[] = $pathname;
     99                }
     100            }
     101        } catch ( Exception $e ) {
     102
     103        }
     104        return $filtered_files;
     105    }
     106
     107    public static function normalize_directory_separators( $path ) {
     108        return str_replace( '\\', '/', $path );
     109    }
     110
     111    function get_plugin_version($slug)
     112    {
     113        $plugins = $this->get();
     114        foreach($plugins as $plugin)
     115        {
     116            if($plugin['TextDomain'] == $slug)
     117            {
     118                return $plugin['Version'];
     119            }
     120        }
     121    }
     122
    56123    function get_plugin_checksum($slug)
    57124    {
     
    68135        return update_option('wpfingerprint_checksum', $checksums);
    69136    }
     137
    70138    function remove_plugin_checksums()
    71139    {
     
    73141    }
    74142
     143    function read_file_contents($plugin, $file)
     144    {
     145
     146        return file_get_contents($this->path($plugin).'/'.$file);
     147    }
     148
     149    function filter_files($slug)
     150    {
     151        $no_check = array(
     152            'readme.txt',
     153            'readme.md',
     154            'README.md',
     155            'README.txt'
     156        );
     157        if( in_array($slug, $no_check)) return true;
     158        return false;
     159    }
     160
    75161}
  • wp-fingerprint/tags/2.1/inc/class-wpfingerprint-runner.php

    r1829106 r1970866  
    11<?php
    22class WPFingerprint_Runner{
     3    public $plugins;
     4    public $hash;
     5    public $model_checksums;
     6    public $diff;
     7    public $model_diffs;
     8    public $transport;
    39    private $path;
    4     public $plugins;
    5     function __construct($path)
    6     {
    7         $this->path = $path;
    8         $this->load('plugins');
     10
     11    function __construct()
     12    {
     13        if(isset($this->path)){
     14            $this->path = plugin_dir_path( __FILE__ );
     15        }
    916        if(empty($this->plugins)){
     17            $this->load('plugins');
    1018            $this->plugins = new WPFingerprint_Plugins;
    1119        }
    12         $this->load('api');
    13     }
    14     function run()
    15     {
    16 
    17         $check_plugins = $this->plugins->all();
    18         $return_plugins = array();
    19         var_dump($check_plugins);
    20         foreach ( $check_plugins as $check => $att )
    21         {
    22             if(is_dir($att['path'])){
    23                 $files = new RecursiveIteratorIterator(
    24                 new RecursiveDirectoryIterator( $att['path'],
    25                     RecursiveDirectoryIterator::SKIP_DOTS ),
    26                 RecursiveIteratorIterator::CHILD_FIRST
    27             );
    28                 $dir_files =array();
    29 
    30                 foreach($files as $file_info){
    31                     $pathname = substr( $file_info->getPathname(),  strlen($att['path']) );
    32                 $dir_files[$pathname] = hash_file( 'md5', $file_info->getPathname() );
    33                 }
    34                 $att['files'] = $dir_files;
    35                 unset($dir_files);
    36             }
    37             else{
    38                 $att['files'][$att['path']] = hash_file( 'md5', $att['path']);
    39             }
    40             $result_checksums = $this->validate_checksums($att['files']);
    41             if($att['checksum'] == $result_checksums)
    42             {
    43                 //Don't send the data as its the same as last time
    44                 unset( $check_plugins[$check]);
    45             }
    46             else{
    47                 $this->plugins->update_plugin_checksums($att['slug'], $result_checksums);
    48                 $check_plugins[$check] = $att;
    49             }
    50 
    51         }
    52         var_dump($check_plugins);
    53         if( !empty($check_plugins)){
    54             $api = new WPFingerprint_API;
    55             $api->fire($check_plugins);
    56         }
    57         return;
    58     }
    59 
    60     function validate_checksums($files){
    61         $string = '';
    62         foreach($files as $file)
    63         {
    64             $string .= $file;
    65         }
    66         return hash( 'md5', $string);
    67     }
    68 
     20
     21        if(empty($this->hash)){
     22            $this->load('hash');
     23            $this->hash = new WPFingerprint_Hash;
     24        }
     25        if(empty($this->model_checksums)){
     26            $this->load('model-checksums');
     27            $this->model_checksums = new WPFingerprint_Model_Checksums;
     28        }
     29        if(empty($this->diff)){
     30
     31            $this->load('diff');
     32            $this->diff = new WPFingerprint_Diff;
     33        }
     34        if(empty($this->model_diffs)){
     35            $this->load('model-diffs');
     36            $this->model_diffs = new WPFingerprint_Model_Diffs;
     37        }
     38    }
    6939    //poormans autoloader
    7040    function load($class)
    7141    {
    72 
    73         return require_once $this->path . 'inc/class-wpfingerprint-'.$class.'.php';
     42        return require_once $this->path . 'class-wpfingerprint-'.$class.'.php';
     43    }
     44
     45
     46    /*
     47     * Run command, Compares Checksums
     48     *
     49     */
     50
     51    function run( $plugin = false )
     52    {
     53        $time = time();
     54        /*
     55         * Step 1 - Get plugins to check
     56         */
     57        $plugins_list = array();
     58        $plugin_info = array();
     59        if(!isset($plugin) || !$plugin)
     60        {
     61
     62            //Get all plugins
     63            $plugins_list = $this->plugins->get_all_plugin_names();
     64        }
     65        else{
     66            $plugins_list = $this->plugins->get_some_plugins_names($plugin);
     67        }
     68        //We have nothing to do.
     69        if(empty($plugins_list)) return false;
     70        /*
     71         * Step 2 - Get local checksums.
     72         */
     73        $local_checksums = array();
     74        foreach($plugins_list as $plugin_name)
     75        {
     76            $local_checksums[$plugin_name] = $this->generate_plugin_checksum( $plugin_name );
     77        }
     78
     79        //We have nothing to do.
     80        if(empty($local_checksums)) return false;
     81
     82         /*
     83          * Step 3, 4 - Get Remote Checksums
     84            */
     85
     86            $remote_checksums = array();
     87            foreach($plugins_list as $plugin_name)
     88            {
     89                $remote_get = array();
     90                $remote_get = $this->generate_remote_plugin_checksum( $plugin_name );
     91                if(!empty($remote_get)){
     92                    $remote_checksums[$plugin_name] = $remote_get['checksums'];
     93                    $plugin_info[$plugin_name] = array(
     94                    'source' => $remote_get['source'],
     95                    'version' => $remote_get['version'],
     96                    );
     97                }
     98            }
     99
     100            //We have nothing to do.
     101            if(empty($remote_checksums)) return false;
     102
     103            /*
     104             * Step 5,6,7 - Do comparison
     105             */
     106             $added_files = array();
     107             $not_valid_checksums = array();
     108             $file_diffs = array();
     109
     110             foreach($plugins_list as $plugin_name)
     111            {
     112                $compare = array();
     113                if(!empty($remote_checksums[$plugin_name]) && is_array($remote_checksums[$plugin_name]))
     114                {
     115                    $compare = $this->compare_checksums(    $local_checksums[$plugin_name], $remote_checksums[$plugin_name]);
     116                }
     117                if(!empty($compare['added']))
     118                {
     119                    $added_files[$plugin_name] = $compare['added'];
     120                }
     121                if(!empty($compare['invalid']))
     122                {
     123                /*
     124                 * Step 8 - Check if Support Diff
     125                 */
     126                if($this->diff_available($plugin_info[$plugin_name]['source']))
     127                    {
     128                        $version =  $plugin_info[$plugin_name]['version'];
     129                        $files = array_keys($compare['invalid']);
     130                        $file_diffs[$plugin_name] = $this->generate_plugin_diff($plugin_name, $version, $files, $plugin_info[$plugin_name]['source']);
     131                        if(is_array($file_diffs) && !empty($file_diffs))
     132                        {
     133                            foreach($file_diffs as $file_diff_file => $file_diff_contents)
     134                            {
     135                                if(empty($file_diff_file) || !is_array($file_diff_file))
     136                                {
     137                                    //Remove from file Diffs and $not_valid_checksums
     138                                    unset($compare['invalid'][$file_diff_file]);
     139                                    unset($file_diffs[$file_diff_file]);
     140                                }
     141                            }
     142                        }
     143                    }
     144                    $not_valid_checksums[$plugin_name] = $compare['invalid'];
     145                }
     146            }
     147            /*
     148             * Step 10 & 11 Get Previous Fails, Store new ones
     149             */
     150
     151             $previous_fails = $this->get_previous_fails();
     152             $update_ids = array();
     153             $remove_ids = array();
     154             if(!empty($previous_fails))
     155             {
     156                 foreach($previous_fails as $plugin => $versions)
     157                 {
     158                     if(array_key_exists($plugin, $not_valid_checksums))
     159                     {
     160                         foreach($versions as $version => $content)
     161                         {
     162                             if($plugin_info[$plugin]['version'] == $version )
     163                             {
     164                                 foreach($content as $file => $file_checksums)
     165                                 {
     166                                     if(array_key_exists($file, $not_valid_checksums[$plugin]))
     167                                     {
     168                                         //ok compare the Local Checksums
     169                                         if($file_checksums['local_checksum'] == $not_valid_checksums[$plugin][$file][0])
     170                                         {
     171                                             //Update the time sheet
     172                                             $update_ids[] = $file_checksums['id'];
     173                                             //unset
     174                                             unset($not_valid_checksums[$plugin][$file]);
     175                                         }
     176                                     }
     177                                     else{
     178                                         //Remove ID
     179                                         $remove_ids[] = $file_checksums['id'];
     180                                     }
     181                                 }
     182                             }
     183                             else{
     184                                 foreach($content as $file)
     185                                 {
     186                                     //Remove no longer seen checksums by Version
     187                                     $remove_ids[] = $file['id'];
     188                                 }
     189                             }
     190                         }
     191
     192                     }
     193                 }
     194
     195             }
     196             //Remove IDs
     197             if(!empty($remove_ids) && is_array($remove_ids))
     198             {
     199                 $this->remove_checksums($remove_ids);
     200             }
     201             //Update IDs
     202             if(!empty($update_ids) && is_array($update_ids))
     203             {
     204                 $this->update_checksums($update_ids);
     205             }
     206              //Add New Checksums
     207                if(!empty($not_valid_checksums) && is_array($not_valid_checksums))
     208                {
     209                    foreach($not_valid_checksums as $plugin => $files)
     210                    {
     211                        if(is_array($files) && !empty($files))
     212                        {
     213                            $this->add_checksums($plugin, $plugin_info[$plugin]['version'],$files, $plugin_info[$plugin]['source']);
     214                        }
     215                        else{
     216                            //Clean it up
     217                            unset($not_valid_checksums[$plugin]);
     218                        }
     219                    }
     220                }
     221
     222             /*
     223              * Tidy up
     224                */
     225                //Finished
     226                update_option( 'wpfingerprint_last_run', time() );
     227                $return = array(
     228                    'time_taken' => time() - $time,
     229                    'new_issues' => count($not_valid_checksums,1),
     230                    'removed_isues' => count($remove_ids,0),
     231                    'updated_issues' => count($update_ids,0)
     232                );
     233                do_action('wp_fingerprint_runner',array($not_valid_checksums,$return));
     234                return $return;
     235    }
     236
     237    function compare_checksums($local_checksums, $remote_checksums)
     238    {
     239        $added_file = array();
     240        $invalid_checksum = array();
     241        foreach($local_checksums as $file => $checksum)
     242        {
     243            /*
     244             * Step 5/6 - filter files
     245             */
     246            if(!$this->plugins->filter_files($file)){
     247                /*
     248                 * Step 7 - Look for added and invalid checksums
     249                 */
     250                if(!isset($remote_checksums[$file]))
     251                {
     252                    $added_file[$file] = $checksum;
     253                }
     254                elseif($remote_checksums[$file] != $checksum)
     255                {
     256                    if(!is_array($remote_checksums[$file]) || !in_array($checksum, $remote_checksums[$file]))
     257                    {
     258                        $invalid_checksum[$file] = array($checksum,$remote_checksums[$file]);
     259                    }
     260                }
     261            }
     262        }
     263        return array(
     264            'added' => $added_file,
     265            'invalid' => $invalid_checksum
     266        );
     267    }
     268
     269    function diff_available( $transport )
     270    {
     271        $transport_object = 'transport-'.$transport;
     272        return $this->$transport_object->get_option('diff');
     273    }
     274
     275    function generate_plugin_checksum( $plugin = false )
     276    {
     277        if(!isset($plugin) || $plugin == false) return;
     278
     279        $path = $this->plugins->path($plugin);
     280        $files = $this->plugins->get_files($path);
     281        $checksums = array();
     282
     283        foreach ( $files as $file )
     284        {
     285            $checksums[ltrim($file,'/')] = $this->hash->get_sha256( $path.$file );
     286        }
     287        return $checksums;
     288    }
     289
     290    function generate_remote_plugin_checksum( $plugin = false )
     291    {
     292        if(!isset($plugin) || $plugin == false) return;
     293        $transports = $this->select_transport( $plugin );
     294        $version = $this->plugins->get_plugin_version( $plugin );
     295        foreach($transports as $transport)
     296        {
     297            $transport_object = 'transport-'.$transport;
     298            $remote_checksums = $this->$transport_object->get_plugin_checksums( $plugin, $version );
     299            if(!empty($remote_checksums) )
     300            {
     301                 return array(
     302                     'source' => $transport,
     303                     'version' => $version,
     304                     'checksums' => $remote_checksums,
     305                 );
     306            }
     307        }
     308        return false;
     309
     310    }
     311
     312    function generate_plugin_diff( $plugin, $version, $files, $transport)
     313    {
     314        if(!isset($plugin) || $plugin == false) return false;
     315        if(!isset($version) || $version == false) return false;
     316        $transport_object = 'transport-'.$transport;
     317        $diffs = array();
     318        foreach ( $files as $file )
     319        {
     320            $local_file = $this->plugins->read_file_contents($plugin,$file);
     321            $remote_file = $this->$transport_object->get_plugin_file($plugin,$version,$file);
     322
     323            $diff_file = $this->diff->check_file_diff($local_file,$remote_file);
     324            $diffs[$file] = $this->diff->show_diffs($diff_file);
     325        }
     326        return $diffs;
     327    }
     328
     329    function get_previous_fails()
     330    {
     331        $get_fails = $this->model_checksums->get();
     332        if( empty($get_fails) ) return false;
     333
     334        $failed_plugins = array();
     335        foreach($get_fails as $fail)
     336        {
     337            $checksum = array(
     338                'id' => $fail->id,
     339                'local_checksum' => $fail->checksum_local,
     340                'remote_checksum' => $fail->checksum_remote
     341            );
     342            $failed_plugins[$fail->plugin][$fail->version][$fail->filename] = $checksum;
     343        }
     344        return $failed_plugins;
     345    }
     346
     347    function remove_checksums($ids)
     348    {
     349        foreach($ids as $id)
     350        {
     351            $this->model_checksums->remove($id);
     352        }
     353    }
     354
     355    function update_checksums($ids)
     356    {
     357        foreach($ids as $id)
     358        {
     359            $this->model_checksums->update_last_checked($id);
     360        }
     361    }
     362    function add_checksums($plugin, $version, $files, $source)
     363    {
     364        foreach($files as $filename => $data)
     365        {
     366            $checksums = array(
     367                'local' => $data[0],
     368                'remote' => $data[1],
     369                'source' => $source
     370            );
     371            $this->model_checksums->set($plugin, $version,$filename,$checksums);
     372        }
     373    }
     374
     375    private function select_transport( $plugin = false )
     376    {
     377        $transports = array(
     378            'wporg',
     379            'local',
     380            'wpfingerprint'
     381        );
     382        //Add and load our transports
     383        foreach( $transports as $transport )
     384        {
     385            $transport_object = 'transport-'.$transport;
     386            $this->load(    $transport_object );
     387            $transport_name = 'WPFingerprint_Transport_'.ucwords( $transport );
     388            $this->$transport_object = new $transport_name;
     389        }
     390        //Add your own transport such as a remote repository, need to load your own handler
     391        return apply_filters( 'wp_fingerprint_transports', $transports, $plugin );
     392    }
     393
     394    function report( $plugin = false)
     395    {
     396        $report = array();
     397        $files = $this->model_checksums->get($plugin);
     398        foreach($files as $file)
     399        {
     400            $report[] = array(
     401                'plugin' => $file->plugin,
     402                'file' => $file->filename,
     403                'checksum_local' => $file->checksum_local,
     404                'checksum_remote' => $file->checksum_remote,
     405                'last_checked' => $file->last_checked
     406            );
     407        }
     408        return $report;
    74409    }
    75410}
  • wp-fingerprint/tags/2.1/wp-fingerprint.php

    r1853983 r1970866  
    33Plugin Name: WP Fingerprint
    44Description: WP Fingerprint adds an additional layer of security to your WordPress website, working to check your plugins for signs of hack or exploit
    5 Version: 1.0.1
     5Version: 2.1.0
    66Author: 34SP.com
    77Author URI: https://www.34SP.com
     
    1010
    1111    private $path;
    12     private $runner;
     12    public $runner;
     13    private $db_version = 3;
    1314
    1415    public function __construct( )
     
    2324            $this->runner = new WPFingerprint_Runner($this->path);
    2425         }
     26         /*
     27          * Runs our migrations for updates
     28            */
     29         if($this->db_version > get_option('wpfingerprint_db_version',0))
     30         {
     31             $this->runner->model_checksums->migrate($this->db_version);
     32             $this->runner->model_diffs->migrate($this->db_version);
     33             update_option('wpfingerprint_db_version',$this->db_version);
     34         }
    2535    }
    2636
     
    2838    {
    2939        //load additional additional actions
    30         add_action( 'rest_api_init', array($this,'webhook'));
    3140        add_action( 'admin_init', array($this,'admin_init'));
    3241        add_action( 'wpfingerprint_cron', array($this,'cron'));
     42        add_action( 'wpfingerprint_run_now', array($this,'cron'));
    3343    }
    3444
    3545    function admin_init( )
    3646    {
     47        //Admin settings loading
     48        $this->runner->load('settings');
     49        //No point adding the filters to a screen they wont see.
     50        if( current_user_can('manage_options') ){
     51            //Only folks who can do something with plugins are alerted
     52            add_action( 'admin_footer', array('WPFingerprint_Settings', 'admin_bar_footer_js') );
     53            add_action( 'wp_ajax_wp-fingerprint-recheck', array('WPFingerprint_Settings', 'recheck_callback') );
     54            add_action( 'admin_bar_menu', array('WPFingerprint_Settings', 'admin_bar_menu'),110 );
     55        }
    3756        /* Settings and hooks specific to Plugin Page */
    3857        global $pagenow;
    39         //No point adding the filters to a screen they wont see.
    4058        if( current_user_can('activate_plugins') && $pagenow == 'plugins.php'){
    4159            if ( ! function_exists( 'get_plugins' ) ) {
     
    4765            foreach($all_plugins as $key => $value){
    4866                $hook = 'after_plugin_row_'.$key;
    49                 add_action( $hook , array($this,'notices'), 10,3);
     67                add_action( $hook , array('WPFingerprint_Settings','notices'), 10,3);
    5068            }
    51             //add_action( 'admin_notices', array($this,'after_activation_notice') );
    5269        }
    5370    }
     
    5572    function runner( )
    5673    {
    57         $this->runner->run();
     74        return $this->runner->run();
    5875    }
     76
    5977    //Trigger the cron
    6078    function cron( )
     
    6381            $this->runner();
    6482        }
    65         return;
    6683    }
    6784
    68     function webhook( )
    69     {
    70         $api = new WPFingerprint_API;
    71         register_rest_route( 'wpfingerprint/v1', '/webhook',
    72             array(
    73                 'method' => 'POST',
    74                 'callback' => array($api, 'webhook'),
    75             )
    76         );
    77         register_rest_route( 'wpfingerprint/v1', '/webhook',
    78             array(
    79                 'method' => 'GET',
    80                 'callback' => array($api, 'webhook')
    81             )
    82         );
    83     }
    84 
    85     function notices ($plugin_file, $plugin_data, $status )
    86     {
    87         $plugins = $this->runner->plugins;
    88         $invalid_plugins = get_option( 'wpfingerprint_invalid' );
    89         if(!is_array($invalid_plugins))
    90         {
    91             $invalid_plugins = array('results' => array());
    92         }
    93         $slug = $plugins->get_plugin_name( $plugin_data['Name'] );
    94         if( !in_array( $slug, array_keys( $invalid_plugins['results'] ) ) ) return;
    95             $return;
    96             $return .= '<tr id="wpfingerprint-warning">';
    97             $return .=  '<td colspan="3" class="plugin-update colspanchange">';
    98             if($this->notice_type())
    99             {
    100                 $return .=  '<div class="warning inline warning-error warning-alt">';
    101                 $return .=  '<p><strong>WARNING</strong> - WP Fingerprint has detected that the following files may have been tampered with:</p>';
    102             }
    103             else{
    104                 $return .=  '<div class="notice inline notice-error notice-alt">';
    105                 $return .=  '<p>WP Fingerprint has detected that the following files may have been tampered with:</p>';
    106             }
    107 
    108             foreach( $invalid_plugins['results'][$slug] as $path => $result)
    109             {
    110                 $return .=  '<p>';
    111                 $return .=  esc_html($path);
    112                 if($result['score'] != '100' && $result['type'] == 'modified')
    113                 {
    114                     $return .= sprintf( esc_html__( "does not match %s percent of installs seen.", "wpfingerprint"), $result['score'] );
    115                 }
    116                 if($result['type'] == 'malicious')
    117                 {
    118                     $return .= esc_html__(" is known to be malicious", 'wpfingerprint');
    119                 }
    120                 if($result['type'] == 'added')
    121                 {
    122                     $return .= esc_html__('has been added since download', 'wpfingerprint');
    123                 }
    124                 $return .= '</p>';
    125             }
    126                 $return .= sprintf( esc_html__( "Last Check: %s", 'wpfingerint'), date('Y-m-d H:i:s', $invalid_plugins['timestamp']) );
    127                 $return .=  '</div></td></tr>';
    128 
    129                 $return = apply_filters('wpfingerprint_output', $return, $path, $result);
    130                 echo $return;
    131     }
    132     function notice_type($results)
    133     {
    134         $return = false;
    135         if(array_search('malicious', array_column($results,'type'))) $return = true;
    136         return $return;
    137     }
    138 
    139     function after_activation_notice( )
    140     {
    141         if( get_transient( 'wpfingerprint-first-run' ) ){
    142             $return = ' <div class="updated notice is-dismissible"><p>';
    143             $return .= esc_html("WP Fingerprint is currently validating your plugin files, this process may take up to an hour. Go enjoy the internet for a bit!", 'wpfingerprint');
    144       $return .= '</p></div>';
    145             echo $return;
    146     }
    147     }
    14885    public function activation_hook() {
    149     set_transient( 'wpfingerprint-first-run', true, 2 * HOUR_IN_SECONDS );
     86        add_option('wpfingerprint_last_run', time());
     87        add_option('wpfingerprint_mode', 'cron');
     88        add_option('wpfingerprint_fails', 0);
    15089        if (! wp_next_scheduled ( 'wpfingerprint_cron' )) {
    15190            wp_schedule_event(time(), 'hourly', 'wpfingerprint_cron');
    15291    }
     92
    15393    }
    15494    public function deactivation_hook() {
     
    15797        delete_option('wpfingerprint_invalid');
    15898        delete_option('wpfingerprint_checksum');
    159         delete_transient('wpfingerprint-first-run');
     99        delete_option('wpfingerprint_db_version');
     100        delete_option('wpfingerprint_last_run');
     101        delete_option('wpfingerprint_fails', 0);
    160102    }
    161103}
  • wp-fingerprint/trunk/README.txt

    r1853983 r1970866  
    33Tags: security, plugins, checksums
    44Requires at least: 4.9
    5 Tested up to: 4.9.4
     5Tested up to: 4.9.8
    66Requires PHP: 5.4
    77License: GPLv3
    8 Stable tag: 1.0.1
     8Stable tag: 2.1.0
    99== Description ==
    1010WP Fingerprint adds an additional layer of security to your WordPress website, working to check your plugins for signs of hack or exploit. WP Fingerprint works by collecting checksums of your plugins and comparing it with the checksums collected by WP Fingerprint. If the plugin detects any abnormalities it will let you know so you can take immediate action.
    11 This plugin transmits and stores checksums on WP Fingerprint servers (all hosted in EU and run by 34SP.com) to work for details see https://wpfingerprint.com/how-it-works/ for the data we collect and store.
     11This plugin transmits and stores checksums on WP Fingerprint servers(all hosted in EU and run by 34SP.com) & WordPress.org to work for details see https://wpfingerprint.com/how-it-works/ for the data we collect and store.
    1212== Installation ==
    1313As normal click activate to activate the plugin and go make a cup of tea while it works away in the background. Allow an hour after installation for WPFingerprint to complete its first set of checks.
     
    2626
    2727== Changelog ==
     282.1 - Remove notice in admin section, refactored the primary checker, added ability to diff files if source allows, added WP-CLI commands for report and Diff - 16th October 2018
     292.0.4 - Show the source with a human friendly name, clear down logs so they are not showing spurious data
     302.0.3 - Added a "what does this mean notification", created clear logs wp-cli command  - 26th September 2018
     312.0.2 - Fixed issue where incorrectly announcing that checksums did not match - 24th September 2018
     322.0.1 - Fixed bug where Notification counter wasn't showing
     332.0.0 - Rewritten WP Fingerprint see blog post for full details - 19th September 2018
    28341.0.1 - 6th April 2018
    2935Bug fix - Removed notice, when plugin is not in the results array
  • wp-fingerprint/trunk/fingerprint-command.php

    r1829106 r1970866  
    22class Fingerprint_Command extends WP_CLI_Command {
    33
    4     function run( $args, $assoc_args )
     4    /*
     5     * Check Plugin against Checksums
     6     * @alias run
     7     */
     8    function check( $args, $assoc_args )
    59    {
    6         $path = plugin_dir_path( __FILE__ );
    7         $runner = new WPFingerprint_Runner($path);
    8         if(array_key_exists('force',$assoc_args))
     10        $wpfingerprint = new WPFingerprint_Plugin();
     11        if(isset( $wpfingerprint ))
    912        {
     13            $plugin = false;
     14            if(isset($args[0])){
     15                $plugin = $args[0];
     16            }
     17            $diffs = $wpfingerprint->runner($plugin);
     18            $message = 'WP Fingerprint has found: ';
     19            $message .= strval($diffs['new_issues']).' New Issues ';
     20            $message .= 'has updated '.strval($diffs['updated_issues']).' issues';
     21            $message .= ' and removed '.strval($diffs['removed_isues']).' issues';
     22            WP_CLI::success( $message );
     23        }else{
     24            WP_CLI::error( 'Error Running WP Fingerprint' );
     25        }
    1026
    11             $runner->plugins->remove_plugin_checksums();
     27    }
     28    /*
     29     * Create Checksum for a given plugin
     30     *
     31     */
     32    function generate( $args, $assoc_args )
     33    {
     34        $wpfingerprint = new WPFingerprint_Plugin();
     35        if(empty($args)) $args = array();
     36        $generate = $wpfingerprint->generate($args[0]);
     37        if(!empty($generate) || !is_array($generate)){
     38            WP_CLI::error( 'Generate Plugin failed' );
    1239        }
    13         $runner->run();
     40        else{
     41            return json_encode($generate);
     42        }
     43    }
     44    /*
     45     * Show any plugins that have currently failed.
     46     *
     47     */
     48    function report( $args, $assoc_args )
     49    {
     50        $format = 'table';
     51        if(isset($assoc_args['format']))
     52        {
     53            $format_options = array(
     54                'table','csv','yaml','json'
     55            );
     56            if(in_array( $assoc_args['format'], $format_options ))
     57            {
     58                $format = $assoc_args['format'];
     59            }
     60        }
     61        $plugin = false;
     62        if(!empty($args))
     63        {
     64            $plugin = $args[0];
     65        }
     66        $wpfingerprint = new WPFingerprint_Plugin();
     67        $report = $wpfingerprint->runner->report($plugin);
     68        WP_CLI\Utils\format_items( $format, $report, 'plugin,file,checksum_local, checksum_remote, last_checked' );
    1469    }
    1570
     71    function diff( $args, $assoc_args )
     72    {
     73        if(!empty($args))
     74        {
     75            $plugin = $args[0];
     76            $file = $args[1];
     77        }
     78        if(!isset($plugin))
     79        {
     80            WP_CLI::error( 'Need to specify a plugin' );
     81            die;
     82        }
     83        if(!isset($file))
     84        {
     85            WP_CLI::error( 'Need to specify a file' );
     86            die;
     87        }
     88        $wpfingerprint = new WPFingerprint_Plugin();
     89        $version = $wpfingerprint->runner->plugins->get_plugin_version($plugin);
     90        $wpfingerprint->runner->load('transport-wporg');
     91        $transport_name = 'WPFingerprint_Transport_Wporg';
     92        $transport_name = new $transport_name;
     93        $local_file = $wpfingerprint->runner->plugins->read_file_contents($plugin,$file);
     94        if(!$local_file)
     95        {
     96            WP_CLI::error( 'Local file could not be found' );
     97            die;
     98        }
     99        $remote_file = $transport_name->get_plugin_file($plugin,$version,$file);
     100        if(!$remote_file)
     101        {
     102            WP_CLI::error( 'Remote file could not be found' );
     103            die;
     104        }
     105        $diff_file = $wpfingerprint->runner->diff->check_file_diff($local_file,$remote_file);
     106        $format = 'table';
     107        if(isset($assoc_args['format']))
     108        {
     109            $format_options = array(
     110                'table','csv','yaml','json'
     111            );
     112            if(in_array( $assoc_args['format'], $format_options ))
     113            {
     114                $format = $assoc_args['format'];
     115            }
     116        }
     117        $report = $wpfingerprint->runner->diff->show_diffs($diff_file);
     118        WP_CLI\Utils\format_items( $format, $report, 'line,local,remote' );
     119    }
     120    /*
     121     * Delete the logs
     122     *
     123     */
     124    function clear( $args, $assoc_args )
     125    {
     126        $wpfingerprint = new WPFingerprint_Plugin();
     127        $wpfingerprint->runner->model_checksums->clear();
     128        WP_CLI::success( 'Wiped Logs' );
     129    }
    16130}
    17131WP_CLI::add_command( 'fingerprint', 'Fingerprint_Command' );
  • wp-fingerprint/trunk/inc/class-wpfingerprint-plugins.php

    r1829106 r1970866  
    11<?php
    22class WPFingerprint_Plugins{
     3   
    34
    45    private $plugins;
     
    67    function __construct()
    78    {
    8         $this->plugins = array();
     9
    910    }
    1011
    1112    function get()
    1213    {
    13         if(empty($this->plugins)) {
     14
     15        if(empty($this->plugins) && !is_array($this->plugins)) {
    1416            if ( ! function_exists( 'get_plugins' ) ) {
    1517                require_once ABSPATH . 'wp-admin/includes/plugin.php';
     
    5456    }
    5557
     58    function get_all_plugin_names() {
     59        $names = array();
     60        foreach ( get_plugins() as $file => $details ) {
     61            $names[] = $this->get_plugin_name( $file );
     62        }
     63        return $names;
     64    }
     65
     66    function get_some_plugins_names($plugin)
     67    {
     68        $plugins = array();
     69        if(!is_array($plugin))
     70        {
     71            return $plugins[] = $plugin;
     72        }
     73        else{
     74            //Explicit assumption this is a flat array
     75            return $plugin;
     76        }
     77    }
     78
     79    function get_plugin_files( $path ) {
     80        $folder = dirname( $this->get_absolute_path( $path ) );
     81        if ( WP_PLUGIN_DIR === $folder ) {
     82            return (array) $path;
     83        }
     84        return $this->get_files( trailingslashit( $folder ) );
     85    }
     86
     87    function get_files( $path ) {
     88        $filtered_files = array();
     89        try {
     90            $files = new RecursiveIteratorIterator(
     91                new RecursiveDirectoryIterator( $path,
     92                    RecursiveDirectoryIterator::SKIP_DOTS ),
     93                RecursiveIteratorIterator::CHILD_FIRST
     94            );
     95            foreach ( $files as $file_info ) {
     96                $pathname = self::normalize_directory_separators( substr( $file_info->getPathname(), strlen( $path ) ) );
     97                if ( $file_info->isFile()) {
     98                    $filtered_files[] = $pathname;
     99                }
     100            }
     101        } catch ( Exception $e ) {
     102
     103        }
     104        return $filtered_files;
     105    }
     106
     107    public static function normalize_directory_separators( $path ) {
     108        return str_replace( '\\', '/', $path );
     109    }
     110
     111    function get_plugin_version($slug)
     112    {
     113        $plugins = $this->get();
     114        foreach($plugins as $plugin)
     115        {
     116            if($plugin['TextDomain'] == $slug)
     117            {
     118                return $plugin['Version'];
     119            }
     120        }
     121    }
     122
    56123    function get_plugin_checksum($slug)
    57124    {
     
    68135        return update_option('wpfingerprint_checksum', $checksums);
    69136    }
     137
    70138    function remove_plugin_checksums()
    71139    {
     
    73141    }
    74142
     143    function read_file_contents($plugin, $file)
     144    {
     145
     146        return file_get_contents($this->path($plugin).'/'.$file);
     147    }
     148
     149    function filter_files($slug)
     150    {
     151        $no_check = array(
     152            'readme.txt',
     153            'readme.md',
     154            'README.md',
     155            'README.txt'
     156        );
     157        if( in_array($slug, $no_check)) return true;
     158        return false;
     159    }
     160
    75161}
  • wp-fingerprint/trunk/inc/class-wpfingerprint-runner.php

    r1829106 r1970866  
    11<?php
    22class WPFingerprint_Runner{
     3    public $plugins;
     4    public $hash;
     5    public $model_checksums;
     6    public $diff;
     7    public $model_diffs;
     8    public $transport;
    39    private $path;
    4     public $plugins;
    5     function __construct($path)
    6     {
    7         $this->path = $path;
    8         $this->load('plugins');
     10
     11    function __construct()
     12    {
     13        if(isset($this->path)){
     14            $this->path = plugin_dir_path( __FILE__ );
     15        }
    916        if(empty($this->plugins)){
     17            $this->load('plugins');
    1018            $this->plugins = new WPFingerprint_Plugins;
    1119        }
    12         $this->load('api');
    13     }
    14     function run()
    15     {
    16 
    17         $check_plugins = $this->plugins->all();
    18         $return_plugins = array();
    19         var_dump($check_plugins);
    20         foreach ( $check_plugins as $check => $att )
    21         {
    22             if(is_dir($att['path'])){
    23                 $files = new RecursiveIteratorIterator(
    24                 new RecursiveDirectoryIterator( $att['path'],
    25                     RecursiveDirectoryIterator::SKIP_DOTS ),
    26                 RecursiveIteratorIterator::CHILD_FIRST
    27             );
    28                 $dir_files =array();
    29 
    30                 foreach($files as $file_info){
    31                     $pathname = substr( $file_info->getPathname(),  strlen($att['path']) );
    32                 $dir_files[$pathname] = hash_file( 'md5', $file_info->getPathname() );
    33                 }
    34                 $att['files'] = $dir_files;
    35                 unset($dir_files);
    36             }
    37             else{
    38                 $att['files'][$att['path']] = hash_file( 'md5', $att['path']);
    39             }
    40             $result_checksums = $this->validate_checksums($att['files']);
    41             if($att['checksum'] == $result_checksums)
    42             {
    43                 //Don't send the data as its the same as last time
    44                 unset( $check_plugins[$check]);
    45             }
    46             else{
    47                 $this->plugins->update_plugin_checksums($att['slug'], $result_checksums);
    48                 $check_plugins[$check] = $att;
    49             }
    50 
    51         }
    52         var_dump($check_plugins);
    53         if( !empty($check_plugins)){
    54             $api = new WPFingerprint_API;
    55             $api->fire($check_plugins);
    56         }
    57         return;
    58     }
    59 
    60     function validate_checksums($files){
    61         $string = '';
    62         foreach($files as $file)
    63         {
    64             $string .= $file;
    65         }
    66         return hash( 'md5', $string);
    67     }
    68 
     20
     21        if(empty($this->hash)){
     22            $this->load('hash');
     23            $this->hash = new WPFingerprint_Hash;
     24        }
     25        if(empty($this->model_checksums)){
     26            $this->load('model-checksums');
     27            $this->model_checksums = new WPFingerprint_Model_Checksums;
     28        }
     29        if(empty($this->diff)){
     30
     31            $this->load('diff');
     32            $this->diff = new WPFingerprint_Diff;
     33        }
     34        if(empty($this->model_diffs)){
     35            $this->load('model-diffs');
     36            $this->model_diffs = new WPFingerprint_Model_Diffs;
     37        }
     38    }
    6939    //poormans autoloader
    7040    function load($class)
    7141    {
    72 
    73         return require_once $this->path . 'inc/class-wpfingerprint-'.$class.'.php';
     42        return require_once $this->path . 'class-wpfingerprint-'.$class.'.php';
     43    }
     44
     45
     46    /*
     47     * Run command, Compares Checksums
     48     *
     49     */
     50
     51    function run( $plugin = false )
     52    {
     53        $time = time();
     54        /*
     55         * Step 1 - Get plugins to check
     56         */
     57        $plugins_list = array();
     58        $plugin_info = array();
     59        if(!isset($plugin) || !$plugin)
     60        {
     61
     62            //Get all plugins
     63            $plugins_list = $this->plugins->get_all_plugin_names();
     64        }
     65        else{
     66            $plugins_list = $this->plugins->get_some_plugins_names($plugin);
     67        }
     68        //We have nothing to do.
     69        if(empty($plugins_list)) return false;
     70        /*
     71         * Step 2 - Get local checksums.
     72         */
     73        $local_checksums = array();
     74        foreach($plugins_list as $plugin_name)
     75        {
     76            $local_checksums[$plugin_name] = $this->generate_plugin_checksum( $plugin_name );
     77        }
     78
     79        //We have nothing to do.
     80        if(empty($local_checksums)) return false;
     81
     82         /*
     83          * Step 3, 4 - Get Remote Checksums
     84            */
     85
     86            $remote_checksums = array();
     87            foreach($plugins_list as $plugin_name)
     88            {
     89                $remote_get = array();
     90                $remote_get = $this->generate_remote_plugin_checksum( $plugin_name );
     91                if(!empty($remote_get)){
     92                    $remote_checksums[$plugin_name] = $remote_get['checksums'];
     93                    $plugin_info[$plugin_name] = array(
     94                    'source' => $remote_get['source'],
     95                    'version' => $remote_get['version'],
     96                    );
     97                }
     98            }
     99
     100            //We have nothing to do.
     101            if(empty($remote_checksums)) return false;
     102
     103            /*
     104             * Step 5,6,7 - Do comparison
     105             */
     106             $added_files = array();
     107             $not_valid_checksums = array();
     108             $file_diffs = array();
     109
     110             foreach($plugins_list as $plugin_name)
     111            {
     112                $compare = array();
     113                if(!empty($remote_checksums[$plugin_name]) && is_array($remote_checksums[$plugin_name]))
     114                {
     115                    $compare = $this->compare_checksums(    $local_checksums[$plugin_name], $remote_checksums[$plugin_name]);
     116                }
     117                if(!empty($compare['added']))
     118                {
     119                    $added_files[$plugin_name] = $compare['added'];
     120                }
     121                if(!empty($compare['invalid']))
     122                {
     123                /*
     124                 * Step 8 - Check if Support Diff
     125                 */
     126                if($this->diff_available($plugin_info[$plugin_name]['source']))
     127                    {
     128                        $version =  $plugin_info[$plugin_name]['version'];
     129                        $files = array_keys($compare['invalid']);
     130                        $file_diffs[$plugin_name] = $this->generate_plugin_diff($plugin_name, $version, $files, $plugin_info[$plugin_name]['source']);
     131                        if(is_array($file_diffs) && !empty($file_diffs))
     132                        {
     133                            foreach($file_diffs as $file_diff_file => $file_diff_contents)
     134                            {
     135                                if(empty($file_diff_file) || !is_array($file_diff_file))
     136                                {
     137                                    //Remove from file Diffs and $not_valid_checksums
     138                                    unset($compare['invalid'][$file_diff_file]);
     139                                    unset($file_diffs[$file_diff_file]);
     140                                }
     141                            }
     142                        }
     143                    }
     144                    $not_valid_checksums[$plugin_name] = $compare['invalid'];
     145                }
     146            }
     147            /*
     148             * Step 10 & 11 Get Previous Fails, Store new ones
     149             */
     150
     151             $previous_fails = $this->get_previous_fails();
     152             $update_ids = array();
     153             $remove_ids = array();
     154             if(!empty($previous_fails))
     155             {
     156                 foreach($previous_fails as $plugin => $versions)
     157                 {
     158                     if(array_key_exists($plugin, $not_valid_checksums))
     159                     {
     160                         foreach($versions as $version => $content)
     161                         {
     162                             if($plugin_info[$plugin]['version'] == $version )
     163                             {
     164                                 foreach($content as $file => $file_checksums)
     165                                 {
     166                                     if(array_key_exists($file, $not_valid_checksums[$plugin]))
     167                                     {
     168                                         //ok compare the Local Checksums
     169                                         if($file_checksums['local_checksum'] == $not_valid_checksums[$plugin][$file][0])
     170                                         {
     171                                             //Update the time sheet
     172                                             $update_ids[] = $file_checksums['id'];
     173                                             //unset
     174                                             unset($not_valid_checksums[$plugin][$file]);
     175                                         }
     176                                     }
     177                                     else{
     178                                         //Remove ID
     179                                         $remove_ids[] = $file_checksums['id'];
     180                                     }
     181                                 }
     182                             }
     183                             else{
     184                                 foreach($content as $file)
     185                                 {
     186                                     //Remove no longer seen checksums by Version
     187                                     $remove_ids[] = $file['id'];
     188                                 }
     189                             }
     190                         }
     191
     192                     }
     193                 }
     194
     195             }
     196             //Remove IDs
     197             if(!empty($remove_ids) && is_array($remove_ids))
     198             {
     199                 $this->remove_checksums($remove_ids);
     200             }
     201             //Update IDs
     202             if(!empty($update_ids) && is_array($update_ids))
     203             {
     204                 $this->update_checksums($update_ids);
     205             }
     206              //Add New Checksums
     207                if(!empty($not_valid_checksums) && is_array($not_valid_checksums))
     208                {
     209                    foreach($not_valid_checksums as $plugin => $files)
     210                    {
     211                        if(is_array($files) && !empty($files))
     212                        {
     213                            $this->add_checksums($plugin, $plugin_info[$plugin]['version'],$files, $plugin_info[$plugin]['source']);
     214                        }
     215                        else{
     216                            //Clean it up
     217                            unset($not_valid_checksums[$plugin]);
     218                        }
     219                    }
     220                }
     221
     222             /*
     223              * Tidy up
     224                */
     225                //Finished
     226                update_option( 'wpfingerprint_last_run', time() );
     227                $return = array(
     228                    'time_taken' => time() - $time,
     229                    'new_issues' => count($not_valid_checksums,1),
     230                    'removed_isues' => count($remove_ids,0),
     231                    'updated_issues' => count($update_ids,0)
     232                );
     233                do_action('wp_fingerprint_runner',array($not_valid_checksums,$return));
     234                return $return;
     235    }
     236
     237    function compare_checksums($local_checksums, $remote_checksums)
     238    {
     239        $added_file = array();
     240        $invalid_checksum = array();
     241        foreach($local_checksums as $file => $checksum)
     242        {
     243            /*
     244             * Step 5/6 - filter files
     245             */
     246            if(!$this->plugins->filter_files($file)){
     247                /*
     248                 * Step 7 - Look for added and invalid checksums
     249                 */
     250                if(!isset($remote_checksums[$file]))
     251                {
     252                    $added_file[$file] = $checksum;
     253                }
     254                elseif($remote_checksums[$file] != $checksum)
     255                {
     256                    if(!is_array($remote_checksums[$file]) || !in_array($checksum, $remote_checksums[$file]))
     257                    {
     258                        $invalid_checksum[$file] = array($checksum,$remote_checksums[$file]);
     259                    }
     260                }
     261            }
     262        }
     263        return array(
     264            'added' => $added_file,
     265            'invalid' => $invalid_checksum
     266        );
     267    }
     268
     269    function diff_available( $transport )
     270    {
     271        $transport_object = 'transport-'.$transport;
     272        return $this->$transport_object->get_option('diff');
     273    }
     274
     275    function generate_plugin_checksum( $plugin = false )
     276    {
     277        if(!isset($plugin) || $plugin == false) return;
     278
     279        $path = $this->plugins->path($plugin);
     280        $files = $this->plugins->get_files($path);
     281        $checksums = array();
     282
     283        foreach ( $files as $file )
     284        {
     285            $checksums[ltrim($file,'/')] = $this->hash->get_sha256( $path.$file );
     286        }
     287        return $checksums;
     288    }
     289
     290    function generate_remote_plugin_checksum( $plugin = false )
     291    {
     292        if(!isset($plugin) || $plugin == false) return;
     293        $transports = $this->select_transport( $plugin );
     294        $version = $this->plugins->get_plugin_version( $plugin );
     295        foreach($transports as $transport)
     296        {
     297            $transport_object = 'transport-'.$transport;
     298            $remote_checksums = $this->$transport_object->get_plugin_checksums( $plugin, $version );
     299            if(!empty($remote_checksums) )
     300            {
     301                 return array(
     302                     'source' => $transport,
     303                     'version' => $version,
     304                     'checksums' => $remote_checksums,
     305                 );
     306            }
     307        }
     308        return false;
     309
     310    }
     311
     312    function generate_plugin_diff( $plugin, $version, $files, $transport)
     313    {
     314        if(!isset($plugin) || $plugin == false) return false;
     315        if(!isset($version) || $version == false) return false;
     316        $transport_object = 'transport-'.$transport;
     317        $diffs = array();
     318        foreach ( $files as $file )
     319        {
     320            $local_file = $this->plugins->read_file_contents($plugin,$file);
     321            $remote_file = $this->$transport_object->get_plugin_file($plugin,$version,$file);
     322
     323            $diff_file = $this->diff->check_file_diff($local_file,$remote_file);
     324            $diffs[$file] = $this->diff->show_diffs($diff_file);
     325        }
     326        return $diffs;
     327    }
     328
     329    function get_previous_fails()
     330    {
     331        $get_fails = $this->model_checksums->get();
     332        if( empty($get_fails) ) return false;
     333
     334        $failed_plugins = array();
     335        foreach($get_fails as $fail)
     336        {
     337            $checksum = array(
     338                'id' => $fail->id,
     339                'local_checksum' => $fail->checksum_local,
     340                'remote_checksum' => $fail->checksum_remote
     341            );
     342            $failed_plugins[$fail->plugin][$fail->version][$fail->filename] = $checksum;
     343        }
     344        return $failed_plugins;
     345    }
     346
     347    function remove_checksums($ids)
     348    {
     349        foreach($ids as $id)
     350        {
     351            $this->model_checksums->remove($id);
     352        }
     353    }
     354
     355    function update_checksums($ids)
     356    {
     357        foreach($ids as $id)
     358        {
     359            $this->model_checksums->update_last_checked($id);
     360        }
     361    }
     362    function add_checksums($plugin, $version, $files, $source)
     363    {
     364        foreach($files as $filename => $data)
     365        {
     366            $checksums = array(
     367                'local' => $data[0],
     368                'remote' => $data[1],
     369                'source' => $source
     370            );
     371            $this->model_checksums->set($plugin, $version,$filename,$checksums);
     372        }
     373    }
     374
     375    private function select_transport( $plugin = false )
     376    {
     377        $transports = array(
     378            'wporg',
     379            'local',
     380            'wpfingerprint'
     381        );
     382        //Add and load our transports
     383        foreach( $transports as $transport )
     384        {
     385            $transport_object = 'transport-'.$transport;
     386            $this->load(    $transport_object );
     387            $transport_name = 'WPFingerprint_Transport_'.ucwords( $transport );
     388            $this->$transport_object = new $transport_name;
     389        }
     390        //Add your own transport such as a remote repository, need to load your own handler
     391        return apply_filters( 'wp_fingerprint_transports', $transports, $plugin );
     392    }
     393
     394    function report( $plugin = false)
     395    {
     396        $report = array();
     397        $files = $this->model_checksums->get($plugin);
     398        foreach($files as $file)
     399        {
     400            $report[] = array(
     401                'plugin' => $file->plugin,
     402                'file' => $file->filename,
     403                'checksum_local' => $file->checksum_local,
     404                'checksum_remote' => $file->checksum_remote,
     405                'last_checked' => $file->last_checked
     406            );
     407        }
     408        return $report;
    74409    }
    75410}
  • wp-fingerprint/trunk/wp-fingerprint.php

    r1853983 r1970866  
    33Plugin Name: WP Fingerprint
    44Description: WP Fingerprint adds an additional layer of security to your WordPress website, working to check your plugins for signs of hack or exploit
    5 Version: 1.0.1
     5Version: 2.1.0
    66Author: 34SP.com
    77Author URI: https://www.34SP.com
     
    1010
    1111    private $path;
    12     private $runner;
     12    public $runner;
     13    private $db_version = 3;
    1314
    1415    public function __construct( )
     
    2324            $this->runner = new WPFingerprint_Runner($this->path);
    2425         }
     26         /*
     27          * Runs our migrations for updates
     28            */
     29         if($this->db_version > get_option('wpfingerprint_db_version',0))
     30         {
     31             $this->runner->model_checksums->migrate($this->db_version);
     32             $this->runner->model_diffs->migrate($this->db_version);
     33             update_option('wpfingerprint_db_version',$this->db_version);
     34         }
    2535    }
    2636
     
    2838    {
    2939        //load additional additional actions
    30         add_action( 'rest_api_init', array($this,'webhook'));
    3140        add_action( 'admin_init', array($this,'admin_init'));
    3241        add_action( 'wpfingerprint_cron', array($this,'cron'));
     42        add_action( 'wpfingerprint_run_now', array($this,'cron'));
    3343    }
    3444
    3545    function admin_init( )
    3646    {
     47        //Admin settings loading
     48        $this->runner->load('settings');
     49        //No point adding the filters to a screen they wont see.
     50        if( current_user_can('manage_options') ){
     51            //Only folks who can do something with plugins are alerted
     52            add_action( 'admin_footer', array('WPFingerprint_Settings', 'admin_bar_footer_js') );
     53            add_action( 'wp_ajax_wp-fingerprint-recheck', array('WPFingerprint_Settings', 'recheck_callback') );
     54            add_action( 'admin_bar_menu', array('WPFingerprint_Settings', 'admin_bar_menu'),110 );
     55        }
    3756        /* Settings and hooks specific to Plugin Page */
    3857        global $pagenow;
    39         //No point adding the filters to a screen they wont see.
    4058        if( current_user_can('activate_plugins') && $pagenow == 'plugins.php'){
    4159            if ( ! function_exists( 'get_plugins' ) ) {
     
    4765            foreach($all_plugins as $key => $value){
    4866                $hook = 'after_plugin_row_'.$key;
    49                 add_action( $hook , array($this,'notices'), 10,3);
     67                add_action( $hook , array('WPFingerprint_Settings','notices'), 10,3);
    5068            }
    51             //add_action( 'admin_notices', array($this,'after_activation_notice') );
    5269        }
    5370    }
     
    5572    function runner( )
    5673    {
    57         $this->runner->run();
     74        return $this->runner->run();
    5875    }
     76
    5977    //Trigger the cron
    6078    function cron( )
     
    6381            $this->runner();
    6482        }
    65         return;
    6683    }
    6784
    68     function webhook( )
    69     {
    70         $api = new WPFingerprint_API;
    71         register_rest_route( 'wpfingerprint/v1', '/webhook',
    72             array(
    73                 'method' => 'POST',
    74                 'callback' => array($api, 'webhook'),
    75             )
    76         );
    77         register_rest_route( 'wpfingerprint/v1', '/webhook',
    78             array(
    79                 'method' => 'GET',
    80                 'callback' => array($api, 'webhook')
    81             )
    82         );
    83     }
    84 
    85     function notices ($plugin_file, $plugin_data, $status )
    86     {
    87         $plugins = $this->runner->plugins;
    88         $invalid_plugins = get_option( 'wpfingerprint_invalid' );
    89         if(!is_array($invalid_plugins))
    90         {
    91             $invalid_plugins = array('results' => array());
    92         }
    93         $slug = $plugins->get_plugin_name( $plugin_data['Name'] );
    94         if( !in_array( $slug, array_keys( $invalid_plugins['results'] ) ) ) return;
    95             $return;
    96             $return .= '<tr id="wpfingerprint-warning">';
    97             $return .=  '<td colspan="3" class="plugin-update colspanchange">';
    98             if($this->notice_type())
    99             {
    100                 $return .=  '<div class="warning inline warning-error warning-alt">';
    101                 $return .=  '<p><strong>WARNING</strong> - WP Fingerprint has detected that the following files may have been tampered with:</p>';
    102             }
    103             else{
    104                 $return .=  '<div class="notice inline notice-error notice-alt">';
    105                 $return .=  '<p>WP Fingerprint has detected that the following files may have been tampered with:</p>';
    106             }
    107 
    108             foreach( $invalid_plugins['results'][$slug] as $path => $result)
    109             {
    110                 $return .=  '<p>';
    111                 $return .=  esc_html($path);
    112                 if($result['score'] != '100' && $result['type'] == 'modified')
    113                 {
    114                     $return .= sprintf( esc_html__( "does not match %s percent of installs seen.", "wpfingerprint"), $result['score'] );
    115                 }
    116                 if($result['type'] == 'malicious')
    117                 {
    118                     $return .= esc_html__(" is known to be malicious", 'wpfingerprint');
    119                 }
    120                 if($result['type'] == 'added')
    121                 {
    122                     $return .= esc_html__('has been added since download', 'wpfingerprint');
    123                 }
    124                 $return .= '</p>';
    125             }
    126                 $return .= sprintf( esc_html__( "Last Check: %s", 'wpfingerint'), date('Y-m-d H:i:s', $invalid_plugins['timestamp']) );
    127                 $return .=  '</div></td></tr>';
    128 
    129                 $return = apply_filters('wpfingerprint_output', $return, $path, $result);
    130                 echo $return;
    131     }
    132     function notice_type($results)
    133     {
    134         $return = false;
    135         if(array_search('malicious', array_column($results,'type'))) $return = true;
    136         return $return;
    137     }
    138 
    139     function after_activation_notice( )
    140     {
    141         if( get_transient( 'wpfingerprint-first-run' ) ){
    142             $return = ' <div class="updated notice is-dismissible"><p>';
    143             $return .= esc_html("WP Fingerprint is currently validating your plugin files, this process may take up to an hour. Go enjoy the internet for a bit!", 'wpfingerprint');
    144       $return .= '</p></div>';
    145             echo $return;
    146     }
    147     }
    14885    public function activation_hook() {
    149     set_transient( 'wpfingerprint-first-run', true, 2 * HOUR_IN_SECONDS );
     86        add_option('wpfingerprint_last_run', time());
     87        add_option('wpfingerprint_mode', 'cron');
     88        add_option('wpfingerprint_fails', 0);
    15089        if (! wp_next_scheduled ( 'wpfingerprint_cron' )) {
    15190            wp_schedule_event(time(), 'hourly', 'wpfingerprint_cron');
    15291    }
     92
    15393    }
    15494    public function deactivation_hook() {
     
    15797        delete_option('wpfingerprint_invalid');
    15898        delete_option('wpfingerprint_checksum');
    159         delete_transient('wpfingerprint-first-run');
     99        delete_option('wpfingerprint_db_version');
     100        delete_option('wpfingerprint_last_run');
     101        delete_option('wpfingerprint_fails', 0);
    160102    }
    161103}
Note: See TracChangeset for help on using the changeset viewer.