security: harden application security baseline

- Fixed SQL injection vulnerabilities with prepared statements
- Added column whitelist protection for dynamic termin updates
- Extracted iCloud credentials into protected config file
- Moved sensitive configuration files out of webroot
- Added authentication and admin checks to PVE APIs
- Added CSRF protection to write operations and APIs
- Disabled debug output in production mode
- Added centralized configuration handling
- Protected sensitive directories with access rules
- Added initial project security documentation
This commit is contained in:
2026-07-22 02:23:34 +02:00
parent 28ebf1ff12
commit d97605f801
8 changed files with 370 additions and 121 deletions
+21 -67
View File
@@ -108,6 +108,11 @@ $changes = $sql->fetchAll(PDO::FETCH_ASSOC);
if(isset($_POST['save'])){
// CSRF check
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
die("CSRF validation failed");
}
if(isset($_POST['inputStartzeit_ts']) && !empty($_POST['inputStartzeit_ts'])){
$startzeit = $_POST['inputStartzeit_ts'];
if($Data->getTerminDataById($id, "startzeit") != $startzeit){
@@ -342,7 +347,6 @@ include('navbar.php');
>
<input type="hidden" name="inputStartzeit_ts" id="inputStartzeit_ts">
</div>
</div>
<!-- Endzeit -->
<div class="col-md-6">
@@ -357,7 +361,6 @@ include('navbar.php');
>
<input type="hidden" name="inputEndzeit_ts" id="inputEndzeit_ts">
</div>
</div>
<!-- Location -->
<div class="col-md-6">
@@ -367,7 +370,6 @@ include('navbar.php');
<input type="text" class="form-control" name="inputLocation"
value="<?php echo $Data->getTerminDataById($id, 'location') ?>">
</div>
</div>
<!-- Gage -->
<div class="col-md-6">
@@ -377,7 +379,6 @@ include('navbar.php');
<input type="text" class="form-control" name="inputGage"
value="<?php echo $Data->getTerminDataById($id, 'gage') ?>">
</div>
</div>
<!-- Beschreibung -->
<div class="col-12">
@@ -394,7 +395,6 @@ include('navbar.php');
echo htmlspecialchars($Data->getTerminDataById($id, 'beschreibung'));
?></textarea>
</div>
</div>
<!-- Beteiligte Personen -->
<div class="col-md-6">
@@ -412,9 +412,6 @@ include('navbar.php');
</select>
</div>
</div>
<!-- Status -->
<div class="btn-group my-4" role="group">
<label class="btn btn-primary">
@@ -435,6 +432,7 @@ include('navbar.php');
</div>
<input type="hidden" name="id" value="<?php echo $id ?>">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>">
<?php if (isset($_SESSION['role']) && $_SESSION['role'] === 'admin'): ?>
@@ -501,7 +499,6 @@ include('navbar.php');
</form>
</div>
</div>
</div>
@@ -610,7 +607,6 @@ include('navbar.php');
</div>
</div>
</div>
<!-- Invoice Modal -->
<div class="modal fade" id="invoiceModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
@@ -640,21 +636,17 @@ include('navbar.php');
<label class="form-label mb-1">Ort</label>
<input type="text" class="form-control" id="invCity" placeholder="Nienburg">
</div>
</div>
<div class="mt-2">
<label class="form-label mb-1">Land</label>
<input type="text" class="form-control" id="invCountry" value="DE">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button type="submit" class="btn btn-success" id="btnCreateInvoice">Erstellen & PDF</button>
</div>
</form>
</div>
</div>
<!-- Delete Modal -->
<div class="modal fade" id="deleteModal" tabindex="-1">
@@ -680,15 +672,13 @@ include('navbar.php');
<form action="termin_delete.php" method="POST">
<input type="hidden" name="id" value="<?= $id ?>">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<button type="submit" class="btn btn-danger">
<i class="bi bi-trash"></i> Ja, löschen
</button>
</form>
</div>
</div>
</div>
</div>
<!-- MODAL: Termin existiert -->
<div class="modal fade" id="terminConflictModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
@@ -702,15 +692,11 @@ include('navbar.php');
<div class="modal-body">
<p>Für dieses Datum existiert bereits mindestens ein Termin:</p>
<div id="terminConflictList" class="border rounded p-2 bg-light"></div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">Okay</button>
</div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
@@ -838,53 +824,21 @@ document.addEventListener("DOMContentLoaded", () => {
// reset
document.getElementById('invCustomerName').value = '';
document.getElementById('invStreet').value = '';
document.getElementById('invZip').value = '';
document.getElementById('invCity').value = '';
document.getElementById('invCountry').value = 'DE';
Now I'll add the CSRF validation to `termin_show.php`. Let me make the edits step by step:
invoiceModal.show();
});
<edit_file>
<path>d:/anjuma-dashboard/termin_show.php</path>
<content>
<<<<<<< SEARCH
if(isset($_POST['save'])){
document.getElementById('invoiceForm').addEventListener('submit', async (ev) => {
ev.preventDefault();
if(isset($_POST['inputStartzeit_ts']) && !empty($_POST['inputStartzeit_ts'])){
=======
if(isset($_POST['save'])){
const cname = document.getElementById('invCustomerName').value.trim();
if (!cname) { alert('Bitte Kunde/Firma eintragen'); return; }
const btn = document.getElementById('btnCreateInvoice');
btn.disabled = true;
btn.textContent = 'Erstelle…';
try {
const fd = new FormData();
fd.append('termin_id', terminIdForInvoice);
fd.append('customer_name', cname);
fd.append('customer_street', document.getElementById('invStreet').value.trim());
fd.append('customer_zip', document.getElementById('invZip').value.trim());
fd.append('customer_city', document.getElementById('invCity').value.trim());
fd.append('customer_country', document.getElementById('invCountry').value.trim() || 'DE');
const res = await fetch(API_INVOICE_CREATE, { method: 'POST', body: fd });
const json = await res.json();
if (!json.ok) throw new Error(json.error || 'Rechnung konnte nicht erstellt werden');
invoiceModal.hide();
// PDF öffnen
window.open(API_INVOICE_PDF + json.invoice_id, '_blank');
// Seite neu laden, damit Button auf "PDF öffnen" wechselt
window.location.reload();
} catch (e) {
alert('Fehler: ' + e.message);
} finally {
btn.disabled = false;
btn.textContent = 'Erstellen & PDF';
}
});
});
</script>
// CSRF check
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
die("CSRF validation failed");
}
if(isset($_POST['inputStartzeit_ts']) && !empty($_POST['inputStartzeit_ts'])){