CSRF - erste Erichtigung
This commit is contained in:
@@ -11,7 +11,7 @@ Testing, markiert aber die wichtigsten Risiken fuer die SaaS-Umstrukturierung.
|
|||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| Hart codierte DB-Zugangsdaten | `jahresauswertung.php` Zeilen 4-8 | Secret-Leak, direkte Produktiv-DB-Gefahr | Zugangsdaten rotieren, Skript deaktivieren oder auf Env-Konfiguration umstellen |
|
| Hart codierte DB-Zugangsdaten | `jahresauswertung.php` Zeilen 4-8 | Secret-Leak, direkte Produktiv-DB-Gefahr | Zugangsdaten rotieren, Skript deaktivieren oder auf Env-Konfiguration umstellen |
|
||||||
| Schreibende Seiten ohne eigene Rollenpruefung | `stricheintragen.php`, `einzahlung.php`, `mailversenden.php`, `exportKaffeeliste.php` | Direkter URL-Aufruf kann Aktionen erlauben | Jede Seite serverseitig mit `requireRole` absichern |
|
| Schreibende Seiten ohne eigene Rollenpruefung | `stricheintragen.php`, `einzahlung.php`, `mailversenden.php`, `exportKaffeeliste.php` | Direkter URL-Aufruf kann Aktionen erlauben | Jede Seite serverseitig mit `requireRole` absichern |
|
||||||
| Keine CSRF-Token | nahezu alle POST-/Delete-Formulare | Ungewollte Buchungen, Loeschungen, Imports | CSRF fuer alle schreibenden Aktionen |
|
| CSRF-Schutz nur teilweise vorhanden | viele POST-/Delete-Formulare; M2 hat `hinweise.php`, `mitarbeiterverwalten.php`, `namenanpassen.php` abgesichert | Ungewollte Buchungen, Loeschungen, Imports | CSRF fuer alle verbleibenden schreibenden Aktionen |
|
||||||
| Harte Deletes fuer Buchungen | `letzteneintraege.php` | Audit-Historie und Revisionsfaehigkeit gehen verloren | Storno-/Reversal-Modell statt Delete |
|
| Harte Deletes fuer Buchungen | `letzteneintraege.php` | Audit-Historie und Revisionsfaehigkeit gehen verloren | Storno-/Reversal-Modell statt Delete |
|
||||||
| Upload in Webroot | `csvupload.php` Zeilen 131-138 | Dateiablage kann missbraucht werden | Upload ausserhalb Webroot, Dateityp/MIME/Name pruefen |
|
| Upload in Webroot | `csvupload.php` Zeilen 131-138 | Dateiablage kann missbraucht werden | Upload ausserhalb Webroot, Dateityp/MIME/Name pruefen |
|
||||||
| Kein Tenant-Scope | alle fachlichen Queries | Zentrales SaaS-Leak-Risiko | `tenant_id` verpflichtend und Query-Schicht testen |
|
| Kein Tenant-Scope | alle fachlichen Queries | Zentrales SaaS-Leak-Risiko | `tenant_id` verpflichtend und Query-Schicht testen |
|
||||||
@@ -52,7 +52,7 @@ Fuer SaaS gilt:
|
|||||||
|
|
||||||
1. Secrets rotieren und aus dem Code entfernen.
|
1. Secrets rotieren und aus dem Code entfernen.
|
||||||
2. Legacy-Schreibseiten bis zum Umbau hinter explizite Admin-Pruefung setzen.
|
2. Legacy-Schreibseiten bis zum Umbau hinter explizite Admin-Pruefung setzen.
|
||||||
3. CSRF-Schutz in der neuen App-Basis einplanen.
|
3. CSRF-Schutz schrittweise auf alle verbleibenden Legacy-Schreibseiten ausrollen.
|
||||||
4. Finanzdaten im Zielmodell nur noch stornieren, nicht loeschen.
|
4. Finanzdaten im Zielmodell nur noch stornieren, nicht loeschen.
|
||||||
5. Uploads ausserhalb des Webroots modellieren.
|
5. Uploads ausserhalb des Webroots modellieren.
|
||||||
6. Einheitliche Escape-/View-Helfer einfuehren.
|
6. Einheitliche Escape-/View-Helfer einfuehren.
|
||||||
|
|||||||
@@ -35,6 +35,23 @@ Legacy-Verhalten oder das bestehende Design zu veraendern.
|
|||||||
werden spaeter einzeln umgestellt, damit keine Formulare oder Spezialflows
|
werden spaeter einzeln umgestellt, damit keine Formulare oder Spezialflows
|
||||||
brechen.
|
brechen.
|
||||||
|
|
||||||
|
### CSRF-Rollout
|
||||||
|
|
||||||
|
Erste Legacy-POST-Seiten sind opt-in abgesichert:
|
||||||
|
|
||||||
|
- `hinweise.php`: Hinweis anlegen und Hinweis loeschen. Der bisherige
|
||||||
|
GET-Loeschlink wurde durch ein POST-Formular mit CSRF-Token ersetzt.
|
||||||
|
- `mitarbeiterverwalten.php`: Mitglied anlegen, Bearbeitungsformular oeffnen,
|
||||||
|
Mitglied speichern, aktivieren und deaktivieren.
|
||||||
|
- `namenanpassen.php`: Anzeigenamen aktualisieren.
|
||||||
|
|
||||||
|
Noch offen:
|
||||||
|
|
||||||
|
- Buchungsflows: `index.php`, `stricheintragen.php`, `einzahlung.php`.
|
||||||
|
- Korrektur-/Loeschflows: `letzteneintraege.php`.
|
||||||
|
- Uploads: `csvupload.php`.
|
||||||
|
- Spezialprozesse: `mailversenden.php`, `jahresauswertung.php`.
|
||||||
|
|
||||||
### Migrationen
|
### Migrationen
|
||||||
|
|
||||||
- `database/migrations/0001_legacy_mysql_baseline.sql` bildet die bisherige
|
- `database/migrations/0001_legacy_mysql_baseline.sql` bildet die bisherige
|
||||||
@@ -82,21 +99,28 @@ Die Skripte erwarten die bekannten Dev-Umgebungsvariablen `DB_HOST`, `DB_NAME`,
|
|||||||
- PHP-Syntax fuer Bootstrap, Migrationen, Init-Skript und Config ist sauber.
|
- PHP-Syntax fuer Bootstrap, Migrationen, Init-Skript und Config ist sauber.
|
||||||
- Session-Start laeuft in der lokalen Dev-Umgebung ohne PHP-Warnings ueber
|
- Session-Start laeuft in der lokalen Dev-Umgebung ohne PHP-Warnings ueber
|
||||||
`var/sessions`.
|
`var/sessions`.
|
||||||
|
- CSRF negative Tests: `hinweise.php`, `mitarbeiterverwalten.php` und
|
||||||
|
`namenanpassen.php` liefern bei POST ohne Token HTTP 419.
|
||||||
|
- CSRF positive Tests: gueltige Token funktionieren fuer Hinweis-Anlage,
|
||||||
|
Mitglieder-Bearbeitungsformular und Namensanpassung. Der temporaere
|
||||||
|
Testhinweis wurde wieder entfernt.
|
||||||
- Golden Master weiterhin gruen mit 104 Assertions.
|
- Golden Master weiterhin gruen mit 104 Assertions.
|
||||||
- HTTP-Smoke weiterhin gruen mit 14 sicheren Seiten.
|
- HTTP-Smoke weiterhin gruen mit 14 sicheren Seiten.
|
||||||
|
|
||||||
## Bewusste Grenzen
|
## Bewusste Grenzen
|
||||||
|
|
||||||
- Keine Tenant-/User-/Rollen-Tabellen in M2. Diese gehoeren zu M3.
|
- Keine Tenant-/User-/Rollen-Tabellen in M2. Diese gehoeren zu M3.
|
||||||
- Keine globale CSRF-Erzwingung in M2. Die Absicherung der POST-Seiten erfolgt
|
- Keine globale CSRF-Erzwingung in M2. Die Absicherung weiterer POST-Seiten
|
||||||
schrittweise.
|
erfolgt schrittweise.
|
||||||
- Kein Layout-Umbau in M2. Die visuelle Struktur bleibt stabil.
|
- Kein Layout-Umbau in M2. Die visuelle Struktur bleibt stabil.
|
||||||
- PDF-/Mail-/Jahresprozesse bleiben als M6-Themen offen.
|
- PDF-/Mail-/Jahresprozesse bleiben als M6-Themen offen.
|
||||||
|
|
||||||
## Naechste Schritte
|
## Naechste Schritte
|
||||||
|
|
||||||
1. POST-Seiten einzeln mit `app_csrf_field()` und `app_require_csrf()`
|
1. Buchungs- und Korrektur-POST-Seiten einzeln mit `app_csrf_field()` und
|
||||||
absichern.
|
`app_require_csrf()` absichern.
|
||||||
2. Eine duenne View-/Layout-Struktur vorbereiten, ohne Header/Footer-Markup
|
2. CSV-Upload gesondert absichern und spaeter Uploads ausserhalb des Webroots
|
||||||
|
verlegen.
|
||||||
|
3. Eine duenne View-/Layout-Struktur vorbereiten, ohne Header/Footer-Markup
|
||||||
sofort zu verschieben.
|
sofort zu verschieben.
|
||||||
3. Danach M3 starten: Tenants, User, Registrierung, Login und Rollen.
|
4. Danach M3 starten: Tenants, User, Registrierung, Login und Rollen.
|
||||||
|
|||||||
@@ -381,7 +381,9 @@ Schritte:
|
|||||||
bleiben vorerst Legacy-Wrapper.
|
bleiben vorerst Legacy-Wrapper.
|
||||||
- Bestehende Assets weiterverwenden.
|
- Bestehende Assets weiterverwenden.
|
||||||
- CSRF- und Session-Basis einziehen. Session und CSRF-Helper sind vorhanden;
|
- CSRF- und Session-Basis einziehen. Session und CSRF-Helper sind vorhanden;
|
||||||
globale Erzwingung erfolgt schrittweise pro POST-Seite.
|
globale Erzwingung erfolgt schrittweise pro POST-Seite. Erste Seiten sind
|
||||||
|
abgesichert: `hinweise.php`, `mitarbeiterverwalten.php`,
|
||||||
|
`namenanpassen.php`.
|
||||||
- Konfigurationswerte aus Code in Umgebung oder Settings verschieben.
|
- Konfigurationswerte aus Code in Umgebung oder Settings verschieben.
|
||||||
|
|
||||||
Ergebnis:
|
Ergebnis:
|
||||||
|
|||||||
+18
-9
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
include "functions.php";
|
include "functions.php";
|
||||||
|
app_require_csrf();
|
||||||
include "header.php";
|
include "header.php";
|
||||||
include "headerline.php";
|
include "headerline.php";
|
||||||
include "nav.php";
|
include "nav.php";
|
||||||
@@ -20,17 +21,17 @@ if(checkKaffeelisteAdmin($conn, $mailadress)){
|
|||||||
|
|
||||||
echo "<h2>Kaffeeliste - Hinweise</h2>";
|
echo "<h2>Kaffeeliste - Hinweise</h2>";
|
||||||
|
|
||||||
// Hinweis löschen
|
// Hinweis speichern oder löschen
|
||||||
if (isset($_GET['delete'])) {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
$id = (int)$_GET['delete'];
|
$aktion = $_POST['aktion'] ?? 'speichern';
|
||||||
|
|
||||||
|
if ($aktion === 'loeschen') {
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
if ($id > 0) {
|
||||||
$stmt = sqlsrv_query($conn, "DELETE FROM kl_hinweise WHERE id = ?", [$id]);
|
$stmt = sqlsrv_query($conn, "DELETE FROM kl_hinweise WHERE id = ?", [$id]);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
// Hinweis speichern
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
$nachricht = $_POST['nachricht'];
|
$nachricht = $_POST['nachricht'];
|
||||||
|
|
||||||
|
|
||||||
$gueltig_bis = $_POST['gueltig_bis']; // z.B. "2025-09-03T14:00"
|
$gueltig_bis = $_POST['gueltig_bis']; // z.B. "2025-09-03T14:00"
|
||||||
$dt = DateTime::createFromFormat('Y-m-d\TH:i', $gueltig_bis);
|
$dt = DateTime::createFromFormat('Y-m-d\TH:i', $gueltig_bis);
|
||||||
|
|
||||||
@@ -49,6 +50,7 @@ if(checkKaffeelisteAdmin($conn, $mailadress)){
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Hinweise abrufen
|
// Hinweise abrufen
|
||||||
$hinweise = [];
|
$hinweise = [];
|
||||||
@@ -61,6 +63,8 @@ if(checkKaffeelisteAdmin($conn, $mailadress)){
|
|||||||
|
|
||||||
<h2>Neuen Hinweis hinzufügen</h2>
|
<h2>Neuen Hinweis hinzufügen</h2>
|
||||||
<form method="post">
|
<form method="post">
|
||||||
|
<input type="hidden" name="aktion" value="speichern">
|
||||||
|
<?php echo app_csrf_field(); ?>
|
||||||
<label>Nachricht:</label><br>
|
<label>Nachricht:</label><br>
|
||||||
<textarea name="nachricht" required></textarea><br><br>
|
<textarea name="nachricht" required></textarea><br><br>
|
||||||
<label>Gültig bis:</label><br>
|
<label>Gültig bis:</label><br>
|
||||||
@@ -73,7 +77,12 @@ if(checkKaffeelisteAdmin($conn, $mailadress)){
|
|||||||
<div class="hinweis">
|
<div class="hinweis">
|
||||||
<strong><?= htmlspecialchars($hinweis['nachricht']) ?></strong><br>
|
<strong><?= htmlspecialchars($hinweis['nachricht']) ?></strong><br>
|
||||||
<small>Gültig bis: <?= $hinweis['gueltig_bis']->format('d.m.Y H:i') ?></small><br>
|
<small>Gültig bis: <?= $hinweis['gueltig_bis']->format('d.m.Y H:i') ?></small><br>
|
||||||
<a href="?delete=<?= $hinweis['id'] ?>" onclick="return confirm('Diesen Hinweis wirklich löschen?')">🗑️ Löschen</a>
|
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" onsubmit="return confirm('Diesen Hinweis wirklich löschen?')">
|
||||||
|
<input type="hidden" name="aktion" value="loeschen">
|
||||||
|
<input type="hidden" name="id" value="<?= (int)$hinweis['id'] ?>">
|
||||||
|
<?php echo app_csrf_field(); ?>
|
||||||
|
<button type="submit">Löschen</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
|
|
||||||
include "functions.php";
|
include "functions.php";
|
||||||
|
app_require_csrf();
|
||||||
include "header.php";
|
include "header.php";
|
||||||
include "headerline.php";
|
include "headerline.php";
|
||||||
include "nav.php";
|
include "nav.php";
|
||||||
@@ -69,6 +70,7 @@ if(checkKaffeelisteAdmin($conn, $mailadress)){
|
|||||||
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
|
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
|
||||||
<input type="hidden" name="aktion" value="bearbeitenspeichern">
|
<input type="hidden" name="aktion" value="bearbeitenspeichern">
|
||||||
<input type="hidden" name="mitgliedID" value="<?php echo $mitgliedID; ?>">
|
<input type="hidden" name="mitgliedID" value="<?php echo $mitgliedID; ?>">
|
||||||
|
<?php echo app_csrf_field(); ?>
|
||||||
|
|
||||||
<label for="name">Name:</label>
|
<label for="name">Name:</label>
|
||||||
<input type="text" name="name" id="name" value="<?php echo $einzelmitglied['Name']; ?>" required>
|
<input type="text" name="name" id="name" value="<?php echo $einzelmitglied['Name']; ?>" required>
|
||||||
@@ -157,6 +159,7 @@ if(checkKaffeelisteAdmin($conn, $mailadress)){
|
|||||||
<!-- Formular für das Anlegen und Bearbeiten von Mitgliedern -->
|
<!-- Formular für das Anlegen und Bearbeiten von Mitgliedern -->
|
||||||
<form method="post" action="<?php # echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
|
<form method="post" action="<?php # echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
|
||||||
<input type="hidden" name="aktion" id="aktion" value="anlegen">
|
<input type="hidden" name="aktion" id="aktion" value="anlegen">
|
||||||
|
<?php echo app_csrf_field(); ?>
|
||||||
|
|
||||||
<label for="name">Name:</label>
|
<label for="name">Name:</label>
|
||||||
<input type="text" name="name" id="name" required>
|
<input type="text" name="name" id="name" required>
|
||||||
@@ -210,6 +213,7 @@ if(checkKaffeelisteAdmin($conn, $mailadress)){
|
|||||||
echo "<form method='post' action='{$_SERVER["PHP_SELF"]}'>";
|
echo "<form method='post' action='{$_SERVER["PHP_SELF"]}'>";
|
||||||
echo "<input type='hidden' name='aktion' value='bearbeiten'>";
|
echo "<input type='hidden' name='aktion' value='bearbeiten'>";
|
||||||
echo "<input type='hidden' name='mitgliedID' value='{$row['MitarbeiterID']}'>";
|
echo "<input type='hidden' name='mitgliedID' value='{$row['MitarbeiterID']}'>";
|
||||||
|
echo app_csrf_field();
|
||||||
echo "<button type='submit'>Bearbeiten</button>";
|
echo "<button type='submit'>Bearbeiten</button>";
|
||||||
echo "</form></li><li>";
|
echo "</form></li><li>";
|
||||||
|
|
||||||
@@ -218,12 +222,14 @@ if(checkKaffeelisteAdmin($conn, $mailadress)){
|
|||||||
echo "<form method='post' action='{$_SERVER["PHP_SELF"]}'>";
|
echo "<form method='post' action='{$_SERVER["PHP_SELF"]}'>";
|
||||||
echo "<input type='hidden' name='aktion' value='deaktivieren'>";
|
echo "<input type='hidden' name='aktion' value='deaktivieren'>";
|
||||||
echo "<input type='hidden' name='mitgliedID' value='{$row['MitarbeiterID']}'>";
|
echo "<input type='hidden' name='mitgliedID' value='{$row['MitarbeiterID']}'>";
|
||||||
|
echo app_csrf_field();
|
||||||
echo "<button type='submit'>Deaktivieren</button>";
|
echo "<button type='submit'>Deaktivieren</button>";
|
||||||
echo "</form>";
|
echo "</form>";
|
||||||
} else {
|
} else {
|
||||||
echo "<form method='post' action='{$_SERVER["PHP_SELF"]}'>";
|
echo "<form method='post' action='{$_SERVER["PHP_SELF"]}'>";
|
||||||
echo "<input type='hidden' name='aktion' value='aktivieren'>";
|
echo "<input type='hidden' name='aktion' value='aktivieren'>";
|
||||||
echo "<input type='hidden' name='mitgliedID' value='{$row['MitarbeiterID']}'>";
|
echo "<input type='hidden' name='mitgliedID' value='{$row['MitarbeiterID']}'>";
|
||||||
|
echo app_csrf_field();
|
||||||
echo "<button type='submit'>Aktivieren</button>";
|
echo "<button type='submit'>Aktivieren</button>";
|
||||||
echo "</form>";
|
echo "</form>";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
|
|
||||||
include "functions.php";
|
include "functions.php";
|
||||||
|
app_require_csrf();
|
||||||
include "header.php";
|
include "header.php";
|
||||||
include "headerline.php";
|
include "headerline.php";
|
||||||
include "nav.php";
|
include "nav.php";
|
||||||
@@ -84,6 +85,7 @@ if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST["aktion"]) && $_POST["a
|
|||||||
|
|
||||||
|
|
||||||
<form method="post" action="<?php echo $_SERVER["PHP_SELF"]; ?>">
|
<form method="post" action="<?php echo $_SERVER["PHP_SELF"]; ?>">
|
||||||
|
<?php echo app_csrf_field(); ?>
|
||||||
<label for="mitarbeiterID">Mitarbeiter auswählen:</label><br>
|
<label for="mitarbeiterID">Mitarbeiter auswählen:</label><br>
|
||||||
<select name="mitarbeiterID" id="mitarbeiterID">
|
<select name="mitarbeiterID" id="mitarbeiterID">
|
||||||
<?php
|
<?php
|
||||||
|
|||||||
Reference in New Issue
Block a user