During a code review of XenServer, we found and exploited a vulnerability in the XAPI management service that allows an attacker to bypass authentication and remotely perform arbitrary XAPI calls with administrative privileges.
During a brief code review of XenServer, Computest found and exploited a vulnerability in the XAPI management service that allows an attacker to bypass authentication and remotely perform arbitrary XAPI calls with administrative privileges.
This vulnerability can be further exploited to execute arbitrary shell commands as the operating system “root” user on the Dom0 virtual machine. The Dom0 is the component that manages the hypervisor, and has full control over all the virtual machines as well as the network and storage resources attached to the system.
To exploit this vulnerability an attacker has to be on a network that can reach one of the IPs and ports the XAPI service is available on (port numbers are 80 and 443 by default). Alternatively they can perform the attack through the browser of a user who has access to this port, via either a DNS rebinding attack or possibly by using the primary vulnerability to mount a cross-site scripting attack by using it to read a logfile containing attacker-controlled HTML.
This was not a full audit and further issues may or may not be present.
About XenServer:
XenServer is the leading open source virtualization platform, powered by the Xen Project hypervisor and the XAPI toolstack. It is used in the world’s largest clouds and enterprises.
Technical support for XenServer is available from Citrix.
About XAPI:
The Xen Project Management API (XAPI) is:
- A Xen Project Toolstack that exposes the XAPI interface. When we refer to XAPI as a toolstack, we typically include all dependencies and components that are needed for XAPI to operate (e.g. xenopsd).
- An interface for remotely configuring and controlling virtualised guests running on a Xen-enabled host. XAPI is the core component of XenServer and XCP.
While XAPI is maintained by the Xen project, it is not a required component of all Xen-based systems. It is required in XenServer.
Virtual machines have become the platform of choice for nearly all new IT infrastructure because of the massive benefits in manageability and resource optimization. However, a virtual machine can only be as secure as the platform it runs on.
For this reason compromising a hypervisor is always a high priority target, both during penetration tests and for real attackers.
The XAPI toolstack provides an API interface that is used both for communication between nodes in the same pool and for managing the pool, for example using a desktop application such as XenCenter. It is also the backend used by command line tools such as ‘xe’ and can be used by management platforms such as OpenStack, CloudStack, and Xen Orchestra.
While Citrix recommends keeping management traffic separate from storage traffic and VM traffic, in practice the system is often not configured this way. By default, the XAPI service appears to listen on any IP assigned to the hypervisor (actually the Dom0, to be precise). If no external interface is selected as a management interface, the XAPI service may still be accessible through one or more host internal management networks which can be made available to VMs.
The XAPI service is available both over unencrypted HTTP on port 80 and over HTTPS on port 443 (with a self-signed certificate by default).
The service does not check the HTTP Host
header specified in requests, which
makes the service vulnerable to DNS rebinding attacks. Using a DNS rebinding
attack a remote attacker can reach a XAPI service on the internal network by
convincing a user on the internal network to visit a malicious website, without
needing to exploit any vulnerability in the web browser or client OS.
Either way, because of the importance of a hypervisor it still needs to be able to defend against attackers who have already gained access to internal networks.
In assessing the XAPI we started by identifying the parts of the code where authentication checks are performed. All code is available on GitHub.
The first thing to note is that API endpoints are registered using add_handler
in the file /ocaml/xapi/xapi_http.ml
.
let add_handler (name, handler) =
let action =
try List.assoc name Datamodel.http_actions
with Not_found ->
(* This should only affect developers: *)
error "HTTP handler %s not registered in ocaml/idl/datamodel.ml" name;
failwith (Printf.sprintf "Unregistered HTTP handler: %s" name) in
let check_rbac = Rbac.is_rbac_enabled_for_http_action name in
let h = match handler with
| Http_svr.BufIO callback ->
Http_svr.BufIO (fun req ic context ->
(try
if check_rbac
then (* rbac checks *)
(try
assert_credentials_ok name req ~fn:(fun () -> callback req ic context) (Buf_io.fd_of ic)
with e ->
debug "Leaving RBAC-handler in xapi_http after: %s" (ExnHelper.string_of_exn e);
raise e
)
else (* no rbac checks *)
callback req ic context
So in short: if Rbac.is_rbac_enabled_for_http_action
returns true
,
authentication is not needed. Otherwise assert_credentials is called, which
will throw an exception if the request is not authorized.
Looking into is_rbac_enabled_for_http_action
a bit more, the following
endpoints are exempted from authentication:
(* these public http actions will NOT be checked by RBAC *)
(* they are meant to be used in exceptional cases where RBAC is already *)
(* checked inside them, such as in the XMLRPC (API) calls *)
let public_http_actions_with_no_rbac_check =
[
"post_root"; (* XMLRPC (API) calls -> checks RBAC internally *)
"post_cli"; (* CLI commands -> calls XMLRPC *)
"post_json"; (* JSON -> calls XMLRPC *)
"get_root"; (* Make sure that downloads, personal web pages etc do not go through RBAC asking for a password or session_id *)
(* also, without this line, quicktest_http.ml fails on non_resource_cmd and bad_resource_cmd with a 401 instead of 404 *)
"get_blob"; (* Public blobs don't need authentication *)
"post_root_options"; (* Preflight-requests are not RBAC checked *)
"post_json_options";
"post_jsonrpc";
"post_jsonrpc_options";
"get_pool_update_download";
]
Authentication is performed in the function assert_credentials_ok
, in the
file /ocaml/xapi/xapi_http.ml
. Some things to note about this function:
-
Besides username and password or an existing
session_id
, access can be granted by passing thepool_token
parameter. This is a static token shared by all nodes in the pool, and is stored at/etc/xensource/ptoken
. This token grants full administrative privileges. -
Adminstrative access is granted for connections over a local UNIX socket.
This means that any vulnerability that can perform an arbitrary file read or access an internal socket will enable full administrative access.
Since there are not too many endpoints that bypass authentication, it makes sense to quickly skim over each one to see if there is anything interesting.
The mapping from HTTP verb (GET
, POST
, …) and URL to action name is located
in the files under /ocaml/idl/datamodel*.m
, and the mapping from action name
to handler function happens in various calls to the add_handler
function we
already saw. We use this to find that, for example, the action
get_pool_update_download
is associated with a GET
request to the /update/
URL,
and is dispatched to the pool_update_download_handler function:
let pool_update_download_handler (req: Request.t) s _ =
debug "pool_update.pool_update_download_handler URL %s" req.Request.uri;
(* remove any dodgy use of "." or ".." NB we don't prevent the use of symlinks *)
let filepath = String.sub_to_end req.Request.uri (String.length Constants.get_pool_update_download_uri)
|> Filename.concat Xapi_globs.host_update_dir
|> Stdext.Unixext.resolve_dot_and_dotdot
|> Uri.pct_decode in
debug "pool_update.pool_update_download_handler %s" filepath;
if not(String.startswith Xapi_globs.host_update_dir filepath) || not (Sys.file_exists filepath) then begin
debug "Rejecting request for file: %s (outside of or not existed in directory %s)" filepath Xapi_globs.host_update_dir;
Http_svr.response_forbidden ~req s
end else begin
Http_svr.response_file s filepath;
req.Request.close <- true
end
This immediately looks extremely suspicious, in particular these two lines:
|> Stdext.Unixext.resolve_dot_and_dotdot
|> Uri.pct_decode in
Here, the code is first resolving any ../
sequences, and after that it will
perform decoding of urlencoding sequences such as %25
. (In OCaml, the |>
operator behaves somewhat like the |
(pipe) operator in unix shell scripts.)
This means that if the decoding produces new ../
sequences, they will not be
resolved and the naive check below it to verify that the produced path is under
the update root directory is no longer sufficient.
In short, this leads to a classic path traversal vulnerability where %2e%2e%2f
sequences can be used to escape the parent directory and read arbitrary files,
including the file containing the pool token.
As described earlier, possession of the pool token enables full administrative access to the hypervisor.
One other thing to note is that the response does not set a HTTP Content-Type
header, which might make it possible for attackers to exploit a XAPI service on
an internal network from the internet if they can trick someone on the internal
network into visiting a malicious site (a scenario similar to the previously
described DNS rebinding attack).
In this attack the malicious site would first perform a request that contains HTML and JavaScript in the URL, causing these to be written to a log file. In a second request, that logfile would then be loaded as a HTML page. The JavaScript on that page would then be able to read and exfiltrate the pool token and/or perform further requests using the pool token, something JavaScript on a random website on the internet would not normally be able to do because of the single origin policy enforced by web browsers.
This attack is overall much less practical than the DNS rebinding attack, and we have not investigated it further. The only advantage this attack has is that it could still work even if the XAPI was only available over HTTPS (DNS rebinding is in general not possible over HTTPS because the hostname will be validated by the TLS connection setup even if the HTTP server itself does not).
While the pool token is sufficient to perform most actions on the hypervisor, an attacker is still restricted by the operations that the XAPI supports.
To determine the full impact we investigated whether it was possible to obtain full remote shell access as the operating system “root” user with this pool token. If so, it makes the impact story a good deal simpler: remote shell access as root means complete control, period.
As it turns out, it is possible to abuse the /remotecmd
endpoint for this.
The code for this endpoint is located in /ocaml/xapi/xapi_remotecmd.ml
:
let allowed_cmds = ["rsync","/usr/bin/rsync"]
(* Handle URIs of the form: vmuuid:port *)
let handler (req: Http.Request.t) s _ =
let q = req.Http.Request.query in
debug "remotecmd handler running";
Xapi_http.with_context "Remote command" req s
(fun __context ->
let session_id = Context.get_session_id __context in
if not (Db.Session.get_pool ~__context ~self:session_id) then
begin
failwith "Not a pool session"
end;
let cmd=List.assoc "cmd" q in
let cmd=List.assoc cmd allowed_cmds in
let args = List.map snd (List.filter (fun (x,y) -> x="arg") q) in
do_cmd s cmd args
)
This appears to restrict the command to be executed to only rsync
, but since
rsync supports the -e
option that lets you execute arbitrary shell commands
anyway this restriction is not actually effective. Its unclear whether this
constitutes a separate vulnerability, since there are plenty of other ways to
abuse rsync
to gain remote shell access (overwriting various config files or
shellscripts, for example.)
This endpoint and the associated ability to execute shell commands is only available to ‘pool sessions’ (i.e. administrative sessions started by other nodes in the same pool), but because we have stolen the pool token we can produce such a session just by passing the stolen token in the right parameter.
Even though a complete exploit is deliberately not provided here, the core vulnerability is simple enough that an attacker will be able to exploit it with a minimum of effort.
Computest recommends verifying that none of the IPs assigned to the Dom0 are reachable from less-trusted networks (including the virtual networks assigned to the hosted virtual machines). While this is a best practice, it should not be considered a complete fix for this issue (especially considering the DNS rebinding concerns, which might provide an alternative route of attack).
Xen has noted that some versions of XenServer do not immediately create the
/var/update
directory on installation. Since the vulnerability can only be
exploited when this directory exists, those versions will not be vulnerable
directly after installation but will become vulnerable when installing their
first update.
It is possible to prevent exploitation of this issue by moving the /var/update
directory elsewhere and creating a file named /var/update
to prevent the
automatic creation of this directory. This will prevent the update
functionality from working and may have further negative impact. It is not
recommended by us, by Citrix, or by the Xen project, and we take no
responsibility for problems caused by doing this.
Xen has released a patch for the primary XAPI vulnerability under XSA-271 and has incorporated the fix in future XAPI versions.
Citrix will shortly publish or has published updates for supported XenServer releases 7.1 LTSR, 7.4 CR and 7.5 CR. Notably, no update will be published for version 7.3, which is out of support since June.
If you use either of these products you are advised to upgrade immediately.
Various cloud providers and other members of the Xen security pre-release list have received information about this vulnerability before the public release according to Xen’s usual policy (see also the timeline at the bottom of this document). If they were using XAPI they were able to apply the fix early.
If there is a risk that credentials stored on the dom0 or any of the VMs hosted by the hypervisor may have been compromised they should be changed.
There was previously no documented way of rotating the pool token, so Xen has provided the following steps to change it if deemed necessary.
-
On all pool members, stop Xapi:
-
On the pool master:
rm /etc/xensource/ptoken /opt/xensource/libexec/genptoken -f -o /etc/xensource/ptoken
-
Copy
/etc/xensource/ptoken
to all pool slaves -
On the pool master, restart the toolstack:
-
On all pool slaves, restart the toolstack:
Note that rotating credentials (including the pool token) is not sufficient to lock out an attacker who has already established an alternative means of control. The above steps are only intended as a possible extra layer of assurance when there is already reasonable confidence that no attack happened, or possibly as part of making a “known good” backup from before an attack safe for use.
Xen and Citrix have responded quickly to patch the issue. However, older versions of XenServer remain without a patch, and upgrading XenServer may not be easy for some users because some features are no longer supported in the free version distributed by Citrix.
In response to the miscellaneous concerns raised in this document Xen has documented a new procedure to change the pool token if desired, but we have had no clear indication of whether other things such as the DNS rebinding aspect will be addressed in the future.
The unexpected discovery of this vulnerability during a basic software quality review shows once again that it’s more than worth it to spend some extra time during network design to lock down and segregate management services. Especially since the consequences of bugs in such basic infrastructure can be disastrous and patching is often complicated.
In our opinion the XAPI service does not take a very principled approach in its HTTP and authentication layers, which provided room for this bug and some of the other things we mentioned when investigating the impact of this vulnerability.
2018-07-04 Disclosure of our draft to Xen and Citrix security teams
2018-07-05 First response from the Xen security team, XSA-271 assigned
2018-07-05 First response from the Citrix security team
2018-07-17 Xen proposes embargo date of 2017-08-14
2018-07-20 Agreed to set embargo date at 2018-08-14
2018-07-30 Received draft of Xen’s advisory
2018-07-31 Xen sends its advisory to its pre-release partners
2018-08-14 Public release of advisories