prevent traversal & xss risk

This commit is contained in:
Ryan
2025-03-05 05:47:48 -05:00
committed by GitHub
parent 17d46a6a32
commit 6a41280667
2 changed files with 56 additions and 15 deletions

View File

@@ -8,7 +8,12 @@ if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
exit;
}
// Validate folder name input. Only allow letters, numbers, underscores, dashes, and spaces.
$folder = isset($_POST['folder']) ? trim($_POST['folder']) : 'root';
if ($folder !== 'root' && !preg_match('/^[A-Za-z0-9_\- ]+$/', $folder)) {
echo json_encode(["error" => "Invalid folder name"]);
exit;
}
// Determine the target upload directory.
$uploadDir = UPLOAD_DIR;
@@ -23,15 +28,28 @@ if ($folder !== 'root') {
}
}
// Load metadata for uploaded files.
$metadataFile = META_DIR . META_FILE;
$metadata = file_exists($metadataFile) ? json_decode(file_get_contents($metadataFile), true) : [];
$metadataChanged = false;
// Define a safe pattern for file names: letters, numbers, underscores, dashes, dots, and spaces.
$safeFileNamePattern = '/^[A-Za-z0-9_\-\. ]+$/';
foreach ($_FILES["file"]["name"] as $index => $fileName) {
$targetPath = $uploadDir . basename($fileName);
// Use basename to strip any directory components.
$safeFileName = basename($fileName);
// Validate that the sanitized file name contains only allowed characters.
if (!preg_match($safeFileNamePattern, $safeFileName)) {
echo json_encode(["error" => "Invalid file name: " . $fileName]);
exit;
}
$targetPath = $uploadDir . $safeFileName;
if (move_uploaded_file($_FILES["file"]["tmp_name"][$index], $targetPath)) {
// Use a metadata key that includes the folder if not in root.
$metaKey = ($folder !== 'root') ? $folder . "/" . $fileName : $fileName;
// Build the metadata key, including the folder if not in root.
$metaKey = ($folder !== 'root') ? $folder . "/" . $safeFileName : $safeFileName;
if (!isset($metadata[$metaKey])) {
$uploadedDate = date(DATE_TIME_FORMAT);
$uploader = $_SESSION['username'] ?? "Unknown";

View File

@@ -435,8 +435,19 @@ document.addEventListener("DOMContentLoaded", function () {
.catch(error => console.error("Error loading file list:", error));
}
// Helper function to escape special HTML characters
function escapeHTML(str) {
return String(str)
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
function renderFileTable(folder) {
const fileListContainer = document.getElementById("fileList");
// Use encodeURIComponent on folder for the URL part
const folderPath = (folder === "root") ? "uploads/" : "uploads/" + encodeURIComponent(folder) + "/";
let tableHTML = `<table class="table">
<thead>
@@ -465,17 +476,27 @@ document.addEventListener("DOMContentLoaded", function () {
fileData.forEach(file => {
// Determine if file is editable via your canEditFile() helper
const isEditable = canEditFile(file.name);
// Escape user-supplied file name and other properties for safe HTML output.
const safeFileName = escapeHTML(file.name);
const safeModified = escapeHTML(file.modified);
const safeUploaded = escapeHTML(file.uploaded);
const safeSize = escapeHTML(file.size);
const safeUploader = escapeHTML(file.uploader || "Unknown");
tableHTML += `<tr>
<td><input type="checkbox" class="file-checkbox" value="${file.name}" onclick="toggleDeleteButton()"></td>
<td>${file.name}</td>
<td style="white-space: nowrap;">${file.modified}</td>
<td style="white-space: nowrap;">${file.uploaded}</td>
<td style="white-space: nowrap;">${file.size}</td>
<td style="white-space: nowrap;">${file.uploader || "Unknown"}</td>
<td><input type="checkbox" class="file-checkbox" value="${safeFileName}" onclick="toggleDeleteButton()"></td>
<td>${safeFileName}</td>
<td style="white-space: nowrap;">${safeModified}</td>
<td style="white-space: nowrap;">${safeUploaded}</td>
<td style="white-space: nowrap;">${safeSize}</td>
<td style="white-space: nowrap;">${safeUploader}</td>
<td>
<div style="display: inline-flex; align-items: center; gap: 5px; flex-wrap: nowrap;">
<a class="btn btn-sm btn-success" href="${folderPath + encodeURIComponent(file.name)}" download>Download</a>
${isEditable ? `<button class="btn btn-sm btn-primary ml-2" onclick="editFile('${file.name}', '${folder}')">Edit</button>` : ""}
${isEditable
? `<button class="btn btn-sm btn-primary ml-2" onclick="editFile(${JSON.stringify(file.name)}, ${JSON.stringify(folder)})">Edit</button>`
: ""
}
</div>
</td>
</tr>`;
@@ -493,6 +514,8 @@ document.addEventListener("DOMContentLoaded", function () {
});
});
// Show or hide action buttons based on whether files exist
const deleteBtn = document.getElementById("deleteSelectedBtn");
const copyBtn = document.getElementById("copySelectedBtn");
@@ -508,7 +531,7 @@ document.addEventListener("DOMContentLoaded", function () {
moveBtn.style.display = "none";
document.getElementById("copyMoveFolderSelect").style.display = "none";
}
}
function sortFiles(column, folder) {
@@ -691,7 +714,7 @@ document.addEventListener("DOMContentLoaded", function () {
const folderUsed = folder || currentFolder || "root";
const folderPath = (folderUsed === "root") ? "uploads/" : "uploads/" + encodeURIComponent(folderUsed) + "/";
const fileUrl = folderPath + encodeURIComponent(fileName) + "?t=" + new Date().getTime();
// First, use a HEAD request to check file size
fetch(fileUrl, { method: "HEAD" })
.then(response => {
@@ -704,8 +727,8 @@ document.addEventListener("DOMContentLoaded", function () {
return fetch(fileUrl);
})
.then(response => {
if (!response.ok) {
throw new Error("HTTP error! Status: " + response.status);
if (!response.ok) {
throw new Error("HTTP error! Status: " + response.status);
}
return response.text();
})
@@ -726,7 +749,7 @@ document.addEventListener("DOMContentLoaded", function () {
})
.catch(error => console.error("Error loading file:", error));
};
window.saveFile = function (fileName, folder) {
const editor = document.getElementById("fileEditor");