CVE-2019-16988
Description
In FusionPBX up to v4.5.7, the file app\basic_operator_panel\resources\content.php uses an unsanitized "eavesdrop_dest" variable coming from the URL, which is reflected on 3 occasions in HTML, leading to XSS.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
FusionPBX up to v4.5.7 has a reflected XSS in the operator panel via unsanitized eavesdrop_dest parameter.
Vulnerability
In FusionPBX up to v4.5.7, the file app\basic_operator_panel\resources\content.php does not sanitize the eavesdrop_dest URL parameter before reflecting it in three places in HTML output [1][2]. This results in a reflected cross-site scripting (XSS) vulnerability. The vulnerable code path is only reachable when the user has the operator_panel_eavesdrop permission and is authenticated. The fix introduced the escape() function to sanitize the user-supplied input [1].
Exploitation
An attacker must trick an authenticated FusionPBX user into visiting a crafted URL containing malicious JavaScript in the eavesdrop_dest parameter [2]. No other special network position or additional privileges are required; the victim's session provides the necessary context. The attacker can deliver the link via email, chat, or other social engineering means.
Impact
Successful exploitation allows the attacker to execute arbitrary JavaScript in the context of the victim's FusionPBX session [2]. This can lead to theft of session cookies, defacement of the operator panel page, or other actions the authenticated user can perform, effectively compromising the confidentiality and integrity of the session.
Mitigation
The vulnerability was fixed in commit 7fec1014ff0d08e36be6a3f7664edb3a9df7b4ac on 2019-10-08, and the fix should be applied to any installation running FusionPBX up to v4.5.7 [1][2]. Upgrading to a version that includes this commit is the recommended mitigation. No workaround is described in the available references.
AI Insight generated on May 26, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2- FusionPBX/FusionPBXdescription
Patches
11 file changed · +36 −36
app/basic_operator_panel/resources/content.php+36 −36 modified@@ -114,38 +114,38 @@ if (permission_exists('operator_panel_eavesdrop')) { echo " <td valign='top' nowrap='nowrap'>"; if (sizeof($_SESSION['user']['extensions']) > 1) { - echo " <input type='hidden' id='eavesdrop_dest' value=\"".(($_REQUEST['eavesdrop_dest'] == '') ? $_SESSION['user']['extension'][0]['destination'] : $_REQUEST['eavesdrop_dest'])."\">"; + echo " <input type='hidden' id='eavesdrop_dest' value=\"".(($_REQUEST['eavesdrop_dest'] == '') ? $_SESSION['user']['extension'][0]['destination'] : escape($_REQUEST['eavesdrop_dest']))."\">"; echo " <img src='resources/images/eavesdrop.png' style='width: 12px; height: 12px; border: none; margin: 0px 5px; cursor: help;' title='".$text['description-eavesdrop_destination']."' align='absmiddle'>"; echo " <select class='formfld' style='margin-right: 5px;' align='absmiddle' onchange=\"document.getElementById('eavesdrop_dest').value = this.options[this.selectedIndex].value; refresh_start();\" onfocus='refresh_stop();'>\n"; if (is_array($_SESSION['user']['extensions'])) foreach ($_SESSION['user']['extensions'] as $user_extension) { - echo " <option value='".$user_extension."' ".(($_REQUEST['eavesdrop_dest'] == $user_extension) ? "selected" : null).">".$user_extension."</option>\n"; + echo " <option value='".escape($user_extension)."' ".(($_REQUEST['eavesdrop_dest'] == $user_extension) ? "selected" : null).">".escape($user_extension)."</option>\n"; } echo " </select>\n"; } else if (sizeof($_SESSION['user']['extensions']) == 1) { - echo " <input type='hidden' id='eavesdrop_dest' value=\"".$_SESSION['user']['extension'][0]['destination']."\">"; + echo " <input type='hidden' id='eavesdrop_dest' value=\"".escape($_SESSION['user']['extension'][0]['destination'])."\">"; } echo " </td>"; } if (sizeof($groups) > 0) { echo " <td valign='top' nowrap='nowrap'>"; - echo " <input type='hidden' id='group' value=\"".$_REQUEST['group']."\">"; + echo " <input type='hidden' id='group' value=\"".escape($_REQUEST['group'])."\">"; if (sizeof($groups) > 5) { //show select box echo " <select class='formfld' onchange=\"document.getElementById('group').value = this.options[this.selectedIndex].value; refresh_start();\" onfocus='refresh_stop();'>\n"; echo " <option value='' ".(($_REQUEST['group'] == '') ? "selected" : null).">".$text['label-call_group']."</option>"; echo " <option value=''>".$text['button-all']."</option>"; if (is_array($groups)) foreach ($groups as $group) { - echo " <option value='".$group."' ".(($_REQUEST['group'] == $group) ? "selected" : null).">".$group."</option>\n"; + echo " <option value='".escape($group)."' ".(($_REQUEST['group'] == $group) ? "selected" : null).">".escape($group)."</option>\n"; } echo " </select>\n"; } else { //show buttons echo " <input type='button' class='btn' title=\"".$text['label-call_group']."\" value=\"".$text['button-all']."\" onclick=\"document.getElementById('group').value = '';\" ".$onhover_pause_refresh.">"; if (is_array($groups)) foreach ($groups as $group) { - echo " <input type='button' class='btn' title=\"".$text['label-call_group']."\" value=\"".$group."\" ".(($_REQUEST['group'] == $group) ? "disabled='disabled'" : null)." onclick=\"document.getElementById('group').value = this.value;\" ".$onhover_pause_refresh.">"; + echo " <input type='button' class='btn' title=\"".$text['label-call_group']."\" value=\"".escape($group)."\" ".(($_REQUEST['group'] == $group) ? "disabled='disabled'" : null)." onclick=\"document.getElementById('group').value = this.value;\" ".$onhover_pause_refresh.">"; } } echo " </td>"; @@ -309,35 +309,35 @@ $status_hover = $text['label-status_logged_out_or_unknown']; } - $block .= "<div id='".$extension."' class='op_ext ".$style."' ".(($_GET['vd_ext_from'] == $extension || $_GET['vd_ext_to'] == $extension) ? "style='border-style: dotted;'" : null)." ".(($ext_state != 'active' && $ext_state != 'ringing') ? "ondrop='drop(event, this.id);' ondragover='allowDrop(event, this.id);' ondragleave='discardDrop(event, this.id);'" : null).">"; // DRAG TO + $block .= "<div id='".escape($extension)."' class='op_ext ".$style."' ".(($_GET['vd_ext_from'] == $extension || $_GET['vd_ext_to'] == $extension) ? "style='border-style: dotted;'" : null)." ".(($ext_state != 'active' && $ext_state != 'ringing') ? "ondrop='drop(event, this.id);' ondragover='allowDrop(event, this.id);' ondragleave='discardDrop(event, this.id);'" : null).">"; // DRAG TO $block .= "<table class='op_ext ".$style."'>"; $block .= " <tr>"; $block .= " <td class='op_ext_icon'>"; - $block .= " <span name='".$extension."'>"; // DRAG FROM - $block .= "<img id='".$call_identifier."' class='op_ext_icon' src='resources/images/status_".$status_icon.".png' title='".$status_hover."' ".(($draggable) ? "draggable='true' ondragstart=\"drag(event, this.parentNode.getAttribute('name'));\" onclick=\"virtual_drag('".$call_identifier."', '".$extension."');\"" : "onfocus='this.blur();' draggable='false' style='cursor: not-allowed;'").">"; + $block .= " <span name='".escape($extension)."'>"; // DRAG FROM + $block .= "<img id='".escape($call_identifier)."' class='op_ext_icon' src='resources/images/status_".$status_icon.".png' title='".$status_hover."' ".(($draggable) ? "draggable='true' ondragstart=\"drag(event, this.parentNode.getAttribute('name'));\" onclick=\"virtual_drag('".escape($call_identifier)."', '".escape($extension)."');\"" : "onfocus='this.blur();' draggable='false' style='cursor: not-allowed;'").">"; $block .= "</span>"; $block .= " </td>"; $block .= " <td class='op_ext_info ".$style."'>"; if ($dir_icon != '') { $block .= " <img src='resources/images/".$dir_icon.".png' align='right' style='margin-top: 3px; margin-right: 1px; width: 12px; height: 12px; cursor: help;' draggable='false' alt=\"".$text['label-call_direction']."\" title=\"".$text['label-call_direction']."\">"; } $block .= " <span class='op_user_info'>"; - if ($ext['effective_caller_id_name'] != '' && $ext['effective_caller_id_name'] != $extension) { - $block .= " <strong class='strong'>".$ext['effective_caller_id_name']."</strong> (".$extension.")"; + if ($ext['effective_caller_id_name'] != '' && escape($ext['effective_caller_id_name']) != $extension) { + $block .= " <strong class='strong'>".escape($ext['effective_caller_id_name'])."</strong> (".escape($extension).")"; } else { - $block .= " <strong class='strong'>".$extension."</strong>"; + $block .= " <strong class='strong'>".escape($extension)."</strong>"; } $block .= " </span><br>"; if ($ext_state != '') { $block .= " <span class='op_caller_info'>"; $block .= " <table align='right'><tr><td style='text-align: right;'>"; - $block .= " <span class='op_call_info'>".$ext['call_length']."</span><br>"; + $block .= " <span class='op_call_info'>".escape($ext['call_length'])."</span><br>"; $block .= " <span class='call_control'>"; //record if (permission_exists('operator_panel_record') && $ext_state == 'active') { $call_identifier_record = $ext['call_uuid']; - $rec_file = $_SESSION['switch']['recordings']['dir']."/archive/".date("Y")."/".date("M")."/".date("d")."/".$call_identifier_record.".wav"; + $rec_file = $_SESSION['switch']['recordings']['dir']."/archive/".date("Y")."/".date("M")."/".date("d")."/".escape($call_identifier_record).".wav"; if (file_exists($rec_file)) { $block .= "<img src='resources/images/recording.png' style='width: 12px; height: 12px; border: none; margin: 4px 0px 0px 5px; cursor: help;' title=\"".$text['label-recording']."\" ".$onhover_pause_refresh.">"; } @@ -347,7 +347,7 @@ } //eavesdrop if (permission_exists('operator_panel_eavesdrop') && $ext_state == 'active' && sizeof($_SESSION['user']['extensions']) > 0 && !in_array($extension, $_SESSION['user']['extensions'])) { - $block .= "<img src='resources/images/eavesdrop.png' style='width: 12px; height: 12px; border: none; margin: 4px 0px 0px 5px; cursor: pointer;' title='".$text['label-eavesdrop']."' onclick=\"eavesdrop_call('".$ext['destination']."','".$call_identifier."');\" ".$onhover_pause_refresh.">"; + $block .= "<img src='resources/images/eavesdrop.png' style='width: 12px; height: 12px; border: none; margin: 4px 0px 0px 5px; cursor: pointer;' title='".$text['label-eavesdrop']."' onclick=\"eavesdrop_call('".escape($ext['destination'])."','".escape($call_identifier)."');\" ".$onhover_pause_refresh.">"; } //hangup if (permission_exists('operator_panel_hangup') || in_array($extension, $_SESSION['user']['extensions'])) { @@ -360,32 +360,32 @@ else { $call_identifier_hangup_uuid = $call_identifier; } - $block .= "<img src='resources/images/kill.png' style='width: 12px; height: 12px; border: none; margin: 4px 0px 0px 5px; cursor: pointer;' title='".$text['label-hangup']."' onclick=\"hangup_call('".$call_identifier_hangup_uuid."');\" ".$onhover_pause_refresh.">"; + $block .= "<img src='resources/images/kill.png' style='width: 12px; height: 12px; border: none; margin: 4px 0px 0px 5px; cursor: pointer;' title='".$text['label-hangup']."' onclick=\"hangup_call('".escape($call_identifier_hangup_uuid)."');\" ".$onhover_pause_refresh.">"; } $block .= "</span>"; //transfer if (in_array($extension, $_SESSION['user']['extensions']) && $ext_state == 'active') { - $block .= "<img id='destination_control_".$extension."_transfer' class='destination_control' src='resources/images/keypad_transfer.png' style='width: 12px; height: 12px; border: none; margin: 4px 0px 0px 5px; cursor: pointer;' onclick=\"toggle_destination('".$extension."', 'transfer');\" ".$onhover_pause_refresh.">"; + $block .= "<img id='destination_control_".escape($extension)."_transfer' class='destination_control' src='resources/images/keypad_transfer.png' style='width: 12px; height: 12px; border: none; margin: 4px 0px 0px 5px; cursor: pointer;' onclick=\"toggle_destination('".escape($extension)."', 'transfer');\" ".$onhover_pause_refresh.">"; } $block .= " </td></tr></table>"; if (permission_exists('operator_panel_call_details')) { - $block .= " <span id='op_caller_details_".$extension."'><strong>".escape($call_name)."</strong><br>".escape($call_number)."</span>"; + $block .= " <span id='op_caller_details_".escape($extension)."'><strong>".escape($call_name)."</strong><br>".escape($call_number)."</span>"; } $block .= " </span>"; //transfer if (in_array($extension, $_SESSION['user']['extensions']) && $ext_state == 'active') { $call_identifier_transfer = $ext['variable_bridge_uuid']; - $block .= " <form id='frm_destination_".$extension."_transfer' onsubmit=\"go_destination('".$extension."', document.getElementById('destination_".$extension."_transfer').value, 'transfer', '".$call_identifier_transfer."'); return false;\">"; - $block .= " <input type='text' class='formfld' id='destination_".$extension."_transfer' style='width: 100px; min-width: 100px; max-width: 100px; margin-top: 3px; text-align: center; display: none;' onblur=\"toggle_destination('".$extension."', 'transfer');\">"; + $block .= " <form id='frm_destination_".escape($extension)."_transfer' onsubmit=\"go_destination('".escape($extension)."', document.getElementById('destination_".escape($extension)."_transfer').value, 'transfer', '".escape($call_identifier_transfer)."'); return false;\">"; + $block .= " <input type='text' class='formfld' id='destination_".escape($extension)."_transfer' style='width: 100px; min-width: 100px; max-width: 100px; margin-top: 3px; text-align: center; display: none;' onblur=\"toggle_destination('".escape($extension)."', 'transfer');\">"; $block .= " </form>\n"; } } else { //call if (in_array($extension, $_SESSION['user']['extensions'])) { - $block .= " <img id='destination_control_".$extension."_call' class='destination_control' src='resources/images/keypad_call.png' style='width: 12px; height: 12px; border: none; margin-top: 26px; margin-right: 1px; cursor: pointer;' align='right' onclick=\"toggle_destination('".$extension."', 'call');\" ".$onhover_pause_refresh.">"; - $block .= " <form id='frm_destination_".$extension."_call' onsubmit=\"go_destination('".$extension."', document.getElementById('destination_".$extension."_call').value, 'call'); return false;\">"; - $block .= " <input type='text' class='formfld' id='destination_".$extension."_call' style='width: 100px; min-width: 100px; max-width: 100px; margin-top: 10px; text-align: center; display: none;' onblur=\"toggle_destination('".$extension."', 'call');\">"; + $block .= " <img id='destination_control_".escape($extension)."_call' class='destination_control' src='resources/images/keypad_call.png' style='width: 12px; height: 12px; border: none; margin-top: 26px; margin-right: 1px; cursor: pointer;' align='right' onclick=\"toggle_destination('".escape($extension)."', 'call');\" ".$onhover_pause_refresh.">"; + $block .= " <form id='frm_destination_".escape($extension)."_call' onsubmit=\"go_destination('".escape($extension)."', document.getElementById('destination_".escape($extension)."_call').value, 'call'); return false;\">"; + $block .= " <input type='text' class='formfld' id='destination_".escape($extension)."_call' style='width: 100px; min-width: 100px; max-width: 100px; margin-top: 10px; text-align: center; display: none;' onblur=\"toggle_destination('".escape($extension)."', 'call');\">"; $block .= " </form>\n"; } } @@ -395,18 +395,18 @@ if (if_group("superadmin") && isset($_GET['debug'])) { $block .= "<span style='font-size: 10px;'>"; - $block .= "From ID<br> <strong style='color: maroon'>".$extension."</strong><br>"; - $block .= "uuid<br> <strong style='color: ".($call_identifier == $ext['uuid'] ? 'blue' : 'black').";'>".$ext['uuid']."</strong><br>"; - $block .= "call_uuid<br> <strong style='color: ".($call_identifier == $ext['call_uuid'] ? 'blue' : 'black').";'>".$ext['call_uuid']."</strong><br>"; - $block .= "variable_bridge_uuid<br> <strong style='color: ".($call_identifier == $ext['variable_bridge_uuid'] ? 'blue' : 'black').";'>".$ext['variable_bridge_uuid']."</strong><br>"; - $block .= "direction<br> <strong style='color: black;'>".$ext['direction']."</strong><br>"; - $block .= "variable_call_direction<br> <strong style='color: black;'>".$ext['variable_call_direction']."</strong><br>"; - $block .= "state<br> <strong style='color: black;'>".$ext['state']."</strong><br>"; - $block .= "cid_num<br> <strong style='color: black;'>".$ext['cid_num']."</strong><br>"; - $block .= "dest<br> <strong style='color: black;'>".$ext['dest']."</strong><br>"; - $block .= "context<br> <strong style='color: black;'>".$ext['context']."</strong><br>"; - $block .= "presence_id<br> <strong style='color: black;'>".$ext['presence_id']."</strong><br>"; - $block .= "callstate<br> <strong style='color: black;'>".$ext['callstate']."</strong><br>"; + $block .= "From ID<br> <strong style='color: maroon'>".escape($extension)."</strong><br>"; + $block .= "uuid<br> <strong style='color: ".($call_identifier == $ext['uuid'] ? 'blue' : 'black').";'>".escape($ext['uuid'])."</strong><br>"; + $block .= "call_uuid<br> <strong style='color: ".($call_identifier == $ext['call_uuid'] ? 'blue' : 'black').";'>".escape($ext['call_uuid'])."</strong><br>"; + $block .= "variable_bridge_uuid<br> <strong style='color: ".($call_identifier == $ext['variable_bridge_uuid'] ? 'blue' : 'black').";'>".escape($ext['variable_bridge_uuid'])."</strong><br>"; + $block .= "direction<br> <strong style='color: black;'>".escape($ext['direction'])."</strong><br>"; + $block .= "variable_call_direction<br> <strong style='color: black;'>".escape($ext['variable_call_direction'])."</strong><br>"; + $block .= "state<br> <strong style='color: black;'>".escape($ext['state'])."</strong><br>"; + $block .= "cid_num<br> <strong style='color: black;'>".escape($ext['cid_num'])."</strong><br>"; + $block .= "dest<br> <strong style='color: black;'>".escape($ext['dest'])."</strong><br>"; + $block .= "context<br> <strong style='color: black;'>".escape($ext['context'])."</strong><br>"; + $block .= "presence_id<br> <strong style='color: black;'>".escape($ext['presence_id'])."</strong><br>"; + $block .= "callstate<br> <strong style='color: black;'>".escape($ext['callstate'])."</strong><br>"; $block .= "</span>"; } $block .= "</div>"; @@ -430,7 +430,7 @@ if ($_REQUEST['group'] != '') { if (sizeof($user_extensions) > 0) { echo "<br>"; } - echo "<strong style='color: black;'>".ucwords($_REQUEST['group'])."</strong>"; + echo "<strong style='color: black;'>".ucwords(escape($_REQUEST['group']))."</strong>"; echo "<br><br>"; } else if (sizeof($user_extensions) > 0) {
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
2- github.com/fusionpbx/fusionpbx/commit/7fec1014ff0d08e36be6a3f7664edb3a9df7b4acmitrex_refsource_MISC
- resp3ctblog.wordpress.com/2019/10/19/fusionpbx-xss-18/mitrex_refsource_MISC
News mentions
0No linked articles in our index yet.