Back to Repositories

Validating WordPress Security Scanning Functionality in WPScan

This test suite validates core functionality of the WPScan Target class, focusing on WordPress scanning capabilities and vulnerability detection. The tests cover XML-RPC detection, version checking, theme analysis, and security assessment features.

Test Coverage Overview

The test suite provides comprehensive coverage of the WPScan Target class functionality.

Key areas tested include:
  • XML-RPC endpoint detection and validation
  • WordPress version identification
  • Theme and plugin enumeration
  • Configuration backup detection
  • Database export discovery
  • User enumeration and password checking

Implementation Analysis

The testing approach utilizes RSpec’s behavior-driven development patterns with extensive use of shared examples and context-specific testing.

Technical implementation features:
  • Dependency injection through let blocks
  • Shared behavior testing with it_behaves_like
  • Mock object implementation with allow/receive patterns
  • Instance variable manipulation for state testing

Technical Details

Testing infrastructure includes:
  • RSpec as the primary testing framework
  • Frozen string literals for Ruby optimization
  • Mock objects for external dependencies
  • Dynamic method testing using metaprogramming
  • Shared context definitions for reusable test scenarios

Best Practices Demonstrated

The test suite exemplifies high-quality testing practices with clear separation of concerns and thorough edge case coverage.

Notable practices include:
  • Consistent use of context blocks for different scenarios
  • Proper isolation of external dependencies
  • Comprehensive vulnerability testing patterns
  • DRY principle application through shared examples
  • Clear test organization and naming conventions

wpscanteam/wpscan

spec/lib/target_spec.rb

            
# frozen_string_literal: true

describe WPScan::Target do
  subject(:target) { described_class.new(url, opts) }
  let(:url)        { 'http://ex.lo' }
  let(:opts)       { {} }

  it_behaves_like WPScan::Target::Platform::WordPress

  describe 'xmlrpc' do
    before do
      allow(target).to receive(:sub_dir)

      expect(target).to receive(:interesting_findings).and_return(interesting_findings)
    end

    context 'when no interesting_findings' do
      let(:interesting_findings) { [] }

      its(:xmlrpc) { should be_nil }
    end

    context 'when interesting_findings' do
      let(:interesting_findings) { ['aa', CMSScanner::Model::RobotsTxt.new(target.url)] }

      context 'when no XMLRPC' do
        its(:xmlrpc) { should be_nil }
      end

      context 'when XMLRPC' do
        let(:xmlrpc) { WPScan::Model::XMLRPC.new(target.url('xmlrpc.php')) }
        let(:interesting_findings) { super() << xmlrpc }

        its(:xmlrpc) { should eq xmlrpc }
      end
    end
  end

  %i[wp_version main_theme plugins themes timthumbs config_backups db_exports medias users].each do |method|
    describe "##{method}" do
      let(:methods) { %i[wp_version main_theme] }

      before do
        return_value = methods.include?(method) ? false : []

        expect(WPScan::Finders.const_get("#{method.to_s.camelize}::Base"))
          .to receive(:find).with(target, opts).and_return(return_value)
      end

      after { target.send(method, opts) }

      let(:opts) { {} }

      context 'when no options' do
        it 'calls the finder with the correct arguments' do
          # handled by before hook
        end
      end

      context 'when options' do
        let(:opts) { { mode: :passive, somthing: 'k' } }

        it 'calls the finder with the corect arguments' do
          # handled by before hook
        end
      end

      context 'when called multiple times' do
        it 'calls the finder only once' do
          target.send(method, opts)
        end
      end
    end
  end

  describe '#vulnerable?' do
    context 'when all attributes are nil' do
      it { should_not be_vulnerable }
    end

    context 'when wp_version is not found' do
      before { target.instance_variable_set(:@wp_version, false) }

      it { should_not be_vulnerable }
    end

    context 'when wp_version found' do
      before do
        expect(wp_version)
          .to receive(:db_data)
          .and_return(vuln_api_data_for("wordpresses/#{wp_version.number.tr('.', '')}"))

        target.instance_variable_set(:@wp_version, wp_version)
      end

      context 'when not vulnerable' do
        let(:wp_version) { WPScan::Model::WpVersion.new('4.0') }

        it { should_not be_vulnerable }
      end

      context 'when vulnerable' do
        let(:wp_version) { WPScan::Model::WpVersion.new('3.8.1') }

        it { should be_vulnerable }
      end
    end

    context 'when config_backups' do
      before do
        target.instance_variable_set(:@config_backups, [WPScan::Model::ConfigBackup.new(target.url('/a-file-url'))])
      end

      it { should be_vulnerable }
    end

    context 'when db_exports' do
      before do
        target.instance_variable_set(:@db_exports, [WPScan::Model::DbExport.new(target.url('/wordpress.sql'))])
      end

      it { should be_vulnerable }
    end

    context 'when users' do
      before do
        target.instance_variable_set(:@users,
                                     [WPScan::Model::User.new('u1'),
                                      WPScan::Model::User.new('u2')])
      end

      context 'when no passwords' do
        it { should_not be_vulnerable }
      end

      context 'when at least one password has been found' do
        before { target.users[1].password = 'owned' }

        it { should be_vulnerable }
      end
    end
  end
end