Inhaltsverzeichnis

Sigurnosne smjernice

Obavezni sigurnosni standardi za WvdS FPC RAD Suite, sukladni s KRITIS/NIS2.

Ove smjernice su nepregovarajuće. Kod koji ih krši neće biti prihvaćen.

OWASP Top 10 Prevencija

CWE-78: Command Injection

Rizik: Izvršavanje proizvoljnih naredbi putem manipuliranog inputa.

// ZABRANJENO - Nesigurno!
Exec('cmd /c ' + UserInput);
Shell('fpc ' + ProjectPath + ' ' + UserArgs);
 
// ISPRAVNO - Parametrizirano
Options.Shell := False;
Args := TStringArray.Create;
Args.Add(ProjectPath);
Spawn('fpc', Args, Options);

Mjere:

CWE-22: Path Traversal

Rizik: Pristup datotekama izvan dozvoljenog direktorija.

// ZABRANJENO - Nesigurno!
FileName := BasePath + UserInput;
ReadFile(FileName);
 
// ISPRAVNO - Validacija
function IsPathSafe(const ABasePath, AUserInput: string): Boolean;
var
  ResolvedPath: string;
begin
  // Ne dozvoliti ..
  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));

Mjere:

CWE-20: Input Validation

Rizik: Nevaljani podaci dovode do neispravnog rada ili napada.

// ZABRANJENO - Bez validacije
procedure CreateProject(const AName: string);
begin
  MakeDir(AName);  // Što ako AName = '../../../etc'?
end;
 
// ISPRAVNO - Validacija
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

Rizik: Credentials u kodu ili u logovima.

// ZABRANJENO - Credentials u kodu
const
  API_TOKEN = 'ghp_xxxxxxxxxxxx';
  DB_PASSWORD = 'secret123';
 
// ZABRANJENO - Credentials logirati
LogDebug('Token: %s', [Token]);
 
// ISPRAVNO - Environment Variables
Token := GetEnvironmentVariable('GITHUB_TOKEN');
if Token = '' then
  raise EWvdSConfigError.Create(rsTokenNotConfigured);
 
// ISPRAVNO - Maskirano logiranje
LogDebug('Token configured: %s', [BoolToStr(Token <> '', True)]);

CWE-532: Log Injection

Rizik: Osjetljivi podaci vidljivi u logovima.

// ZABRANJENO
LogInfo('User login: %s with password: %s', [User, Password]);
LogDebug('API response: %s', [FullResponse]);  // Može sadržavati tokene!
 
// ISPRAVNO
LogInfo('User login: %s', [User]);  // Bez lozinke
LogDebug('API response received, length: %d', [Length(Response)]);

CWE-79: Cross-Site Scripting (XSS)

Rizik: Ubacivanje Script-koda u WebViews.

// ZABRANJENO - Unescaped HTML
WebView.Html := '<div>' + UserInput + '</div>';
 
// ISPRAVNO - Escaping
function EscapeHtml(const AText: string): string;
begin
  Result := AText;
  Result := StringReplace(Result, '&', '&amp;', [rfReplaceAll]);
  Result := StringReplace(Result, '<', '&lt;', [rfReplaceAll]);
  Result := StringReplace(Result, '>', '&gt;', [rfReplaceAll]);
  Result := StringReplace(Result, '"', '&quot;', [rfReplaceAll]);
  Result := StringReplace(Result, '''', '&#39;', [rfReplaceAll]);
end;
 
WebView.Html := '<div>' + EscapeHtml(UserInput) + '</div>';

CWE-20: WebView Message Whitelist

Rizik: Nepoznati Message-tipovi omogućuju napade na WebView-handlere.

Svaki WebView-Message-Handler MORA implementirati whitelistu dozvoljenih Message-tipova.
// ZABRANJENO - Bez provjere Message-Type
procedure HandleMessage(AMessage: TJSObject);
var
  MsgType: string;
begin
  MsgType := string(AMessage['type']);
  case MsgType of
    'browse': HandleBrowse(AMessage);
    'save': HandleSave(AMessage);
  end;
end;
 
// ISPRAVNO - S Whitelistom
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']);
 
  // Provjera Whiteliste PRVO
  if not IsAllowedMessageType(MsgType) then
  begin
    LogWarning(rsUnknownMessageType, [MsgType]);
    Exit;
  end;
 
  case MsgType of
    'browse': HandleBrowse(AMessage);
    'save': HandleSave(AMessage);
    // ...
  end;
end;

Mjere:

Defenzivni Node.js API-pozivi

Rizik: Node.js APIs mogu imati različite metode ovisno o kontekstu.

Problem iz prakse: stdout.setEncoding nije funkcija ako Process nije ispravno inicijaliziran.
// ZABRANJENO - Direktni pozivi metoda bez provjere
procedure SetupProcessHandlers;
begin
  asm
    this.FProcess.stdout.setEncoding('utf8');
    this.FProcess.stderr.setEncoding('utf8');
  end;
end;
 
// ISPRAVNO - Defenzivne typeof-provjere
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;

Mjere:

Error Handling

Bez praznih Exception-Handlera

// ZABRANJENO
try
  DoSomething;
except
  // Gutanje greške
end;
 
// ISPRAVNO
try
  DoSomething;
except
  on E: ESpecificError do
  begin
    LogError(rsSpecificError, [E.Message]);
    // Obrada...
  end;
  on E: Exception do
  begin
    LogError(rsUnexpectedError, [E.ClassName, E.Message]);
    raise;  // Ili odgovarajuće obraditi
  end;
end;

Specifični Exception-tipovi

// ISPRAVNO - Specifične 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 zahtijeva dva uvjeta:

  1. Compile-Time: {$IFDEF DEBUG}
  2. Runtime: –debug Parametar
{$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}

Što smije biti logirano

Dozvoljeno Zabranjeno
Nazivi datoteka, putanje Tokeni, API-Ključevi
Nazivi akcija Lozinke
Numerički ID-evi Session-ID-evi
Poruke o greškama Potpuni Requests/Responses
Konfiguracijski ključevi Konfiguracijske vrijednosti (osjetljive)

Sigurne Default vrijednosti

// Sigurne standardne vrijednosti
const
  DEFAULT_TIMEOUT = 30000;  // 30 sekundi
  MAX_FILE_SIZE = 10 * 1024 * 1024;  // 10 MB
  MAX_RECURSION_DEPTH = 100;
 
type
  TSpawnOptions = record
    Shell: Boolean;        // Default: False (sigurno)
    Timeout: Integer;      // Default: 30000
    WorkingDir: string;
    Environment: TStringArray;
  end;
 
function DefaultSpawnOptions: TSpawnOptions;
begin
  Result.Shell := False;  // VAŽNO: Bez Shell-a!
  Result.Timeout := DEFAULT_TIMEOUT;
  Result.WorkingDir := '';
  Result.Environment := nil;
end;

Code Review Checklista

Prije Mergea MORA se provjeriti:

SIGURNOST:
[ ] Nema hardkodiranih Credentials
[ ] Svi korisnički unosi validirani
[ ] Shell-Injection nije moguć
[ ] Path-Traversal nije moguć
[ ] HTML se escapea u WebViews
[ ] Nema osjetljivih podataka u logovima

ERROR HANDLING:
[ ] Nema praznih Exception-handlera
[ ] Specifične Exceptions obrađene
[ ] Greške se logiraju (bez osjetljivih podataka)

KONFIGURACIJA:
[ ] Sigurne Default vrijednosti korištene
[ ] Timeouts definirani
[ ] Limiti definirani (veličina datoteke, rekurzija)

Sigurnosni Audit

Redovita provjera s automatiziranim alatima:

wvds-lint Sigurnosne provjere

wvds-lint security --path sources/

Provjerava se:

Ručni Review

Za svaki novi Extension:

Incident Response

Pri otkrivanju sigurnosne ranjivosti:

  1. Ne objavljivati dok Patch nije spreman
  2. Issue na GitHubu s labelom „security“ (privatno)
  3. Razviti i testirati Patch
  4. Izdati novu verziju
  5. Objaviti Advisory

Vidi također