Module: Pluginscan

Defined in:
lib/pluginscan.rb,
lib/pluginscan/error.rb,
lib/pluginscan/printer.rb,
lib/pluginscan/version.rb,
lib/pluginscan/file_finder.rb,
lib/pluginscan/error_printer.rb,
lib/pluginscan/reports/cloc_report.rb,
lib/pluginscan/reports/issues_report.rb,
lib/pluginscan/reports/sloccount_report.rb,
lib/pluginscan/reports/vulnerability_report.rb,
lib/pluginscan/reports/cloc_report/cloc_printer.rb,
lib/pluginscan/reports/issues_report/issue_checks.rb,
lib/pluginscan/reports/issues_report/issues_printer.rb,
lib/pluginscan/reports/issues_report/issues_scanner.rb,
lib/pluginscan/reports/issues_report/error_list_printer.rb,
lib/pluginscan/reports/issues_report/issue_checks/check.rb,
lib/pluginscan/reports/issues_report/issues_models/issues.rb,
lib/pluginscan/reports/sloccount_report/sloccount_printer.rb,
lib/pluginscan/reports/vulnerability_report/advisories_api.rb,
lib/pluginscan/reports/vulnerability_report/wp_vuln_db_api.rb,
lib/pluginscan/reports/issues_report/issues_printer_factory.rb,
lib/pluginscan/reports/issues_report/issue_checks/function_check.rb,
lib/pluginscan/reports/issues_report/issue_checks/variable_check.rb,
lib/pluginscan/reports/issues_report/issues_scanner/utf8_checker.rb,
lib/pluginscan/reports/issues_report/issue_checks/comment_checker.rb,
lib/pluginscan/reports/issues_report/issues_models/check_findings.rb,
lib/pluginscan/reports/vulnerability_report/vulnerability_scanner.rb,
lib/pluginscan/reports/issues_report/issues_printer/finding_printer.rb,
lib/pluginscan/reports/vulnerability_report/vulnerabilities_printer.rb,
lib/pluginscan/reports/issues_report/issues_printer/file_issues_printer.rb,
lib/pluginscan/reports/issues_report/issues_scanner/file_issues_scanner.rb,
lib/pluginscan/reports/issues_report/issues_scanner/file_issues_scanner.rb,
lib/pluginscan/reports/issues_report/issues_scanner/line_issues_scanner.rb,
lib/pluginscan/reports/issues_report/issue_checks/variable_safety_checker.rb,
lib/pluginscan/reports/issues_report/issues_printer/check_findings_printer.rb

Defined Under Namespace

Modules: CommentChecker, Reports, WPVulnDB Classes: AdvisoriesAPI, CLOCPrinter, Check, CheckFindings, CheckFindingsPrinter, CheckView, Error, ErrorLine, ErrorLineFactory, ErrorLinePrinter, ErrorListPrinter, ErrorPrinter, FileFinder, FileIssuesPrinter, FileIssuesScanner, FileScanner, FileView, Finding, FindingPrinter, FindingView, FunctionCheck, IOError, IgnoredErrorLine, IgnoredFindingView, Issues, IssuesPrinter, IssuesPrinterFactory, IssuesScanner, LineIssuesScanner, LinesIssuesScanner, PrintableFinding, Printer, SLOCCountPrinter, Scanner, UTF8Checker, UnknownIssuesFormat, VariableCheck, VariableSafetyChecker, VulnerabilitiesPrinter, VulnerabilityPrinter, VulnerabilityScanner

Constant Summary collapse

VERSION =
"0.9.0".freeze
THE_CHECKS =
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: [
      /<script/i,
    ],
    ignores:  [
      /<script[^>]*\ +src=/i # tags containing src don't include inline JS
    ]
  ),

  Check.new(
    name:     'Inline CSS',
    message:  'Is there inline css which would prevent us enforcing a content security policy?',
    patterns: [
      /<style/i,
    ],
  ),

  Check.new(
    name:     'Event Attributes',
    message:  'Are there HTML event attributes which can execute inline JavaScript and thus prevent us enforcing a content security policy?',
    patterns: [
      /<[^>]* (on[[:alpha:]]+)=/i, # All event attributes start with "on" e.g. "onclick"
    ],
  ),

  #
  # Direct Loading of files
  #
  Check.new(
    name:     'Direct loading',
    message:  'Is someone trying to make a file that can be included directly?',
    patterns: [
      /wp-load\.php/,
    ],
  ),

  #
  # Unreliable IP addresses
  #
  Check.new(
    name:     'Unreliable IP',
    message:  'Are they making unsafe assumptions about client IP addresses?',
    patterns: [
      /HTTP_X_FORWARDED_FOR/,
      /HTTP_CLIENT_IP/,
      /HTTP_X_CLUSTER_CLIENT_IP/,
      /HTTP_X_FORWARDED/,
      /HTTP_FORWARDED_FOR/,
      /HTTP_CF_CONNECTING_IP/,
      /HTTP_X_REAL_IP/,
      /HTTP_FORWARDED/,
    ]
  ),

  #
  # Version leaks
  #
  FunctionCheck.new(
    name:           'Version leaks',
    message:        'Are they leaking secrets to the enemy?',
    function_names: [
      'phpversion',
    ],
    patterns:       [
      /(?:get_)?bloginfo\(.*?version/,
    ],
  ),
].freeze