Skip to content

Commit 343cc56

Browse files
committed
Fix CVE-2025-48494 XSS vulnerability for file upload, refactoring and formatting
1 parent 65ddbc6 commit 343cc56

File tree

4 files changed

+143
-22
lines changed

4 files changed

+143
-22
lines changed

internal/webserver/web/static/js/admin_ui_upload.js

Lines changed: 128 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ function showEditModal(filename, id, downloads, expiry, password, unlimitedown,
419419
$('body').append(myClone);
420420
});
421421

422-
document.getElementById("m_filenamelabel").innerHTML = filename;
422+
document.getElementById("m_filenamelabel").innerText = filename;
423423
document.getElementById("mc_expiry").setAttribute("data-timestamp", expiry);
424424
document.getElementById("mb_save").setAttribute('data-fileid', id);
425425
createCalendar(expiry);
@@ -562,14 +562,14 @@ function parseSseData(data) {
562562
function setNewDownloadCount(id, downloadCount, downloadsRemaining) {
563563
let downloadCell = document.getElementById("cell-downloads-" + id);
564564
if (downloadCell != null) {
565-
downloadCell.innerHTML = downloadCount;
565+
downloadCell.innerText = downloadCount;
566566
downloadCell.classList.add("updatedDownloadCount");
567567
setTimeout(() => downloadCell.classList.remove("updatedDownloadCount"), 500);
568568
}
569569
if (downloadsRemaining != -1) {
570570
let downloadsRemainingCell = document.getElementById("cell-downloadsRemaining-" + id);
571571
if (downloadsRemainingCell != null) {
572-
downloadsRemainingCell.innerHTML = downloadsRemaining;
572+
downloadsRemainingCell.innerText = downloadsRemaining;
573573
downloadsRemainingCell.classList.add("updatedDownloadCount");
574574
setTimeout(() => downloadsRemainingCell.classList.remove("updatedDownloadCount"), 500);
575575
}
@@ -662,6 +662,7 @@ function removeFileStatus(chunkId) {
662662
function addRow(item) {
663663
let table = document.getElementById("downloadtable");
664664
let row = table.insertRow(0);
665+
item.Id = sanitizeId(item.Id);
665666
row.id = "row-" + item.Id;
666667
let cellFilename = row.insertCell(0);
667668
let cellFileSize = row.insertCell(1);
@@ -670,11 +671,7 @@ function addRow(item) {
670671
let cellDownloadCount = row.insertCell(4);
671672
let cellUrl = row.insertCell(5);
672673
let cellButtons = row.insertCell(6);
673-
let lockIcon = "";
674674

675-
if (item.IsPasswordProtected === true) {
676-
lockIcon = ' <i title="Password protected" class="bi bi-key"></i>';
677-
}
678675
cellFilename.innerText = item.Name;
679676
cellFilename.id = "cell-name-" + item.Id;
680677
cellDownloadCount.id = "cell-downloads-" + item.Id;
@@ -691,19 +688,130 @@ function addRow(item) {
691688
cellStoredUntil.innerText = item.ExpireAtString;
692689
}
693690
cellDownloadCount.innerText = item.DownloadCount;
694-
cellUrl.innerHTML = '<a target="_blank" style="color: inherit" id="url-href-' + item.Id + '" href="' + item.UrlDownload + '">' + item.Id + '</a>' + lockIcon;
695691

696-
let buttons = '<button type="button" onclick="showToast(1000)" id="url-button-' + item.Id + '" data-clipboard-text="' + item.UrlDownload + '" class="copyurl btn btn-outline-light btn-sm"><i class="bi bi-copy"></i> URL</button> ';
697-
if (item.UrlHotlink === "") {
698-
buttons = buttons + '<button type="button"class="copyurl btn btn-outline-light btn-sm disabled"><i class="bi bi-copy"></i> Hotlink</button> ';
692+
// === URL Link ===
693+
const link = document.createElement('a');
694+
link.href = item.UrlDownload;
695+
link.target = '_blank';
696+
link.style.color = 'inherit';
697+
link.id = 'url-href-'+item.Id;
698+
link.textContent = item.Id;
699+
700+
cellUrl.appendChild(link);
701+
702+
if (item.IsPasswordProtected === true) {
703+
const icon = document.createElement('i');
704+
icon.className = 'bi bi-key';
705+
icon.title = 'Password protected';
706+
cellUrl.appendChild(document.createTextNode(' '));
707+
cellUrl.appendChild(icon);
708+
}
709+
710+
// === Button: Copy URL ===
711+
const copyUrlBtn = document.createElement('button');
712+
copyUrlBtn.type = 'button';
713+
copyUrlBtn.className = 'copyurl btn btn-outline-light btn-sm';
714+
copyUrlBtn.dataset.clipboardText = item.UrlDownload;
715+
copyUrlBtn.id = 'url-button-'+item.Id;
716+
copyUrlBtn.title = 'Copy URL';
717+
718+
const copyIcon = document.createElement('i');
719+
copyIcon.className = 'bi bi-copy';
720+
copyUrlBtn.appendChild(copyIcon);
721+
copyUrlBtn.appendChild(document.createTextNode(' URL'));
722+
723+
copyUrlBtn.addEventListener('click', () => {
724+
showToast(1000);
725+
});
726+
727+
cellButtons.appendChild(copyUrlBtn);
728+
cellButtons.appendChild(document.createTextNode(' '));
729+
730+
// === Button: Copy Hotlink ===
731+
const hotlinkBtn = document.createElement('button');
732+
hotlinkBtn.type = 'button';
733+
hotlinkBtn.className = 'copyurl btn btn-outline-light btn-sm';
734+
hotlinkBtn.title = 'Copy Hotlink';
735+
736+
const hotlinkIcon = document.createElement('i');
737+
hotlinkIcon.className = 'bi bi-copy';
738+
hotlinkBtn.appendChild(hotlinkIcon);
739+
hotlinkBtn.appendChild(document.createTextNode(' Hotlink'));
740+
741+
if (item.UrlHotlink) {
742+
hotlinkBtn.dataset.clipboardText = item.UrlHotlink;
743+
hotlinkBtn.addEventListener('click', () => {
744+
showToast(1000);
745+
});
699746
} else {
700-
buttons = buttons + '<button type="button" onclick="showToast(1000)" data-clipboard-text="' + item.UrlHotlink + '" class="copyurl btn btn-outline-light btn-sm"><i class="bi bi-copy"></i> Hotlink</button> ';
747+
hotlinkBtn.disabled = true;
701748
}
702-
buttons = buttons + '<button type="button" id="qrcode-' + item.Id + '" title="QR Code" class="btn btn-outline-light btn-sm" onclick="showQrCode(\'' + item.UrlDownload + '\');"><i class="bi bi-qr-code"></i></button> ';
703-
buttons = buttons + '<button type="button" title="Edit" class="btn btn-outline-light btn-sm" onclick="showEditModal(\'' + item.Name + '\',\'' + item.Id + '\', ' + item.DownloadsRemaining + ', ' + item.ExpireAt + ', ' + item.IsPasswordProtected + ', ' + item.UnlimitedDownloads + ', ' + item.UnlimitedTime + ', ' + item.IsEndToEndEncrypted + ', canReplaceOwnFiles);"><i class="bi bi-pencil"></i></button> ';
704-
buttons = buttons + '<button type="button" id="button-delete-' + item.Id + '" title="Delete" class="btn btn-outline-danger btn-sm" onclick="deleteFile(\'' + item.Id + '\')"><i class="bi bi-trash3"></i></button>';
705749

706-
cellButtons.innerHTML = buttons;
750+
cellButtons.appendChild(hotlinkBtn);
751+
cellButtons.appendChild(document.createTextNode(' '));
752+
753+
// === Button: QR Code ===
754+
const qrBtn = document.createElement('button');
755+
qrBtn.type = 'button';
756+
qrBtn.className = 'btn btn-outline-light btn-sm';
757+
qrBtn.title = 'QR Code';
758+
qrBtn.id = 'qrcode-'+item.Id;
759+
760+
const qrIcon = document.createElement('i');
761+
qrIcon.className = 'bi bi-qr-code';
762+
qrBtn.appendChild(qrIcon);
763+
764+
qrBtn.addEventListener('click', () => {
765+
showQrCode(item.UrlDownload);
766+
});
767+
768+
cellButtons.appendChild(qrBtn);
769+
cellButtons.appendChild(document.createTextNode(' '));
770+
771+
// === Button: Edit ===
772+
const editBtn = document.createElement('button');
773+
editBtn.type = 'button';
774+
editBtn.className = 'btn btn-outline-light btn-sm';
775+
editBtn.title = 'Edit';
776+
777+
const editIcon = document.createElement('i');
778+
editIcon.className = 'bi bi-pencil';
779+
editBtn.appendChild(editIcon);
780+
781+
editBtn.addEventListener('click', () => {
782+
showEditModal(
783+
item.Name,
784+
item.Id,
785+
item.DownloadsRemaining,
786+
item.ExpireAt,
787+
item.IsPasswordProtected,
788+
item.UnlimitedDownloads,
789+
item.UnlimitedTime,
790+
item.IsEndToEndEncrypted,
791+
canReplaceOwnFiles
792+
);
793+
});
794+
795+
cellButtons.appendChild(editBtn);
796+
cellButtons.appendChild(document.createTextNode(' '));
797+
798+
// === Button: Delete ===
799+
const deleteBtn = document.createElement('button');
800+
deleteBtn.type = 'button';
801+
deleteBtn.className = 'btn btn-outline-danger btn-sm';
802+
deleteBtn.title = 'Delete';
803+
deleteBtn.id = 'button-delete-'+item.Id;
804+
805+
const deleteIcon = document.createElement('i');
806+
deleteIcon.className = 'bi bi-trash3';
807+
deleteBtn.appendChild(deleteIcon);
808+
809+
deleteBtn.addEventListener('click', () => {
810+
deleteFile(item.Id);
811+
});
812+
813+
cellButtons.appendChild(deleteBtn);
814+
707815

708816
cellFilename.classList.add('newItem');
709817
cellFileSize.classList.add('newItem');
@@ -718,6 +826,10 @@ function addRow(item) {
718826
return item.Id;
719827
}
720828

829+
function sanitizeId(str) {
830+
return str.replace(/[^a-zA-Z0-9]/g, '');
831+
}
832+
721833
function changeRowCount(add, row) {
722834
let datatable = $('#maintable').DataTable();
723835
if (rowCount == -1) {

internal/webserver/web/static/js/end2end_admin.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
function displayError(err) {
22
document.getElementById("errordiv").style.display = "block";
3-
document.getElementById("errormessage").innerHTML = "<b>Error: </b> " + err.toString().replace(/^Error:/gi, "");
4-
console.error('Caught exception', err)
5-
}
3+
const errorMessageEl = document.getElementById("errormessage");
4+
5+
errorMessageEl.innerText = "";
6+
const bold = document.createElement("b");
7+
bold.innerText = "Error: ";
8+
const message = document.createTextNode(err.toString().replace(/^Error:/gi, ""));
69

10+
errorMessageEl.appendChild(bold);
11+
errorMessageEl.appendChild(message);
12+
13+
console.error('Caught exception', err);
14+
}
715

816
function checkIfE2EKeyIsSet() {
917
if (!isE2EKeySet()) {
@@ -165,10 +173,11 @@ function decryptFileEntry(id, filename, cipher) {
165173
for (let i = 0; i < rows.length; i++) {
166174
const cell = datatable.cell(i, 0).node();
167175
if ("cell-name-" + id === $(cell).attr("id")) {
168-
datatable.cell(i, 0).data(filename);
176+
let cellNode = datatable.cell(i, 0).node();
169177
let urlNode = datatable.cell(i, 5).node();
170178
let urlLink = urlNode.querySelector("a");
171179
let url = urlLink.getAttribute("href");
180+
cellNode.textContent = filename;
172181
if (!url.includes(cipher)) {
173182
if (IncludeFilename) {
174183
url = url.replace("/Encrypted%20File", "/" + encodeURI(filename));

0 commit comments

Comments
 (0)