// VERBOTEN - Unsicher!
Exec('cmd /c ' + UserInput);
Shell('fpc ' + ProjectPath + ' ' + UserArgs);
// KORREKT - Parameterisiert
Options.Shell := False;
Args := TStringArray.Create;
Args.Add(ProjectPath);
Spawn('fpc', Args, Options);
**Maßnahmen:**
* Immer ''shell: false'' verwenden
* Argumente als Array übergeben
* Pfade validieren vor Verwendung
==== CWE-22: Path Traversal ====
**Risiko:** Zugriff auf Dateien außerhalb des erlaubten Verzeichnisses.
// VERBOTEN - Unsicher!
FileName := BasePath + UserInput;
ReadFile(FileName);
// KORREKT - Validierung
function IsPathSafe(const ABasePath, AUserInput: string): Boolean;
var
ResolvedPath: string;
begin
// Keine .. erlauben
if Pos('..', AUserInput) > 0 then
Exit(False);
ResolvedPath := ExpandFileName(Concat(ABasePath, AUserInput));
Result := Pos(ABasePath, ResolvedPath) = 1;
end;
if IsPathSafe(BasePath, UserInput) then
ReadFile(Concat(BasePath, UserInput));
**Maßnahmen:**
* ''..'' Sequenzen blockieren
* Pfade normalisieren und validieren
* Basis-Pfad muß Präfix des Ergebnis-Pfads sein
==== CWE-20: Input Validation ====
**Risiko:** Ungültige Daten führen zu Fehlfunktion oder Angriff.
// VERBOTEN - Keine Validierung
procedure CreateProject(const AName: string);
begin
MakeDir(AName); // Was wenn AName = '../../../etc'?
end;
// KORREKT - Validierung
function ValidateProjectName(const AName: string; out AError: string): Boolean;
const
ALLOWED_CHARS = ['a'..'z', 'A'..'Z', '0'..'9', '_', '-'];
MAX_LENGTH = 64;
var
I: Integer;
begin
Result := False;
if AName = '' then
begin
AError := rsProjectNameEmpty;
Exit;
end;
if Length(AName) > MAX_LENGTH then
begin
AError := Format(rsProjectNameTooLong, [MAX_LENGTH]);
Exit;
end;
for I := 1 to Length(AName) do
if not (AName[I] in ALLOWED_CHARS) then
begin
AError := Format(rsProjectNameInvalidChar, [AName[I]]);
Exit;
end;
Result := True;
end;
==== CWE-316: Cleartext Credentials ====
**Risiko:** Credentials im Code oder in Logs.
// VERBOTEN - Credentials im Code
const
API_TOKEN = 'ghp_xxxxxxxxxxxx';
DB_PASSWORD = 'secret123';
// VERBOTEN - Credentials loggen
LogDebug('Token: %s', [Token]);
// KORREKT - Environment Variables
Token := GetEnvironmentVariable('GITHUB_TOKEN');
if Token = '' then
raise EWvdSConfigError.Create(rsTokenNotConfigured);
// KORREKT - Maskiertes Logging
LogDebug('Token configured: %s', [BoolToStr(Token <> '', True)]);
==== CWE-532: Log Injection ====
**Risiko:** Sensible Daten in Logs sichtbar.
// VERBOTEN
LogInfo('User login: %s with password: %s', [User, Password]);
LogDebug('API response: %s', [FullResponse]); // Kann Tokens enthalten!
// KORREKT
LogInfo('User login: %s', [User]); // Kein Password
LogDebug('API response received, length: %d', [Length(Response)]);
==== CWE-79: Cross-Site Scripting (XSS) ====
**Risiko:** Einschleusen von Script-Code in WebViews.
// VERBOTEN - Unescaped HTML
WebView.Html := '' + UserInput + '';
// KORREKT - Escaping
function EscapeHtml(const AText: string): string;
begin
Result := AText;
Result := StringReplace(Result, '&', '&', [rfReplaceAll]);
Result := StringReplace(Result, '<', '<', [rfReplaceAll]);
Result := StringReplace(Result, '>', '>', [rfReplaceAll]);
Result := StringReplace(Result, '"', '"', [rfReplaceAll]);
Result := StringReplace(Result, '''', ''', [rfReplaceAll]);
end;
WebView.Html := '' + EscapeHtml(UserInput) + '';
==== CWE-20: WebView Message Whitelist ====
**Risiko:** Unbekannte Message-Typen ermöglichen Angriffe auf WebView-Handler.
// VERBOTEN - Kein Message-Type-Check
procedure HandleMessage(AMessage: TJSObject);
var
MsgType: string;
begin
MsgType := string(AMessage['type']);
case MsgType of
'browse': HandleBrowse(AMessage);
'save': HandleSave(AMessage);
end;
end;
// KORREKT - Mit Whitelist
const
ALLOWED_MESSAGE_TYPES: array[0..6] of string = (
'browse', 'save', 'cancel', 'validatePath',
'autoDetect', 'config', 'pathSelected'
);
function IsAllowedMessageType(const AType: string): Boolean;
var
I: Integer;
begin
Result := False;
for I := Low(ALLOWED_MESSAGE_TYPES) to High(ALLOWED_MESSAGE_TYPES) do
if ALLOWED_MESSAGE_TYPES[I] = AType then
Exit(True);
end;
procedure HandleMessage(AMessage: TJSObject);
var
MsgType: string;
begin
MsgType := string(AMessage['type']);
// Whitelist-Prüfung ZUERST
if not IsAllowedMessageType(MsgType) then
begin
LogWarning(rsUnknownMessageType, [MsgType]);
Exit;
end;
case MsgType of
'browse': HandleBrowse(AMessage);
'save': HandleSave(AMessage);
// ...
end;
end;
**Maßnahmen:**
* Whitelist mit allen erlaubten Message-Typen definieren
* Unbekannte Typen loggen und ablehnen
* Bei neuen Funktionen Whitelist aktualisieren
==== Defensive Node.js API-Aufrufe ====
**Risiko:** Node.js APIs können je nach Kontext unterschiedliche Methoden haben.
// VERBOTEN - Direkte Methodenaufrufe ohne Check
procedure SetupProcessHandlers;
begin
asm
this.FProcess.stdout.setEncoding('utf8');
this.FProcess.stderr.setEncoding('utf8');
end;
end;
// KORREKT - Defensive typeof-Checks
procedure SetupProcessHandlers;
begin
asm
if (this.FProcess && this.FProcess.stdout) {
if (typeof this.FProcess.stdout.setEncoding === 'function') {
this.FProcess.stdout.setEncoding('utf8');
}
if (typeof this.FProcess.stdout.on === 'function') {
this.FProcess.stdout.on('data', this.HandleStdout.bind(this));
}
}
if (this.FProcess && this.FProcess.stderr) {
if (typeof this.FProcess.stderr.setEncoding === 'function') {
this.FProcess.stderr.setEncoding('utf8');
}
if (typeof this.FProcess.stderr.on === 'function') {
this.FProcess.stderr.on('data', this.HandleStderr.bind(this));
}
}
end;
end;
**Maßnahmen:**
* ''typeof ... === 'function''' vor jedem Methodenaufruf
* Objektexistenz prüfen (''if (obj && obj.property)'')
* Fehlende APIs graceful behandeln
===== Error Handling =====
==== Keine leeren Exception-Handler ====
// VERBOTEN
try
DoSomething;
except
// Fehler verschlucken
end;
// KORREKT
try
DoSomething;
except
on E: ESpecificError do
begin
LogError(rsSpecificError, [E.Message]);
// Behandlung...
end;
on E: Exception do
begin
LogError(rsUnexpectedError, [E.ClassName, E.Message]);
raise; // Oder angemessen behandeln
end;
end;
==== Spezifische Exception-Typen ====
// KORREKT - Spezifische Exceptions
try
Result := DoOperation;
except
on E: EFileNotFoundException do
HandleFileNotFound(E.FileName);
on E: EAccessDenied do
HandleAccessDenied(E.Path);
on E: ENetworkError do
HandleNetworkError(E.Url);
on E: Exception do
HandleUnexpectedError(E);
end;
===== Debug-Logging =====
Debug-Logging erfordert **zwei Bedingungen**:
- **Compile-Zeit:** ''{$IFDEF DEBUG}''
- **Laufzeit:** ''--debug'' Parameter
{$IFDEF DEBUG}
var
DebugLogFile: TextFile;
DebugEnabled: Boolean;
procedure InitDebugLogging;
begin
DebugEnabled := ParamStr(1) = '--debug';
if DebugEnabled then
begin
AssignFile(DebugLogFile, Format('debug-%s.log',
[FormatDateTime('yymmddhhnnss', Now)]));
Rewrite(DebugLogFile);
end;
end;
procedure LogDebugTrace(const AMessage: string; const AArgs: array of const);
begin
if not DebugEnabled then Exit;
WriteLn(DebugLogFile, Format('[%s] %s',
[FormatDateTime('hh:nn:ss.zzz', Now), Format(AMessage, AArgs)]));
Flush(DebugLogFile);
end;
{$ENDIF}
==== Was darf geloggt werden ====
^ Erlaubt ^ Verboten ^
| Dateinamen, Pfade | Tokens, API-Keys |
| Aktionsnamen | Paßwörter |
| Numerische IDs | Session-IDs |
| Fehlermeldungen | Vollständige Requests/Responses |
| Konfigurationskeys | Konfigurationswerte (sensibel) |
===== Sichere Defaults =====
// Sichere Standardwerte
const
DEFAULT_TIMEOUT = 30000; // 30 Sekunden
MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
MAX_RECURSION_DEPTH = 100;
type
TSpawnOptions = record
Shell: Boolean; // Default: False (sicher)
Timeout: Integer; // Default: 30000
WorkingDir: string;
Environment: TStringArray;
end;
function DefaultSpawnOptions: TSpawnOptions;
begin
Result.Shell := False; // WICHTIG: Kein Shell!
Result.Timeout := DEFAULT_TIMEOUT;
Result.WorkingDir := '';
Result.Environment := nil;
end;
===== Code Review Checkliste =====
Vor Merge MUSS geprüft werden:
SICHERHEIT:
[ ] Keine hardcodierten Credentials
[ ] Alle Benutzereingaben validiert
[ ] Keine Shell-Injection möglich
[ ] Keine Path-Traversal möglich
[ ] HTML wird escaped in WebViews
[ ] Keine sensiblen Daten in Logs
ERROR HANDLING:
[ ] Keine leeren Exception-Handler
[ ] Spezifische Exceptions behandelt
[ ] Fehler werden geloggt (ohne sensible Daten)
KONFIGURATION:
[ ] Sichere Defaults verwendet
[ ] Timeouts definiert
[ ] Limits definiert (Dateigröße, Rekursion)
===== Sicherheits-Audit ====
Regelmäßige Prüfung mit automatisierten Tools:
==== wvds-lint Sicherheits-Checks ====
wvds-lint security --path sources/
Geprüft wird:
* Exec/Shell-Aufrufe
* Path-Operationen ohne Validierung
* Hardcodierte Strings (potentielle Credentials)
* Leere Exception-Handler
* Log-Aufrufe mit sensiblen Parametern
==== Manuelle Review ====
Bei jeder neuen Extension:
* Alle Entry-Points prüfen
* Alle externen Inputs tracen
* WebView-Kommunikation prüfen
===== Incident Response =====
Bei Entdeckung einer Sicherheitslücke:
- **Nicht veröffentlichen** bis Patch bereit
- Issue auf GitHub mit Label "security" (privat)
- Patch entwickeln und testen
- Neue Version releasen
- Advisory veröffentlichen
===== Siehe auch =====
* [[.:code-konventionen|Code-Konventionen]]
* [[.:debugging|Debugging]]
* [[.:testing|Testing]]
* [[https://owasp.org/www-project-top-ten/|OWASP Top 10]]