require "pluginscan/reports/issues_report/issue_checks/check" require "pluginscan/reports/issues_report/issue_checks/function_check" require "pluginscan/reports/issues_report/issue_checks/variable_check" require "pluginscan/reports/issues_report/issue_checks/variable_safety_checker" require "pluginscan/reports/issues_report/issue_checks/comment_checker" module Pluginscan THE_CHECKS = [ # # Look for superglobal arrays. Use of data from these arrays should be checked for unsanitised or unescaped use. # VariableCheck.new( name: 'Superglobal', message: 'Superglobal requires manual review', variables: %w( $_GET $_POST $_SERVER $_REQUEST $_COOKIE $_ENV $_FILES ), ), # # Check for SQLi # Check.new( name: 'Database access', message: 'Database access requires manual review', patterns: [ /\$wpdb/, ], ignores: [ /global.+\$wpdb/, lambda do |line| # Try to exclude some things that are definitely not injectable # safe_things = %w(prefix postmeta comments commentmeta links options posts terms term_relationships term_taxonomy usermeta users blogs blog_versions registration_log signups site sitecategories sitemeta).each do |safe| # line.gsub!(/\$wpdb->#{safe}/, '') # end # If it doesn't look like the whole call is on this line, all bets are off return false if line.count('(') != line.count(')') # If it looks like they might be concatenating a string, all bets are off return false if line.match(/["']{1}\s*\./) || line.match(/\.\s['"]{1}/) # If the first occurrence of $wpdb is a function call ($wpdb->thing()) # then we don't care about it or anything before it line = line.gsub(/^.*?\$wpdb-/, '') # If there's anything before the first wpdb, which can't result in it getting # assigned to something else, then we can delete all of that line = line.gsub(/^[^(=]*?\$wpdb/, '') # $wpdb->whatever is safe line = line.gsub(Regexp.new(Regexp.escape("$wpdb->")), '') # Are there any variables left? !line.match(/\$[a-z]{1}[a-z0-9_]*/) end, ] ), FunctionCheck.new( name: 'MySQL functions', message: 'People shouldn\'t use MySQL functions in WordPress.', patterns: [ /mysqli?_[a-z0-9_]+/, ] ), # # Arbitrary code execution # FunctionCheck.new( name: 'PHP code generation', message: 'Code generation functions might be bad', function_names: %w( eval create_function assert ), ), FunctionCheck.new( name: 'User-controllable function calls', message: 'What functions are they calling?', patterns: [ /\$\$[a-zA-Z0-9_]+\s*\(/, ], function_names: %w( call_user_func call_user_func_array ), ), # # Execution of system commands # FunctionCheck.new( name: 'System calls', message: 'Executing system commands might be bad', patterns: [ /`.+`/, ], function_names: %w( popen expect_popen proc_optioni exec shell_exec system passthru ), ), # # File manipulation: arbitrary code, altering system configs, info leakage # FunctionCheck.new( name: 'File operations', message: 'File operations require manual review', patterns: [ /xdiff_[a-z0-9_]+/, ], function_names: %w( bzwrite chmod chgrp chown copy file_put_contents fputscv fputs fprintf ftruncate fwrite gzwrite gzputs loadXML makedir move_uploaded_file rename unlink vfprintf yaml_emit_file fread fget fgets fgetss fgetc fpassthru glob file_get_contents fgetcsv file bzread gzread gzgets gzgetss gzgetc gzpassthru finfo_file highlight_file show_source readlink ), ), # # Attempts to obfuscate badware # FunctionCheck.new( name: 'Possible obfuscation', message: 'Are these obfuscating code or other badstuff?', function_names: %w( str_rot13 uudecode base64_decode base64_encode ), ), # # Network activity - remote shells, information leakage # FunctionCheck.new( name: 'Network functions', message: 'Who\'s sending what and where?', patterns: [ /ftp_[a-z0-9_]+/, /socket_[a-z0-9_]+/, ], function_names: %w( curl_exec curl_setopt fsockopen get_headers wp_get_http wp_get_http_headers wp_remote_get wp_remote_post wp_remote_request wp_remote_head ), ), # # Potentially unsafe wordpress functions # FunctionCheck.new( name: 'Potentially unsafe wordpress functions', message: 'Some wordpress functions are not inherently safe, this functions return value may not be safe.', function_names: %w( wp_unslash wp_get_referer wp_get_original_referer esc_sql ), ), # # PHP object injection # FunctionCheck.new( name: 'PHP object injection', message: "If an object can be created where this function does something dangerous, that's bad", function_names: %w( __wakeup __destruct __toString unserialize ), ), # # These functions parse strings into variables # FunctionCheck.new( name: 'Variable parsing', message: 'These functions parse strings into varables. Could be used as part of an Arbitrary Code Execution', function_names: %w( parse_str extract ), ), # # Using primitives where the WordPress API provides a better alternative # FunctionCheck.new( name: 'Redundant functions', message: 'People using these should probably be using the WordPress API instead', function_names: %w( htmlspecialchars mail ), # Should we ignore user-created mail() functions? # Or is the point of this test to check for those? -dgms ), # # What it says on the tin. WTF. lolwat. # FunctionCheck.new( name: 'Batshit weird', message: 'What on earth is it doing?', patterns: [ /runkit_[a-z0-9_]+/, /ldap_[a-z0-9_]+/, ], function_names: %w( ini_set put_env sleep apache_set_env session_decode ), ), # # Accidents # Check.new( name: 'Accidents', message: 'Did someone do a silly?', patterns: [ /\btodo\b/i, /\bfixme\b/i, /\bfuck\b/i, /\bshit\b/i, /\bcrap\b/i, /\bbroken\b/i, /\bhack\b/i, /\bxxx\b/i, /\bugly\b/i, /\bscary\b/i, /\bwtf\b/i, /\bsecurity\b/i, /\bbodge\b/i, /\bhardening\b/i, ] ), # # Security Policy # Check.new( name: 'Inline JavaScript', message: 'Is there inline javascript which would prevent us enforcing a content security policy?', patterns: [ /