Back to Repositories

Testing WordPress Component Enumeration Controller in WPScan

This test suite validates the WPScan enumeration controller functionality, focusing on command-line argument parsing and WordPress component enumeration capabilities. The tests ensure proper handling of plugin, theme, user, and configuration detection modes.

Test Coverage Overview

The test suite provides comprehensive coverage of the WPScan enumeration controller’s core functionality.

  • Command-line argument parsing and validation
  • Plugin and theme enumeration with different detection modes
  • User enumeration and password testing scenarios
  • Configuration backup and database export detection

Implementation Analysis

The tests employ RSpec’s behavior-driven development approach, using context blocks to organize related test cases.

The implementation leverages RSpec’s powerful mocking capabilities with before/after hooks and shared examples. Test cases systematically verify different enumeration modes (passive, aggressive, mixed) and CLI argument combinations.

Technical Details

  • Testing Framework: RSpec
  • Mocking Tools: RSpec mocks and stubs
  • Test Organization: Nested describe/context blocks
  • Configuration: Frozen string literals, shared setup in before blocks

Best Practices Demonstrated

The test suite exemplifies several testing best practices for Ruby applications.

  • Isolated test cases with proper setup and teardown
  • Comprehensive edge case coverage
  • Clear test organization and naming
  • Effective use of RSpec’s DSL features

wpscanteam/wpscan

spec/app/controllers/enumeration_spec.rb

            
# frozen_string_literal: true

describe WPScan::Controller::Enumeration do
  subject(:controller) { described_class.new }
  let(:target_url)     { 'http://wp.lab/' }
  let(:cli_args)       { "--url #{target_url}" }

  before do
    ## For the --passwords options
    allow_any_instance_of(OptParseValidator::OptPath).to receive(:check_file)

    WPScan::ParsedCli.options = rspec_parsed_options(cli_args)
  end

  describe '#enum_message' do
    after { expect(controller.enum_message(type, detection_mode)).to eql @expected }

    context 'when type argument is incorrect' do
      let(:type)           { 'spec' }
      let(:detection_mode) { :mixed }

      it 'returns nil' do
        @expected = nil
      end
    end

    %w[plugins themes].each do |t|
      context "type = #{t}" do
        let(:type)           { t }
        let(:detection_mode) { :mixed }

        context 'when vulnerable' do
          let(:cli_args) { "#{super()} -e v#{type[0]}" }

          it 'returns the expected string' do
            @expected = "Enumerating Vulnerable #{type.capitalize} (via Passive and Aggressive Methods)"
          end
        end

        context 'when all' do
          let(:cli_args)       { "#{super()} -e a#{type[0]}" }
          let(:detection_mode) { :passive }

          it 'returns the expected string' do
            @expected = "Enumerating All #{type.capitalize} (via Passive Methods)"
          end
        end

        context 'when most popular' do
          let(:cli_args)       { "#{super()} -e #{type[0]}" }
          let(:detection_mode) { :aggressive }

          it 'returns the expected string' do
            @expected = "Enumerating Most Popular #{type.capitalize} (via Aggressive Methods)"
          end
        end
      end
    end
  end

  describe '#default_opts' do
    context 'when no --enumerate' do
      it 'contains the correct version_detection' do
        expect(controller.default_opts('plugins')[:version_detection]).to include(mode: :mixed)
      end
    end
  end

  describe '#cli_options' do
    it 'contains the correct options' do
      expect(controller.cli_options.map(&:to_sym)).to eql(
        %i[enumerate exclude_content_based
           plugins_list plugins_detection plugins_version_all plugins_version_detection plugins_threshold
           themes_list themes_detection themes_version_all themes_version_detection themes_threshold
           timthumbs_list timthumbs_detection
           config_backups_list config_backups_detection
           db_exports_list db_exports_detection
           medias_detection
           users_list users_detection exclude_usernames]
      )
    end
  end

  describe '#enum_users' do
    before { expect(controller.formatter).to receive(:output).twice }
    after { controller.enum_users }

    context 'when --enumerate has been supplied' do
      let(:cli_args) { "#{super()} -e u1-10" }

      it 'calls the target.users with the correct range' do
        expect(controller.target).to receive(:users).with(hash_including(range: (1..10)))
      end
    end

    context 'when --passwords supplied but no --username or --usernames' do
      let(:cli_args) { "#{super()} --passwords some-file.txt" }

      it 'calls the target.users with the default range' do
        expect(controller.target).to receive(:users).with(hash_including(range: (1..10)))
      end
    end
  end

  describe '#run' do
    context 'when no :enumerate' do
      before do
        expect(controller).to receive(:enum_plugins)
        expect(controller).to receive(:enum_config_backups)

        expect(WPScan::ParsedCli.plugins_detection).to eql :passive
      end

      it 'calls enum_plugins and enum_config_backups' do
        controller.run
      end

      context 'when --passwords supplied but no --username or --usernames' do
        let(:cli_args) { "#{super()} --passwords some-file.txt" }

        it 'calls the enum_users' do
          expect(controller).to receive(:enum_users)
          controller.run
        end
      end
    end

    context 'when :enumerate' do
      after { controller.run }

      context 'when no option supplied' do
        let(:cli_args) { "#{super()} -e" }

        it 'calls the correct enum methods' do
          %i[plugins themes timthumbs config_backups db_exports users medias].each do |option|
            expect(controller).to receive("enum_#{option}".to_sym)
          end
        end
      end

      %i[p ap vp].each do |option|
        context "when #{option}" do
          let(:cli_args) { "#{super()} -e #{option}" }

          it 'calls the #enum_plugins' do
            expect(controller).to receive(:enum_plugins)
          end
        end
      end

      %i[t at vt].each do |option|
        context option.to_s do
          let(:cli_args) { "#{super()} -e #{option}" }

          it 'calls the #enum_themes' do
            expect(controller).to receive(:enum_themes)
          end
        end
      end

      { timthumbs: 'tt', config_backups: 'cb', db_exports: 'dbe', medias: 'm', users: 'u' }.each do |option, shortname|
        context "when #{option}" do
          let(:cli_args) { "#{super()} -e #{shortname}" }

          it "calls the ##{option}" do
            expect(controller).to receive("enum_#{option}".to_sym)
          end
        end
      end
    end
  end
end