CVE-2026-44839
Description
RabbitMQ is a messaging and streaming broker. From 3.7.0 to before 4.1.2 and 4.0.13, This vulnerability is fixed in 4.1.2 and 4.0.13.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Unsanitized virtual host names in RabbitMQ management UI allow stored XSS, fixed in 4.0.13 and 4.1.2.
Vulnerability
RabbitMQ versions from 3.7.0 up to but not including 4.0.13 and 4.1.2 are vulnerable to a stored cross-site scripting (XSS) flaw in the management UI. The vhost.ejs and vhosts.ejs templates, introduced in commit 7f54319, include a hidden input field containing the virtual host name without sanitization. An attacker who can create a virtual host and force its failure can inject arbitrary HTML/JavaScript into that field, which is then rendered in the management console when the page is reloaded. The vhost name length is unrestricted, allowing arbitrarily long payloads [2].
Exploitation
An attacker must have the ability to create (or control) a virtual host in RabbitMQ, and also be able to force that virtual host to crash or stop. The attacker sets a malicious vhost name containing JavaScript payload. No authentication is required beyond the existing permissions to create vhosts. When an administrator or other user views the management page listing virtual hosts, the browser executes the injected script in the context of the management UI session [2].
Impact
Successful exploitation leads to stored cross-site scripting within the management UI. The attacker can steal session cookies, perform actions on behalf of the victim administrator, deface the interface, or redirect to malicious sites. The impact is limited to users who visit the vhost page after the malicious vhost has been created and listed; the attacker does not directly achieve remote code execution on the RabbitMQ server but can compromise the administrative session's integrity and confidentiality [2].
Mitigation
RabbitMQ released fixed versions 4.0.13 and 4.1.2. Administrators should upgrade to these versions immediately. The fix sanitizes the vhost name input using the fmt_string helper in the vulnerable templates [1][2]. No workaround is available for unpatched instances; as a hardening measure, restrict vhost creation to trusted users only. The vulnerability is not listed in CISA's Known Exploited Vulnerabilities catalog.
AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2(expand)+ 1 more
- (no CPE)
- (no CPE)range: >=3.7.0, <4.0.13, <4.1.2
Patches
17f54319279d1API to restart a crashed vhost.
7 files changed · +113 −13
deps/rabbitmq_management/priv/www/css/main.css+1 −0 modified@@ -235,6 +235,7 @@ table.two-col-layout > tbody > tr > td { width: 50%; vertical-align: top; } input[type=submit], button { padding: 8px; border-radius: 5px; -moz-border-radius: 5px; color: black !important; text-decoration: none; cursor: pointer; font-weight: normal; } table.list input[type=submit], table.list button { padding: 4px; } +table.mini input[type=submit], table.mini button { padding: 4px; } input[type=submit], button { background: #ddf;
deps/rabbitmq_management/priv/www/js/dispatcher.js+3 −0 modified@@ -282,6 +282,9 @@ dispatcher_add(function(sammy) { } } }); + sammy.post('#/restart_vhost', function(){ + if(sync_post(this, '/vhosts/:vhost/start/:node')) update(); + }) sammy.del('#/limits', function() { if (sync_delete(this, '/vhost-limits/:vhost/:name')) update(); });
deps/rabbitmq_management/priv/www/js/tmpl/vhost.ejs+9 −1 modified@@ -27,7 +27,15 @@ <% for (var node in vhost.cluster_state) { %> <tr> <th><%= fmt_escape_html(node) %> :</th> - <td><%= vhost.cluster_state[node] %></td> + <td><%= vhost.cluster_state[node] %> + <% if (vhost.cluster_state[node] == "stopped"){ %> + <form action="#/restart_vhost" method="post"> + <input type="hidden" name="node" value="<%= node %>"/> + <input type="hidden" name="vhost" value="<%= vhost.name %>"/> + <input type="submit" value="Restart"/> + </form> + <% } %> + </td> </tr> <% } %> </table>
deps/rabbitmq_management/priv/www/js/tmpl/vhosts.ejs+10 −1 modified@@ -69,7 +69,16 @@ %> <tr> <td><%= node %></td> - <td><%= state %></td> + <td> + <%= state %> + <% if (state == "stopped"){ %> + <form action="#/restart_vhost" method="post" class="confirm"> + <input type="hidden" name="node" value="<%= node %>"/> + <input type="hidden" name="vhost" value="<%= vhost.name %>"/> + <input type="submit" value="Restart"/> + </form> + <% } %> + </td> </tr> <% }
deps/rabbitmq_management/src/rabbit_mgmt_dispatcher.erl+1 −0 modified@@ -117,6 +117,7 @@ dispatcher() -> {"/bindings/:vhost/e/:source/:dtype/:destination/:props", rabbit_mgmt_wm_binding, []}, {"/vhosts", rabbit_mgmt_wm_vhosts, []}, {"/vhosts/:vhost", rabbit_mgmt_wm_vhost, []}, + {"/vhosts/:vhost/start/:node", rabbit_mgmt_wm_vhost_restart, []}, {"/vhosts/:vhost/permissions", rabbit_mgmt_wm_permissions_vhost, []}, {"/vhosts/:vhost/topic-permissions", rabbit_mgmt_wm_topic_permissions_vhost, []}, %% /connections/:connection is already taken, we cannot use our standard scheme here
deps/rabbitmq_management/src/rabbit_mgmt_wm_vhost_restart.erl+67 −0 added@@ -0,0 +1,67 @@ +%% The contents of this file are subject to the Mozilla Public License +%% Version 1.1 (the "License"); you may not use this file except in +%% compliance with the License. You may obtain a copy of the License at +%% http://www.mozilla.org/MPL/ +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +%% License for the specific language governing rights and limitations +%% under the License. +%% +%% The Original Code is RabbitMQ Management Plugin. +%% +%% The Initial Developer of the Original Code is GoPivotal, Inc. +%% Copyright (c) 2011-2012 GoPivotal, Inc. All rights reserved. +%% + +-module(rabbit_mgmt_wm_vhost_restart). + +-export([init/3, rest_init/2, resource_exists/2, is_authorized/2, + allowed_methods/2, content_types_accepted/2, accept_content/2]). +-export([variances/2]). + +-include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl"). +-include_lib("amqp_client/include/amqp_client.hrl"). + +%%-------------------------------------------------------------------- + +init(_, _, _) -> {upgrade, protocol, cowboy_rest}. + +rest_init(Req, _Config) -> + {ok, rabbit_mgmt_cors:set_headers(Req, ?MODULE), #context{}}. + +variances(Req, Context) -> + {[<<"accept-encoding">>, <<"origin">>], Req, Context}. + +allowed_methods(ReqData, Context) -> + {[<<"POST">>, <<"OPTIONS">>], ReqData, Context}. + +resource_exists(ReqData, Context) -> + VHost = id(ReqData), + {rabbit_vhost:exists(VHost), ReqData, Context}. + +content_types_accepted(ReqData, Context) -> + {[{'*', accept_content}], ReqData, Context}. + +accept_content(ReqData, Context) -> + VHost = id(ReqData), + NodeB = rabbit_mgmt_util:id(node, ReqData), + Node = binary_to_atom(NodeB, utf8), + case rabbit_vhost_sup_sup:start_vhost(VHost, Node) of + {ok, _} -> + {true, ReqData, Context}; + {error, {already_started, _}} -> + {true, ReqData, Context}; + {error, Err} -> + Message = io_lib:format("Request to node ~s failed with ~p", + [Node, Err]), + rabbit_mgmt_util:bad_request(list_to_binary(Message), ReqData, Context) + end. + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized_admin(ReqData, Context). + +%%-------------------------------------------------------------------- + +id(ReqData) -> + rabbit_mgmt_util:id(vhost, ReqData).
deps/rabbitmq_management/test/rabbit_mgmt_http_SUITE.erl+22 −11 modified@@ -334,6 +334,19 @@ vhosts_test(Config) -> %% Check individually assert_item(#{name => <<"/">>}, http_get(Config, "/vhosts/%2f", ?OK)), assert_item(#{name => <<"myvhost">>},http_get(Config, "/vhosts/myvhost")), + + %% Crash it + rabbit_ct_broker_helpers:force_vhost_failure(Config, <<"myvhost">>), + [NodeData] = http_get(Config, "/nodes"), + Node = binary_to_atom(maps:get(name, NodeData), utf8), + assert_item(#{name => <<"myvhost">>, cluster_state => #{Node => <<"stopped">>}}, + http_get(Config, "/vhosts/myvhost")), + + %% Restart it + http_post(Config, "/vhosts/myvhost/start/" ++ atom_to_list(Node), [], {group, '2xx'}), + assert_item(#{name => <<"myvhost">>, cluster_state => #{Node => <<"running">>}}, + http_get(Config, "/vhosts/myvhost")), + %% Delete it http_delete(Config, "/vhosts/myvhost", {group, '2xx'}), %% It's not there @@ -344,17 +357,15 @@ vhosts_test(Config) -> vhosts_trace_test(Config) -> http_put(Config, "/vhosts/myvhost", none, {group, '2xx'}), - ?assertMatch(#{name := <<"myvhost">>, tracing := false, cluster_state := _}, - http_get(Config, "/vhosts/myvhost")), + Disabled = #{name => <<"myvhost">>, tracing => false}, + Enabled = #{name => <<"myvhost">>, tracing => true}, + assert_item(Disabled, http_get(Config, "/vhosts/myvhost")), http_put(Config, "/vhosts/myvhost", [{tracing, true}], {group, '2xx'}), - ?assertMatch(#{name := <<"myvhost">>, tracing := true, cluster_state := _}, - http_get(Config, "/vhosts/myvhost")), + assert_item(Enabled, http_get(Config, "/vhosts/myvhost")), http_put(Config, "/vhosts/myvhost", [{tracing, true}], {group, '2xx'}), - ?assertMatch(#{name := <<"myvhost">>, tracing := true}, - http_get(Config, "/vhosts/myvhost")), + assert_item(Enabled, http_get(Config, "/vhosts/myvhost")), http_put(Config, "/vhosts/myvhost", [{tracing, false}], {group, '2xx'}), - ?assertMatch(#{name := <<"myvhost">>, tracing := false}, - http_get(Config, "/vhosts/myvhost")), + assert_item(Disabled, http_get(Config, "/vhosts/myvhost")), http_delete(Config, "/vhosts/myvhost", {group, '2xx'}), passed. @@ -809,9 +820,9 @@ queues_test(Config) -> rabbit_ct_broker_helpers:force_vhost_failure(Config, <<"downvhost">>), %% The vhost is down - #{name := <<"downvhost">>, tracing := false, cluster_state := DownClusterState} = - http_get(Config, "/vhosts/downvhost"), - ?assertEqual([<<"stopped">>], lists:usort(maps:values(DownClusterState))), + Node = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), + DownVHost = #{name => <<"downvhost">>, tracing => false, cluster_state => #{Node => <<"stopped">>}}, + assert_item(DownVHost, http_get(Config, "/vhosts/downvhost")), DownQueues = http_get(Config, "/queues/downvhost"), DownQueue = http_get(Config, "/queues/downvhost/foo"),
Vulnerability mechanics
Root cause
"Missing HTTP API endpoint and UI to restart a crashed vhost, preventing administrators from recovering stopped vhosts programmatically."
Attack vector
An authenticated administrator can send a POST request to `/vhosts/:vhost/start/:node` to restart a crashed vhost on a specified cluster node. The endpoint accepts the vhost name and node name directly from the URL path, and calls `rabbit_vhost_sup_sup:start_vhost/2` to restart it. The attacker must have administrator credentials and know the target vhost and node names. The vulnerability is that this restart capability was missing prior to the patch, meaning administrators had no API-driven way to recover a stopped vhost without manual intervention.
Affected code
The vulnerability is introduced in the new HTTP API endpoint `/vhosts/:vhost/start/:node` defined in `deps/rabbitmq_management/src/rabbit_mgmt_wm_vhost_restart.erl` and registered in `deps/rabbitmq_management/src/rabbit_mgmt_dispatcher.erl`. The UI templates `vhosts.ejs` and `vhost.ejs` add "Restart" buttons for stopped vhosts, and `dispatcher.js` routes the POST action.
What the fix does
The patch adds a new HTTP API endpoint `/vhosts/:vhost/start/:node` and the corresponding Erlang module `rabbit_mgmt_wm_vhost_restart.erl` that handles POST requests. It also updates the management UI with "Restart" buttons on vhost status tables and adds the client-side routing in `dispatcher.js`. The fix closes the gap by providing administrators a proper API to restart crashed vhosts, which was previously unavailable. The test suite in `rabbit_mgmt_http_SUITE.erl` is updated to verify the restart flow: it forces a vhost failure, confirms the stopped state, posts to the restart endpoint, and asserts the vhost returns to running state.
Preconditions
- authAttacker must have administrator-level credentials for the RabbitMQ management API
- configThe target vhost must be in a 'stopped' (crashed) state on the target node
- inputAttacker must know the vhost name and the target node name
- networkThe management plugin must be enabled and the HTTP API reachable
Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
2News mentions
0No linked articles in our index yet.