From 06a292651089394112d5dde547fa3ff76cb1c8f1 Mon Sep 17 00:00:00 2001 From: Postmodern Date: Mon, 9 Dec 2024 13:24:43 -0800 Subject: [PATCH] Allow exploit targets to define a range of software versions (closes #150). * Changed `Target#initialize(software_version: ...)` to accept both an exact version or a version range. Both are parsed using `Ronin::Support::Software::VersionRange.parse`. * Changed `Target#software_version` to return a `Ronin::Support::Software::VersionRange`. * Changed `Mixins::HasTargets#select_target` to parse the given `software_version:` String using `Ronin::Support::Software::Version` and compare it against the `#software_version` of each target. --- .rubocop.yml | 7 ++++ lib/ronin/exploits/mixins/has_targets.rb | 6 +++- lib/ronin/exploits/target.rb | 13 ++++--- spec/mixins/has_targets_spec.rb | 45 +++++++++++++++++++++--- spec/target_spec.rb | 28 ++++++++++++--- 5 files changed, 85 insertions(+), 14 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 947d23cc..93719fa0 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -59,3 +59,10 @@ Style/NegatedIfElseCondition: Exclude: - 'lib/ronin/exploits/sqli.rb' - 'lib/ronin/exploits/mixins/html.rb' + +# We need to use the === operator to compare Ronin::Support::Software::Version +# objects against Ronin::Support::Software::VersionRange objects. +Style/CaseEquality: + Exclude: + - 'lib/ronin/exploits/mixins/has_targets.rb' + - 'spec/mixins/has_targets_spec.rb' diff --git a/lib/ronin/exploits/mixins/has_targets.rb b/lib/ronin/exploits/mixins/has_targets.rb index d6894f2d..ece1ed68 100644 --- a/lib/ronin/exploits/mixins/has_targets.rb +++ b/lib/ronin/exploits/mixins/has_targets.rb @@ -22,6 +22,8 @@ require_relative '../target' require_relative '../exceptions' +require 'ronin/support/software/version' + module Ronin module Exploits module Mixins @@ -200,8 +202,10 @@ def select_target(arch: nil, os: nil, os_version: nil, software: nil, software_v end if software_version + version = Support::Software::Version.parse(software_version) + targets = targets.select do |target| - target.software_version == software_version + target.software_version && target.software_version === version end end diff --git a/lib/ronin/exploits/target.rb b/lib/ronin/exploits/target.rb index f63ca56a..576784da 100644 --- a/lib/ronin/exploits/target.rb +++ b/lib/ronin/exploits/target.rb @@ -19,6 +19,8 @@ # along with ronin-exploits. If not, see . # +require 'ronin/support/software/version_range' + require 'ostruct' module Ronin @@ -53,7 +55,7 @@ class Target < OpenStruct # The target's software version. # - # @return [String, nil] + # @return [Ronin::Support::Software::VersionRange, nil] # # @since 1.2.0 attr_reader :software_version @@ -76,7 +78,8 @@ class Target < OpenStruct # The software name of the target (ex: `"Apache"`). # # @param [String, nil] software_version - # The software version of the target (ex: `"2.4.54"`). + # The software version of the target + # (ex: `"2.4.54"` or `">= 1.2.3, < 2.0.0"`). # # @param [String, nil] version # Alias for `software_version:`. @@ -100,9 +103,11 @@ def initialize(arch: nil, @os = os @os_version = os_version + @software = software - @software = software - @software_version = software_version || version + if (string = (software_version || version)) + @software_version = Support::Software::VersionRange.parse(string) + end yield self if block_given? end diff --git a/spec/mixins/has_targets_spec.rb b/spec/mixins/has_targets_spec.rb index 68abc521..beada900 100644 --- a/spec/mixins/has_targets_spec.rb +++ b/spec/mixins/has_targets_spec.rb @@ -290,12 +290,47 @@ class ExampleExploit < Ronin::Exploits::Exploit end context "when given the software_version: keyword argument" do - let(:software_version) { '1.22.0' } + context "and when the targets in .targets define exact software versions" do + let(:software_version) { '1.22.0' } + + it "must find the target in .targets with the matching #software_version" do + subject.select_target(software_version: software_version) - it "must find the target in .targets with the matching #software_version" do - subject.select_target(software_version: software_version) + expect(subject.target.software_version.string).to eq(software_version) + end + end + + context "but the targets in .targets define software version ranges" do + module TestHasTargets + class ExampleExploitWithVersionRanges < Ronin::Exploits::Exploit + include Ronin::Exploits::Mixins::HasTargets - expect(subject.target.software_version).to eq(software_version) + target arch: :x86_64, + os: :linux, + os_version: '5.18.1', + software: 'Apache', + software_version: '>= 2.4.53', + foo: 1 + + target arch: :arm, + os: :macos, + os_version: '10.13', + software: 'nginx', + software_version: '>= 1.22.0, < 1.23.0', + foo: 2 + end + end + + let(:test_class) { TestHasTargets::ExampleExploitWithVersionRanges } + let(:software_version) { '1.22.1' } + + it "must find the target in .targets who's #software_version version range includes the given software version" do + subject.select_target(software_version: software_version) + + expect(subject.target.software_version).to include( + Ronin::Support::Software::Version.parse(software_version) + ) + end end end @@ -319,7 +354,7 @@ class ExampleExploit < Ronin::Exploits::Exploit expect(subject.target.os).to eq(os) expect(subject.target.os_version).to eq(os_version) expect(subject.target.software).to eq(software) - expect(subject.target.software_version).to eq(software_version) + expect(subject.target.software_version).to be === Ronin::Support::Software::Version.parse(software_version) end end diff --git a/spec/target_spec.rb b/spec/target_spec.rb index 15b75fb3..dbbfd091 100644 --- a/spec/target_spec.rb +++ b/spec/target_spec.rb @@ -72,8 +72,18 @@ subject { described_class.new(software_version: software_version) } - it "must set #software_version" do - expect(subject.software_version).to be(software_version) + it "must set #software_version to a new Ronin::Support::Software::VersionRange object" do + expect(subject.software_version).to be_kind_of(Ronin::Support::Software::VersionRange) + expect(subject.software_version.string).to eq(software_version) + end + + context "but the software_version: value is a version range String" do + let(:software_version) { '>= 1.2.3, < 2.0.0' } + + it "must accept and parse the version range String" do + expect(subject.software_version).to be_kind_of(Ronin::Support::Software::VersionRange) + expect(subject.software_version.string).to eq(software_version) + end end end @@ -82,8 +92,18 @@ subject { described_class.new(version: version) } - it "must set #software_version" do - expect(subject.software_version).to be(version) + it "must set #software_version to a new Ronin::Support::Software::VersionRange object" do + expect(subject.software_version).to be_kind_of(Ronin::Support::Software::VersionRange) + expect(subject.software_version.string).to eq(version) + end + + context "but the version: value is a version range String" do + let(:version) { '>= 1.2.3, < 2.0.0' } + + it "must accept and parse the version range String" do + expect(subject.software_version).to be_kind_of(Ronin::Support::Software::VersionRange) + expect(subject.software_version.string).to eq(version) + end end end