prevent traversal & xss risk
This commit is contained in:
24
upload.php
24
upload.php
@@ -8,7 +8,12 @@ if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate folder name input. Only allow letters, numbers, underscores, dashes, and spaces.
|
||||||
$folder = isset($_POST['folder']) ? trim($_POST['folder']) : 'root';
|
$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.
|
// Determine the target upload directory.
|
||||||
$uploadDir = UPLOAD_DIR;
|
$uploadDir = UPLOAD_DIR;
|
||||||
@@ -23,15 +28,28 @@ if ($folder !== 'root') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load metadata for uploaded files.
|
||||||
$metadataFile = META_DIR . META_FILE;
|
$metadataFile = META_DIR . META_FILE;
|
||||||
$metadata = file_exists($metadataFile) ? json_decode(file_get_contents($metadataFile), true) : [];
|
$metadata = file_exists($metadataFile) ? json_decode(file_get_contents($metadataFile), true) : [];
|
||||||
$metadataChanged = false;
|
$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) {
|
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)) {
|
if (move_uploaded_file($_FILES["file"]["tmp_name"][$index], $targetPath)) {
|
||||||
// Use a metadata key that includes the folder if not in root.
|
// Build the metadata key, including the folder if not in root.
|
||||||
$metaKey = ($folder !== 'root') ? $folder . "/" . $fileName : $fileName;
|
$metaKey = ($folder !== 'root') ? $folder . "/" . $safeFileName : $safeFileName;
|
||||||
if (!isset($metadata[$metaKey])) {
|
if (!isset($metadata[$metaKey])) {
|
||||||
$uploadedDate = date(DATE_TIME_FORMAT);
|
$uploadedDate = date(DATE_TIME_FORMAT);
|
||||||
$uploader = $_SESSION['username'] ?? "Unknown";
|
$uploader = $_SESSION['username'] ?? "Unknown";
|
||||||
|
|||||||
47
utils.js
47
utils.js
@@ -435,8 +435,19 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
.catch(error => console.error("Error loading file list:", error));
|
.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, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """)
|
||||||
|
.replace(/'/g, "'");
|
||||||
|
}
|
||||||
|
|
||||||
function renderFileTable(folder) {
|
function renderFileTable(folder) {
|
||||||
const fileListContainer = document.getElementById("fileList");
|
const fileListContainer = document.getElementById("fileList");
|
||||||
|
// Use encodeURIComponent on folder for the URL part
|
||||||
const folderPath = (folder === "root") ? "uploads/" : "uploads/" + encodeURIComponent(folder) + "/";
|
const folderPath = (folder === "root") ? "uploads/" : "uploads/" + encodeURIComponent(folder) + "/";
|
||||||
let tableHTML = `<table class="table">
|
let tableHTML = `<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -465,17 +476,27 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
fileData.forEach(file => {
|
fileData.forEach(file => {
|
||||||
// Determine if file is editable via your canEditFile() helper
|
// Determine if file is editable via your canEditFile() helper
|
||||||
const isEditable = canEditFile(file.name);
|
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>
|
tableHTML += `<tr>
|
||||||
<td><input type="checkbox" class="file-checkbox" value="${file.name}" onclick="toggleDeleteButton()"></td>
|
<td><input type="checkbox" class="file-checkbox" value="${safeFileName}" onclick="toggleDeleteButton()"></td>
|
||||||
<td>${file.name}</td>
|
<td>${safeFileName}</td>
|
||||||
<td style="white-space: nowrap;">${file.modified}</td>
|
<td style="white-space: nowrap;">${safeModified}</td>
|
||||||
<td style="white-space: nowrap;">${file.uploaded}</td>
|
<td style="white-space: nowrap;">${safeUploaded}</td>
|
||||||
<td style="white-space: nowrap;">${file.size}</td>
|
<td style="white-space: nowrap;">${safeSize}</td>
|
||||||
<td style="white-space: nowrap;">${file.uploader || "Unknown"}</td>
|
<td style="white-space: nowrap;">${safeUploader}</td>
|
||||||
<td>
|
<td>
|
||||||
<div style="display: inline-flex; align-items: center; gap: 5px; flex-wrap: nowrap;">
|
<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>
|
<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>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
@@ -493,6 +514,8 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Show or hide action buttons based on whether files exist
|
// Show or hide action buttons based on whether files exist
|
||||||
const deleteBtn = document.getElementById("deleteSelectedBtn");
|
const deleteBtn = document.getElementById("deleteSelectedBtn");
|
||||||
const copyBtn = document.getElementById("copySelectedBtn");
|
const copyBtn = document.getElementById("copySelectedBtn");
|
||||||
@@ -508,7 +531,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
moveBtn.style.display = "none";
|
moveBtn.style.display = "none";
|
||||||
document.getElementById("copyMoveFolderSelect").style.display = "none";
|
document.getElementById("copyMoveFolderSelect").style.display = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortFiles(column, folder) {
|
function sortFiles(column, folder) {
|
||||||
@@ -691,7 +714,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
const folderUsed = folder || currentFolder || "root";
|
const folderUsed = folder || currentFolder || "root";
|
||||||
const folderPath = (folderUsed === "root") ? "uploads/" : "uploads/" + encodeURIComponent(folderUsed) + "/";
|
const folderPath = (folderUsed === "root") ? "uploads/" : "uploads/" + encodeURIComponent(folderUsed) + "/";
|
||||||
const fileUrl = folderPath + encodeURIComponent(fileName) + "?t=" + new Date().getTime();
|
const fileUrl = folderPath + encodeURIComponent(fileName) + "?t=" + new Date().getTime();
|
||||||
|
|
||||||
// First, use a HEAD request to check file size
|
// First, use a HEAD request to check file size
|
||||||
fetch(fileUrl, { method: "HEAD" })
|
fetch(fileUrl, { method: "HEAD" })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
@@ -704,8 +727,8 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
return fetch(fileUrl);
|
return fetch(fileUrl);
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("HTTP error! Status: " + response.status);
|
throw new Error("HTTP error! Status: " + response.status);
|
||||||
}
|
}
|
||||||
return response.text();
|
return response.text();
|
||||||
})
|
})
|
||||||
@@ -726,7 +749,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
})
|
})
|
||||||
.catch(error => console.error("Error loading file:", error));
|
.catch(error => console.error("Error loading file:", error));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
window.saveFile = function (fileName, folder) {
|
window.saveFile = function (fileName, folder) {
|
||||||
const editor = document.getElementById("fileEditor");
|
const editor = document.getElementById("fileEditor");
|
||||||
|
|||||||
Reference in New Issue
Block a user