This commit is contained in:
@@ -38,6 +38,42 @@
|
||||
{"filename":"Auftragsliste.java","content":"import java.util.*;\npublic class Auftragsliste {\n private final List<String> jobs = new ArrayList<>();\n public void add(String job) { jobs.add(job); }\n public List<String> jobs() { return jobs; }\n}"}
|
||||
],
|
||||
"bugs": [{"points":200,"file":"Auftragsliste.java","line":5,"category":"sicherheit","evidence":"Die interne, veränderbare Liste wird direkt an parallele Aufrufer zurückgegeben.","fixOptions":[" public List<String> jobs() { return List.copyOf(jobs); }"," public List<String> jobs() { return new ArrayList<>(); }"," public List<String> jobs() { return null; }"," public List<String> jobs() { jobs.clear(); return jobs; }"],"fixIndex":0,"fixedLine":" public List<String> jobs() { return List.copyOf(jobs); }","explanation":"Eine unveränderliche Momentaufnahme verhindert, dass externe Aufrufer die interne Liste gleichzeitig verändern. Für echte gemeinsame Mutation wäre zusätzlich eine threadsichere Datenstruktur nötig."}]
|
||||
},
|
||||
{
|
||||
"slug": "archiv-unter-druck", "title": "Fall #7: Archiv unter Druck", "language": "Java", "difficulty": "senior", "examScope": "AP2_FIAE",
|
||||
"description": "Der tägliche Audit-Export bricht bei einem leeren Archiv ab und die CSV-Route stürzt nur in der Nachtverarbeitung ab.", "briefing": "Das Compliance-Team benötigt den Export vor 06:00 Uhr. Zwei Fehler liegen in getrennten Dateien; der Stacktrace nennt nur den Controller.", "consoleOutput": "java.lang.NullPointerException: Cannot invoke \"String.equals(Object)\"\n at ExportController.ausliefern(ExportController.java:4)\n[warn] Audit-Archiv enthält heute keine Einträge",
|
||||
"files": [
|
||||
{"filename":"AuditExport.java","content":"import java.util.List;\n\npublic class AuditExport {\n public String export(List<String> eintraege) {\n if (eintraege == null && eintraege.isEmpty()) {\n return \"Keine Einträge\";\n }\n return String.join(\"\\n\", eintraege);\n }\n}"},
|
||||
{"filename":"ExportController.java","content":"public class ExportController {\n public String ausliefern(String format, AuditExport export, java.util.List<String> eintraege) {\n if (format.equals(\"csv\")) {\n return export.export(eintraege);\n }\n return \"Format nicht unterstützt\";\n }\n}"}
|
||||
],
|
||||
"bugs": [
|
||||
{"points":150,"file":"AuditExport.java","line":5,"category":"laufzeit","evidence":"Bei null wird rechts von && trotzdem ein Methodenaufruf ausgeführt.","fixOptions":[" if (eintraege == null || eintraege.isEmpty()) {"," if (eintraege != null && eintraege.isEmpty()) {"," if (eintraege.isEmpty() || eintraege == null) {"," if (eintraege == null & eintraege.isEmpty()) {"],"fixIndex":0,"fixedLine":" if (eintraege == null || eintraege.isEmpty()) {","explanation":"Die ODER-Verknüpfung beendet die Auswertung sofort, wenn die Liste null ist. Danach kann isEmpty sicher geprüft werden."},
|
||||
{"points":150,"file":"ExportController.java","line":3,"category":"laufzeit","evidence":"Der Stacktrace zeigt auf den Methodenaufruf equals; format kann aus einem optionalen Jobparameter stammen.","fixOptions":[" if (\"csv\".equals(format)) {"," if (format == \"csv\") {"," if (format.equalsIgnoreCase == \"csv\") {"," if (format != \"csv\") {"],"fixIndex":0,"fixedLine":" if (\"csv\".equals(format)) {","explanation":"Wird die feste Zeichenkette links verwendet, liefert equals bei einem null-Wert einfach false statt eine NullPointerException auszulösen."}
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "buchung-im-doppelpack", "title": "Fall #8: Buchung im Doppelpack", "language": "Java", "difficulty": "senior", "examScope": "AP2_FIAE",
|
||||
"description": "Nach einem Retry erscheinen Bestellungen doppelt in der Buchhaltung; zusätzlich wird eine Nullzahlung nicht erkannt.", "briefing": "Ein Zahlungsanbieter sendet einen Retry, wenn die Antwort verzögert ist. Prüfe die Idempotenz und die Geldwert-Prüfung, bevor der Monatsabschluss startet.", "consoleOutput": "[payment] retry received for order A-1042\n[ledger] duplicate reference A-1042\n[warn] zero amount accepted",
|
||||
"files": [
|
||||
{"filename":"BestellungService.java","content":"public class BestellungService {\n private final BestellungRepository repository;\n public BestellungService(BestellungRepository repository) { this.repository = repository; }\n\n public void speichern(Bestellung bestellung) {\n if (repository.existsByReferenz(bestellung.referenz())) {\n repository.save(bestellung);\n }\n }\n}"},
|
||||
{"filename":"ZahlungPruefung.java","content":"import java.math.BigDecimal;\n\npublic class ZahlungPruefung {\n static boolean istNullzahlung(BigDecimal betrag) {\n return betrag == BigDecimal.ZERO;\n }\n}"}
|
||||
],
|
||||
"bugs": [
|
||||
{"points":150,"file":"BestellungService.java","line":6,"category":"logik","evidence":"Bei einer bereits bekannten Referenz wird die Bestellung erneut gespeichert.","fixOptions":[" if (!repository.existsByReferenz(bestellung.referenz())) {"," if (repository.existsByReferenz(bestellung.referenz()) == null) {"," if (repository.existsByReferenz(bestellung.referenz()) = false) {"," if (repository.existsByReferenz(bestellung.referenz()) || true) {"],"fixIndex":0,"fixedLine":" if (!repository.existsByReferenz(bestellung.referenz())) {","explanation":"Eine Referenz dient hier als Idempotenzschlüssel. Nur unbekannte Bestellungen dürfen den Speichervorgang auslösen."},
|
||||
{"points":150,"file":"ZahlungPruefung.java","line":4,"category":"logik","evidence":"BigDecimal-Werte können denselben Betrag darstellen, ohne dass sie dasselbe Objekt sind.","fixOptions":[" return betrag != null && betrag.compareTo(BigDecimal.ZERO) == 0;"," return betrag.equals(BigDecimal.ZERO) || betrag == null;"," return betrag == new BigDecimal(\"0\");"," return betrag.toString() == \"0\";"],"fixIndex":0,"fixedLine":" return betrag != null && betrag.compareTo(BigDecimal.ZERO) == 0;","explanation":"compareTo vergleicht den numerischen Wert unabhängig von der Skala. Zusätzlich verhindert die Null-Prüfung einen Absturz."}
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "protokoll-ohne-schutz", "title": "Fall #9: Protokoll ohne Schutz", "language": "PHP", "difficulty": "principal", "examScope": "AP2_FIAE",
|
||||
"description": "Ein Security-Audit findet Zugangsdaten im Log und eine unsichere Passwortprüfung im internen API-Gateway.", "briefing": "Das Incident-Team hat einen Logauszug gesichert. Finde beide Schwachstellen, ohne die Fehlersuche selbst unbrauchbar zu machen.", "consoleOutput": "[api] Token: eyJhbGciOiJIUzI1NiJ9...\nSecurity finding: CWE-532 Sensitive Information in Log Files\nSecurity finding: CWE-256 Plaintext Storage of a Password",
|
||||
"files": [
|
||||
{"filename":"api.php","content":"<?php\nfunction requestApi(string $token, string $path): void {\n error_log('API request ' . $path . ' with token ' . $token);\n // Anfrage senden\n}\n?>"},
|
||||
{"filename":"auth.php","content":"<?php\nfunction anmelden(array $user, string $passwort): bool {\n return $user['password'] == $passwort;\n}\n?>"}
|
||||
],
|
||||
"bugs": [
|
||||
{"points":200,"file":"api.php","line":3,"category":"sicherheit","evidence":"Der vollständige Bearer-Token wird in eine dauerhaft gespeicherte Logzeile übernommen.","fixOptions":[" error_log('API request ' . $path . ' with token [redigiert]');"," error_log('API request ' . $path . ' with token ' . substr($token, 0));"," error_log($token);"," error_log(base64_encode($token));"],"fixIndex":0,"fixedLine":" error_log('API request ' . $path . ' with token [redigiert]');","explanation":"Authentifizierungsdaten gehören nie in Logs. Für die Diagnose reichen Pfad, Request-ID und ein klarer Hinweis auf einen vorhandenen Token."},
|
||||
{"points":200,"file":"auth.php","line":3,"category":"sicherheit","evidence":"Das Passwort wird direkt mit einem gespeicherten Wert verglichen; sichere Passwort-Hashes benötigen eine spezialisierte Prüfung.","fixOptions":[" return password_verify($passwort, $user['password_hash']);"," return md5($passwort) == $user['password'];"," return $user['password'] === $passwort;"," return strlen($passwort) > 0;"],"fixIndex":0,"fixedLine":" return password_verify($passwort, $user['password_hash']);","explanation":"password_verify prüft einen Passwort-Hash inklusive Salt und Algorithmus sicher. Klartextvergleiche und MD5 sind dafür ungeeignet."}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user