Skip to content

Commit a46815d

Browse files
committed
fix(security): escape CDR CallerID data in DataTables to prevent stored XSS
Closes #1024 - Escape src_num, dst_num via SecurityUtils.escapeHtml() in createdRow callback (.html() rendered raw server data — direct XSS vector) - Escape data.ids and data.DT_RowId in template literal attributes - Add strip_tags() on CALLERID(name) and CONNECTEDLINE(name) at CDR write time in ActionHangupChan and ActionDialAnswer (defense-in-depth) - data-cdr-name attributes set via jQuery .attr() remain unescaped since .attr() is DOM-safe and downstream already escapes on read
1 parent 68d57de commit a46815d

4 files changed

Lines changed: 15 additions & 13 deletions

File tree

sites/admin-cabinet/assets/js/pbx/CallDetailRecords/call-detail-records-index.js

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sites/admin-cabinet/assets/js/src/CallDetailRecords/call-detail-records-index.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -359,13 +359,13 @@ const callDetailRecords = {
359359
} else {
360360
$('td', row).eq(0).html('');
361361
}
362-
$('td', row).eq(1).html(data[0]);
362+
$('td', row).eq(1).html(SecurityUtils.escapeHtml(data[0]));
363363
$('td', row).eq(2)
364-
.html(data[1])
364+
.html(SecurityUtils.escapeHtml(data[1]))
365365
.addClass('need-update')
366366
.attr('data-cdr-name', data[6] || '');
367367
$('td', row).eq(3)
368-
.html(data[2])
368+
.html(SecurityUtils.escapeHtml(data[2]))
369369
.addClass('need-update')
370370
.attr('data-cdr-name', data[7] || '');
371371

@@ -385,15 +385,15 @@ const callDetailRecords = {
385385
// WHY: Log icon links to system-diagnostic page which requires specific permissions
386386
const canViewLogs = typeof ACLHelper !== 'undefined' && ACLHelper.isAllowed('viewSystemDiagnostic');
387387
if (canViewLogs && data.ids !== '') {
388-
actionsHtml += `<i data-ids="${data.ids}" class="file alternate outline icon" style="cursor: pointer; margin-right: 8px;"></i>`;
388+
actionsHtml += `<i data-ids="${SecurityUtils.escapeHtml(data.ids)}" class="file alternate outline icon" style="cursor: pointer; margin-right: 8px;"></i>`;
389389
}
390390

391391
// Add delete button
392392
// WHY: Use two-steps-delete mechanism to prevent accidental deletion
393393
// First click changes trash icon to close icon, second click deletes
394394
// WHY: Use data.DT_RowId which contains linkedid for grouped records
395395
actionsHtml += `<a href="#" class="two-steps-delete delete-record popuped"
396-
data-record-id="${data.DT_RowId}"
396+
data-record-id="${SecurityUtils.escapeHtml(data.DT_RowId)}"
397397
data-content="${globalTranslate.cdr_DeleteRecord || 'Delete record'}">
398398
<i class="icon trash red" style="margin: 0;"></i>
399399
</a>`;

src/Core/Workers/Libs/WorkerCallEvents/ActionDialAnswer.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,12 +307,12 @@ private static function fillAnsweredCdr(WorkerCallEvents $worker, array $data, o
307307
$am = Util::getAstManager('off');
308308
$connectedName = $am->GetVar($srcChan, 'CONNECTEDLINE(name)', null, false);
309309
if (is_string($connectedName) && $connectedName !== '' && $connectedName !== $row->dst_num) {
310-
$row->writeAttribute('dst_name', $connectedName);
310+
$row->writeAttribute('dst_name', strip_tags($connectedName));
311311
}
312312
if (empty($row->src_name)) {
313313
$callerName = $am->GetVar($srcChan, 'CALLERID(name)', null, false);
314314
if (is_string($callerName) && $callerName !== '' && $callerName !== $row->src_num) {
315-
$row->writeAttribute('src_name', $callerName);
315+
$row->writeAttribute('src_name', strip_tags($callerName));
316316
}
317317
}
318318
} catch (\Throwable $e) {

src/Core/Workers/Libs/WorkerCallEvents/ActionHangupChan.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,8 @@ public static function hangupChanCheckSipTrtansfer(WorkerCallEvents $worker, arr
305305
$CALLERID_NAME = $am->GetVar($BRIDGEPEER, 'CALLERID(name)', null, false);
306306
if (!is_string($CALLERID_NAME) || $CALLERID_NAME === $CALLERID) {
307307
$CALLERID_NAME = '';
308+
} else {
309+
$CALLERID_NAME = strip_tags($CALLERID_NAME);
308310
}
309311
$n_data['action'] = 'sip_transfer';
310312
$n_data['src_chan'] = $data_chan['out'] ? $data_chan['chan'] : $BRIDGEPEER;

0 commit comments

Comments
 (0)