Tenzan Academy
Motyw
  • 8 sekcji
  • 24 pytania
  • 22 fiszki

Bezpieczeństwo aplikacji (AppSec): OWASP Top 10 w praktyce

Przegląd OWASP Top 10: wstrzyknięcia, XSS, CSRF, IDOR, SSRF, deserializacja, supply chain i hashowanie haseł, z kodem, dla inżynierów.

Bezpieczeństwo aplikacji (AppSec): OWASP Top 10 w praktyce

Przegląd OWASP Top 10: wstrzyknięcia, XSS, CSRF, IDOR, SSRF, deserializacja, supply chain i hashowanie haseł, z kodem, dla inżynierów.

Quiz

Wstrzyknięcia: SQL, Command i NoSQL Injection

na egzaminie

Wstrzyknięcie (injection) to kategoria A05 w OWASP Top 10:2025 i jeden z najgroźniejszych błędów. Dane użytkownika trafiają do interpretera (SQL, powłoki systemowej, silnika NoSQL) i wykonują się jako kod, nie jako dane.

Fakt na test

Skutek: pełny odczyt/zapis bazy, zdalne wykonanie kodu, przejęcie serwera.

Zasada nadrzędna

Parametryzowane zapytania (prepared statements) to jedyna niezawodna obrona przy SQL. Walidacja wejścia to dodatkowa warstwa, nie zamiennik.

Termin kluczowy

SQL Injection: Wstrzyknięcie fragmentu SQL przez niesparametryzowane dane wejściowe, pozwalające zmienić logikę zapytania (np. ominąć WHERE, dołączyć UNION SELECT, wywołać podzapytania administracyjne).

Termin kluczowy

Command Injection: Wstrzyknięcie poleceń powłoki systemowej przez dane trafiające do funkcji uruchamiających procesy (os.system, exec, subprocess z shell=True). Dzieje się tak, gdy polecenie jest budowane jako string zamiast listy argumentów.

Termin kluczowy

NoSQL Injection: Wstrzyknięcie operatorów zapytań (np. $where, $ne, $gt w MongoDB) przez niekontrolowane obiekty JSON, pozwalające ominąć uwierzytelnienie lub wykonać dowolny JavaScript po stronie bazy.

Pułapka

Częsty błąd: deweloper 'sparametryzował' zapytanie, ale kolumnę w ORDER BY lub nazwę tabeli wstawia przez f-string, bo 'parametrów tam nie da się użyć'. To wciąż SQL injection w innym miejscu zapytania. Rozwiązanie: whitelisting dozwolonych nazw kolumn/tabel, nigdy interpolacja.

SQL: niebezpieczne vs parametryzowane zapytanie python
# NIEBEZPIECZNE: konkatenacja stringów
query = f"SELECT * FROM users WHERE email = '{email}'"
cursor.execute(query)  # email = "' OR '1'='1" omija filtr

# BEZPIECZNE: parametryzowane zapytanie, driver escapuje wartość
cursor.execute(
    "SELECT * FROM users WHERE email = %s",
    (email,),
)
Command Injection: unikanie powłoki python
import subprocess

# NIEBEZPIECZNE: shell=True + interpolacja
subprocess.run(f"convert {filename} output.png", shell=True)

# BEZPIECZNE: lista argumentów, brak powłoki
subprocess.run(["convert", filename, "output.png"], shell=False, check=True)
NoSQL Injection: MongoDB javascript
// NIEBEZPIECZNE: obiekt z request.body trafia bezpośrednio do query
db.users.findOne({ username: req.body.username, password: req.body.password });
// atak: password = { "$ne": null } omija uwierzytelnienie

// BEZPIECZNE: wymuszenie typu string przed budową zapytania
const username = String(req.body.username);
const password = String(req.body.password);
db.users.findOne({ username, password: hash(password) });
  • Zawsze używaj parametryzowanych zapytań lub bezpiecznych metod ORM, nigdy raw query ze sklejaniem stringów
  • Konto bazodanowe aplikacji ma mieć minimalne uprawnienia (least privilege), nie prawa administratora
  • Nazwy kolumn/tabel w ORDER BY, LIMIT i podobnych miejscach: whitelist, nie interpolacja
  • Wyłącz niebezpieczne funkcje silnika bazy (np. xp_cmdshell w SQL Server)
  • Nigdy nie zwracaj surowego błędu SQL do klienta - ujawnia strukturę bazy
Mnemonik

Dane to dane, kod to kod: user input nigdy nie powinien zmieniać struktury zapytania, tylko jego wartości.

Dalej: 02 Broken Authentication i zarządzanie sesją

Broken Authentication i zarządzanie sesją

na egzaminie

Błędy uwierzytelniania i zarządzania sesją (OWASP A07: Authentication Failures) prowadzą do przejęcia konta bez znajomości hasła. Przyczyny to przewidywalne tokeny sesji, brak wygasania i brak rotacji po zmianie uprawnień.

ASVS 5.0, poziom L1

Token sesji wymaga min. 128 bitów entropii, generowany kryptograficznie bezpiecznym generatorem liczb losowych, nigdy z Math.random() ani sekwencyjnego licznika.

Termin kluczowy

Session Fixation: Atakujący narzuca ofierze znany identyfikator sesji (np. przez link z ?sessionid=). Po zalogowaniu ofiary przejmuje tę samą sesję, bo aplikacja nie regeneruje ID po uwierzytelnieniu.

Termin kluczowy

Broken Session Management: Sesje, które nie wygasają, nie są unieważniane po wylogowaniu, lub pozostają aktywne po zmianie hasła czy uprawnień użytkownika.

Pułapka

Częsty błąd: token sesji jest unieważniany tylko po stronie klienta (usunięcie ciasteczka), a serwer nadal go akceptuje. Atakujący z przechwyconym tokenem ma dostęp mimo 'wylogowania' ofiary.

Regeneracja ID sesji po logowaniu (Flask) python
from flask import session, current_app

@app.route("/login", methods=["POST"])
def login():
    user = authenticate(request.form["email"], request.form["password"])
    if user is None:
        abort(401)

    session.clear()
    current_app.session_interface.regenerate(session)  # rotates the actual session ID (Flask-Session); session.clear() alone only empties the payload, it does not fix a pre-login session ID
    session["user_id"] = user.id
    session["issued_at"] = time.time()
    return redirect("/dashboard")
Wygasanie sesji i invalidacja po stronie serwera python
SESSION_TTL_SECONDS = 30 * 60

def is_session_valid(session: dict) -> bool:
    if "user_id" not in session:
        return False
    if time.time() - session["issued_at"] > SESSION_TTL_SECONDS:
        return False
    return session["user_id"] not in revoked_user_ids  # server-side revocation list
  • Wymuszaj MFA dla operacji wrażliwych (ASVS L2)
  • Sprawdzaj hasła wobec list wycieków (np. Have I Been Pwned range API)
  • Rate-limiting na endpointach logowania, blokada po N nieudanych próbach
  • Unieważniaj wszystkie sesje/tokeny po zmianie hasła lub usunięciu z organizacji
  • Ciasteczko sesji: HttpOnly, Secure, SameSite - nigdy w localStorage

Sesja stanowa (server-side)

  • Łatwa natychmiastowa rewokacja
  • Wymaga magazynu (Redis/DB)
  • Domyślnie bezpieczniejsza dla wrażliwych operacji

JWT bezstanowy

  • Brak zapytania do bazy przy każdej weryfikacji
  • Rewokacja wymaga blacklisty/jti + krótkiego TTL
  • Ryzyko: alg confusion, przechowywanie w localStorage
Mnemonik

Nowa sesja przy każdej zmianie stanu uprzywilejowania: logowanie, zmiana hasła, zmiana roli - zawsze session.clear() lub rotacja tokenu.

Dalej: 03 XSS: Stored, Reflected, DOM i kontekstowe enkodowanie

XSS: Stored, Reflected, DOM i kontekstowe enkodowanie

na egzaminie

XSS (Cross-Site Scripting) pozwala atakującemu wykonać dowolny JavaScript w przeglądarce ofiary w kontekście zaufanej domeny: kradzież ciasteczek sesji, keylogging, podmiana treści, ataki na innych użytkowników.

Trzy warianty

Stored: payload zapisany w bazie i serwowany innym użytkownikom. Reflected: payload w parametrze URL odbity natychmiast w odpowiedzi. DOM-based: payload nigdy nie trafia na serwer, wykonuje się przez manipulację DOM po stronie klienta (np. innerHTML z location.hash).

Termin kluczowy

Stored XSS: Złośliwy skrypt trwale zapisany w bazie danych (np. w komentarzu, opisie profilu) i renderowany bez enkodowania każdemu użytkownikowi odwiedzającemu stronę. To najgroźniejszy wariant, bo skaluje się automatycznie.

Termin kluczowy

DOM-based XSS: Podatność istnieje wyłącznie w kodzie JavaScript po stronie klienta. Dane ze źródła kontrolowanego przez atakującego (location.hash, document.referrer, postMessage) trafiają do zlewu (innerHTML, eval, document.write) bez przejścia przez serwer.

Pułapka

Częsty błąd: developerzy sanitują dane przy zapisie do bazy, ale ten sam string jest renderowany w trzech różnych kontekstach (HTML, atrybut, JavaScript inline). Enkodowanie musi być kontekstowe, przy wyjściu (output encoding), nie jednorazowe przy wejściu.

React: domyślne escapowanie vs świadome ominięcie tsx
// BEZPIECZNE: JSX escapuje automatycznie przy renderze
function Comment({ text }: { text: string }) {
  return <p>{text}</p>;
}

// NIEBEZPIECZNE: świadome wyłączenie escapowania bez sanitizacji
function CommentUnsafe({ html }: { html: string }) {
  return <div dangerouslySetInnerHTML={{ __html: html }} />;
}

// BEZPIECZNE, gdy HTML jest wymagany: sanitizacja przez DOMPurify
import DOMPurify from "dompurify";
function CommentRichText({ html }: { html: string }) {
  const clean = DOMPurify.sanitize(html, { ALLOWED_TAGS: ["b", "i", "a"] });
  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}
Content-Security-Policy: strict nonce zamiast allowlisty domen http
Content-Security-Policy:
  script-src 'nonce-{RANDOM_PER_REQUEST}' 'strict-dynamic' https: 'unsafe-inline';
  object-src 'none';
  base-uri 'none';
  frame-ancestors 'none';
Kontekst wyjściaRodzaj enkodowaniaPrzykład
HTML bodyHTML entity encode< -> &lt;, > -> &gt;
Atrybut HTMLHTML attribute encode + cudzysłów" -> &quot;
JavaScript inlineJS string escape, nigdy wstawianie surowego stringu' -> \'
URL/query stringURL encode (percent-encoding)spacja -> %20
CSSCSS escapeznaki specjalne jako \XX
  • Włącz strict CSP z nonce per-request zamiast allowlisty domen (allowlista jest łamliwa i rozrasta się do setek hostów)
  • Nagłówki: X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy: strict-origin-when-cross-origin
  • SVG upload może zawierać <script> - traktuj jak HTML, nie jak obraz
  • Loguj naruszenia CSP przez report-to/report-uri
Mnemonik

Escapuj przy wyjściu, w kontekście, w którym dane wylądują - nie raz przy wejściu na wszelki wypadek.

Dalej: 04 CSRF i obrona SameSite

CSRF i obrona SameSite

na egzaminie

CSRF zmusza przeglądarkę ofiary, zalogowanej w aplikacji, do wysłania niezamierzonego żądania (np. zmiana hasła, przelew). Przeglądarka automatycznie dołącza ciasteczka sesji do żądania z dowolnej domeny, więc serwer widzi je jako 'uwierzytelnione'.

Zasada

Każdy endpoint zmieniający stan (POST/PUT/PATCH/DELETE oraz każdy GET, który zmienia stan - co jest błędem samo w sobie) wymaga ochrony CSRF. Dotyczy to też endpointów przed logowaniem: login, rejestracja, reset hasła.

Termin kluczowy

Login CSRF: Atakujący wymusza zalogowanie ofiary na konto kontrolowane przez atakującego, np. by śledzić późniejszą aktywność ofiary w kontekście cudzego konta. Ten atak jest często pomijany, bo 'to przecież nie zmienia danych ofiary'.

Termin kluczowy

Double-submit cookie: Wzorzec obrony: token CSRF wysyłany jednocześnie w ciasteczku i w nagłówku/body żądania. Serwer odrzuca żądanie, jeśli wartości się nie zgadzają. Działa bez serwerowego magazynu tokenów.

Pułapka

Częsty błąd: walidacja tokenu CSRF jest 'opcjonalna', gdy token jest pusty (np. stare klienty API) - brak tokenu musi zawsze oznaczać odrzucenie żądania, nigdy przepuszczenie.

SameSite jako warstwa obrony w głąb http
Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnly
Middleware CSRF w Flask (flask-wtf) python
from flask_wtf.csrf import CSRFProtect

app = Flask(__name__)
csrf = CSRFProtect(app)  # wymusza token na każdym POST/PUT/PATCH/DELETE

@app.route("/account/email", methods=["POST"])
def change_email():
    # token walidowany automatycznie przez CSRFProtect przed wejściem tutaj;
    # brak/zły token -> 400 Bad Request
    ...

SameSite=Strict

  • Ciasteczko nigdy nie wysyłane cross-site
  • Najwyższe bezpieczeństwo
  • Może zepsuć nawigację z linków zewnętrznych (np. e-mail -> zalogowana strona)

SameSite=Lax

  • Ciasteczko wysyłane przy nawigacji top-level (kliknięcie linku)
  • Dobry kompromis, częste zachowanie domyślne
  • Nadal wymaga tokenu CSRF dla pełnej ochrony
  • SameSite to warstwa dodatkowa, nie zamiennik tokenu CSRF
  • SameSite nie chroni przed żądaniem z tej samej witryny (np. z przejętej subdomeny). CORS odbijający dowolny Origin z Allow-Credentials pozwala obcej stronie czytać odpowiedzi z ciasteczkiem SameSite=None
  • Nie wstawiaj tokenu CSRF do URL - używaj nagłówka, np. X-CSRF-Token
  • Regeneruj token po zmianie stanu uwierzytelnienia
Mnemonik

Brak tokenu = odrzucone żądanie, zawsze, bez wyjątków 'na razie'.

Dalej: 05 Niebezpieczna deserializacja

Niebezpieczna deserializacja

Deserializacja niezaufanych danych pozwala atakującemu skonstruować obiekt, którego proces odtwarzania (deserializacji) wykonuje dowolny kod, zanim aplikacja zdąży cokolwiek zwalidować. Klasyczny przykład: pickle w Pythonie, ObjectInputStream w Javie, unserialize() w PHP.

Dlaczego to groźne

W przeciwieństwie do XSS czy SQLi, atak trafia zanim dane trafią w ręce logiki biznesowej - często wystarczy jeden request HTTP ze złośliwym payloadem serializowanym.

Termin kluczowy

Gadget chain: Łańcuch istniejących w aplikacji klas, których metody (np. __reduce__, readObject) wywołują się automatycznie przy deserializacji. Atakujący składa je tak, by ich efekt uboczny uruchomił dowolny kod.

Pułapka

Częsty błąd: deweloper używa pickle.loads() do 'wygodnego' cache'owania obiektów Pythona w Redis/na dysku. Zapomina, że Redis/dysk może być zapisywalny przez inny, mniej zaufany komponent systemu. Pickle nie ma trybu bezpiecznego dla danych z zewnątrz.

pickle: niebezpieczne vs bezpieczna alternatywa python
import pickle, json

# NIEBEZPIECZNE: deserializacja niezaufanych danych wykonuje dowolny kod
data = pickle.loads(untrusted_bytes_from_request)

# BEZPIECZNE: format danych bez wykonywalnego kodu
data = json.loads(untrusted_bytes_from_request)
Java: whitelisting klas przy deserializacji java
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
    "com.app.model.*;!*"  // dozwolone tylko klasy z com.app.model, reszta odrzucona
);
ObjectInputStream ois = new ObjectInputStream(inputStream);
ois.setObjectInputFilter(filter);
  • Preferuj formaty bez wykonywalnego kodu: JSON, Protocol Buffers, MessagePack
  • Jeśli deserializacja obiektów jest konieczna: podpisuj serializowane dane (HMAC) i weryfikuj podpis przed deserializacją
  • Whitelisting dozwolonych klas/typów zamiast blacklisty
  • Izoluj proces deserializujący (sandbox, brak uprawnień sieciowych/systemowych)
Powiązanie z supply chain

Ataki na deserializację często wykorzystują 'gadgety' z bibliotek trzecich obecnych w classpath/site-packages - kolejny powód do audytu zależności.

Mnemonik

Jeśli format danych umie wywołać kod przy odczycie, traktuj go jak kod, nie jak dane.

Dalej: 06 Security Misconfiguration i SSRF

Security Misconfiguration i SSRF

na egzaminie

Security Misconfiguration (OWASP A02) to najczęstsza kategoria luk w praktyce: domyślne hasła, włączony debug w produkcji, niepotrzebne usługi, brak nagłówków bezpieczeństwa. SSRF (Server-Side Request Forgery) to jej niebezpieczny szczególny przypadek: serwer wykonuje żądanie HTTP do adresu kontrolowanego przez atakującego.

OWASP 2025

W Top 10:2025 SSRF włączono do kategorii A01 (Broken Access Control) jako przypadek łamania granic zaufania serwera. W praktyce traktuje się go jednak jako odrębną klasę podatności, ze względu na specyficzne wektory (webhooki, generatory PDF, import z URL).

Termin kluczowy

SSRF: Podatność, w której serwer aplikacji wykonuje żądanie sieciowe pod adres URL dostarczony lub wpływany przez użytkownika. Pozwala to dosięgnąć zasoby wewnętrzne (metadata API chmury, bazy danych, panele admina) niedostępne z zewnątrz.

Termin kluczowy

Cloud metadata endpoint: Wewnętrzny adres (np. 169.254.169.254) udostępniający tymczasowe poświadczenia instancji chmurowej - klasyczny cel SSRF, bo zwraca klucze IAM/role bez uwierzytelnienia.

Pułapka

Częsty błąd: filtr URL blokuje 'localhost' i '127.0.0.1' jako stringi, ale nie łapie zapisu dziesiętnego (2130706433) ani oktalnego (0177.0.0.1). Walidacja musi się odbywać po rozwiązaniu DNS, na faktycznym adresie IP.

Pułapka

Samo sprawdzenie nie zatrzyma DNS rebinding: jeśli klient HTTP rozwiąże nazwę jeszcze raz, dostanie inny adres niż sprawdzony. Żądanie musi pójść pod ten sam, sprawdzony adres IP.

SSRF: walidacja adresu IP po rezolucji DNS i połączenie pod ten sam adres python
import ipaddress
import socket
from urllib.parse import urlparse

def resolve_safe_ip(url: str) -> str | None:
    parsed = urlparse(url)
    if parsed.scheme not in ("http", "https") or not parsed.hostname:
        return None

    # rozwiąż DNS PRZED walidacją i sprawdź KAŻDY adres (IPv4 i IPv6)
    infos = socket.getaddrinfo(parsed.hostname, None)
    addrs = [ipaddress.ip_address(info[4][0]) for info in infos]
    if not all(addr.is_global for addr in addrs):
        return None  # blokuje też 169.254.169.254 (metadata endpoint)
    # Łącz się pod TEN adres (nagłówek Host = parsed.hostname) i nie podążaj
    # za przekierowaniem bez ponownej walidacji: drugie rozwiązanie nazwy
    # przez klienta HTTP otwiera drogę dla DNS rebinding.
    return str(addrs[0])
Misconfiguration: wyłączenie listowania katalogów i banera wersji nginx
server {
    autoindex off;                 # brak listowania katalogów
    server_tokens off;             # ukryj wersję Nginx w nagłówku Server
    location /admin { deny all; }  # panel admina niedostępny z zewnątrz
}
Błąd konfiguracjiKonsekwencjaPoprawka
Tryb debug włączony w produkcjiStack trace ujawnia ścieżki, wersje, czasem sekretyDEBUG=False w produkcji, generyczne strony błędów
Domyślne konto/hasło adminaNatychmiastowy dostęp administracyjnyWymuszona zmiana hasła przy pierwszym uruchomieniu
Nadmiarowe uprawnienia CORS (*)Dowolna domena czyta odpowiedzi APIAllowlist konkretnych originów
Brak limitu przekierowań w kliencie HTTPSSRF przez łańcuch redirectów do zasobu wewnętrznegoLimit/walidacja każdego przeskoku redirecta
  • Allowlist domen docelowych zamiast blacklisty adresów wewnętrznych
  • Ogranicz timeout i rozmiar odpowiedzi przy żądaniach wychodzących
  • Segmentacja sieci: serwis pobierający URL-e działa bez dostępu do sieci wewnętrznej
  • Regularny przegląd konfiguracji przez checklisty (CIS Benchmarks, OWASP ASVS)
Mnemonik

Zaufanie do URL kończy się na scheme http/https - adres IP zawsze sprawdzaj po rezolucji, nie po stringu.

Dalej: 07 Broken Access Control (IDOR) i zasada najmniejszych uprawnień

Broken Access Control (IDOR) i zasada najmniejszych uprawnień

na egzaminie

Broken Access Control to od lat kategoria #1 w OWASP Top 10 pod względem częstości występowania. IDOR (Insecure Direct Object Reference) to jego najczęstszy podtyp.

Fakt na test

W IDOR aplikacja ufa identyfikatorowi zasobu podanemu przez klienta, zamiast sprawdzić, czy zalogowany użytkownik ma prawo do tego zasobu.

Zasada nadrzędna

Autoryzacja weryfikowana na poziomie danych (per-request), nie tylko na poziomie routingu - middleware chroniący ścieżkę /admin nie chroni przed dostępem do cudzego zasobu pod /api/orders/{id}.

Termin kluczowy

IDOR: Bezpośrednie odwołanie do obiektu (np. /api/invoices/1042) bez weryfikacji, czy zalogowany użytkownik jest właścicielem zasobu o tym identyfikatorze - zmiana ID w URL ujawnia cudze dane.

Termin kluczowy

Horizontal vs Vertical Privilege Escalation: Eskalacja pozioma: użytkownik A uzyskuje dostęp do danych użytkownika B na tym samym poziomie uprawnień. Eskalacja pionowa: zwykły użytkownik uzyskuje funkcje administratora.

Termin kluczowy

Least Privilege: Każdy komponent (konto bazodanowe, rola użytkownika, klucz API, proces) otrzymuje wyłącznie minimalny zestaw uprawnień niezbędny do realizacji swojej funkcji, nic więcej 'na zapas'.

Pułapka

Częsty błąd: sekwencyjne ID (1, 2, 3...) w publicznym API ułatwiają enumerację zasobów. Nawet z poprawną autoryzacją to informacja wywiadowcza (liczba zamówień, tempo wzrostu firmy). UUIDv4 to obrona w głąb, nie zamiennik autoryzacji.

Weryfikacja własności zasobu na poziomie danych (Flask) python
@app.route("/api/invoices/<invoice_id>")
@login_required
def get_invoice(invoice_id):
    invoice = db.get_invoice(invoice_id)

    if invoice is None:
        abort(404)  # nie ujawniaj, czy zasób istnieje

    if invoice.owner_id != current_user.id and not current_user.is_admin:
        abort(404)  # 404, nie 403 - utrudnia enumerację zasobów

    return jsonify(invoice.to_dict())
Least privilege: konto aplikacji bez uprawnień DDL sql
-- Konto aplikacji: tylko operacje na danych, zero uprawnień strukturalnych
CREATE ROLE app_user LOGIN PASSWORD '...';
GRANT SELECT, INSERT, UPDATE, DELETE ON orders, invoices TO app_user;
REVOKE ALL ON SCHEMA public FROM app_user;  -- brak DROP/ALTER/CREATE
  • Weryfikuj właściciela zasobu przy KAŻDYM żądaniu, nie polegaj na tym, że UI 'nie pokazuje' cudzych danych
  • Sprawdzaj też zasoby nadrzędne (np. komentarz -> post -> właściciel posta)
  • Re-waliduj uprawnienia po każdej zmianie roli/organizacji
  • Konta serwisowe i klucze API: osobne, wąskie zakresy (scopes) zamiast jednego klucza z pełnym dostępem
Mnemonik

ID w URL to wskazówka klienta, nie dowód uprawnień - serwer sprawdza właściciela zawsze, niezależnie od tego, co przyszło z frontendu.

Dalej: 08 Supply chain, zarządzanie sekretami i hashowanie haseł

Supply chain, zarządzanie sekretami i hashowanie haseł

na egzaminie

Nowoczesna aplikacja to w 80-90% kod cudzy: zależności npm/pip/cargo. OWASP A03:2025 (Software Supply Chain Failures) i A04 (Cryptographic Failures) obejmują ryzyko podatnych zależności oraz błędy w przechowywaniu sekretów i haseł.

Fakt na test

Te trzy różne problemy (podatne zależności, sekrety, hasła) mają jedną przyczynę: ślepe zaufanie do tego, co 'zawsze tak było'.

SBOM

Software Bill of Materials to pełna, maszynowo czytelna lista zależności (bezpośrednich i przechodnich) wraz z wersjami. To podstawa do automatycznego wykrycia, czy dana CVE dotyczy Twojej aplikacji, zanim ktoś zapyta ręcznie.

Termin kluczowy

Dependency confusion: Atak, w którym pakiet z publicznego rejestru, o tej samej nazwie i wyższym numerze wersji, podszywa się pod prywatny pakiet firmowy. Menedżer pakietów instaluje wersję atakującego, jeśli konfiguracja nie wymusza źródła prywatnego.

Termin kluczowy

Argon2id: Argon2 (zwycięzca Password Hashing Competition 2015). Jego wariant Argon2id rekomenduje dziś OWASP/RFC 9106 do hashowania haseł. Jest odporny na ataki GPU/ASIC dzięki kosztowi pamięciowemu (memory-hard). Razem z bcrypt i scrypt to jedyny akceptowalny wybór do przechowywania haseł.

Pułapka

Częsty błąd: 'hashujemy hasła, więc jest bezpiecznie'. MD5 i SHA-256 to funkcje szybkie (miliardy prób/sekundę na GPU), zaprojektowane do integralności danych, nie do haseł. Do haseł służą tylko funkcje celowo wolne i pamięciochłonne: Argon2id, bcrypt, scrypt.

Hashowanie haseł: niebezpieczne vs poprawne python
import hashlib
from argon2 import PasswordHasher

# NIEBEZPIECZNE: szybki hash bez soli, łamany tęczowymi tablicami w sekundy
hashlib.md5(password.encode()).hexdigest()

# BEZPIECZNE: Argon2id, sól generowana automatycznie, koszt pamięciowy wbudowany
ph = PasswordHasher()
hashed = ph.hash(password)           # zapisz `hashed` do bazy
ph.verify(hashed, password_attempt)  # rzuca wyjątkiem przy niezgodności
Audyt zależności i blokada wersji (Python) bash
# Zamroź dokładne wersje przechodnich zależności (powtarzalny build)
pip freeze > requirements.lock.txt

# Skanuj znane CVE w zainstalowanych pakietach
pip install pip-audit && pip-audit
Sekrety: zmienne środowiskowe, nigdy w repo bash
# .env (w .gitignore, NIGDY commitowane)
DATABASE_URL=postgresql://user:pass@host:5432/db
JWT_SECRET=<losowe 256+ bitów>
Aplikacja czyta wyłącznie ze środowiska, nigdy z hardkodowanej stałej python
import os

database_url = os.environ["DATABASE_URL"]
  • Nigdy nie commituj sekretów do repozytorium (nawet w historii - rotacja po wycieku obowiązkowa)
  • NEXT_PUBLIC_*/REACT_APP_* trafiają do bundla klienta - żadnych kluczy prywatnych pod tymi prefiksami
  • Generuj i utrzymuj SBOM (np. przez cyclonedx/syft) dla każdego wydania
  • Automatyczne skanowanie zależności w CI (Dependabot, pip-audit, npm audit) blokujące merge przy krytycznych CVE
  • Least privilege także dla kluczy API: osobny klucz o wąskim zakresie na integrację, nie jeden klucz-wytrych
Mnemonik

Hasło hashuj wolno i z pamięcią (Argon2id/bcrypt), zależność sprawdzaj zanim zaufasz, sekret trzymaj poza kodem, zawsze.

Sprawdź się

20 z 24 pytań, losowo, z wyjaśnieniem po każdym. Wynik zostaje na tym urządzeniu.

Rozpocznij quiz

Fiszki: Bezpieczeństwo aplikacji

Pytanie

O tym demo

Działające demo kursów napisanych przez Tenzan Logic. Wyniki zostają na tym urządzeniu, bez konta.

Każdy kurs, pytanie i fiszkę napisał Tenzan Logic, po polsku i od zera. Jeśli chcesz taki zestaw dla swojego zespołu, z Twoimi procedurami i Twoim słownictwem, napisz.

Napisz do Tenzan Logic

Jak to działa

100% czas (dni) bez powtórek 1 3 7 21 z powtórkami
Schemat idei, nie dane pomiarowe (Ebbinghaus 1885; Cepeda i in. 2006).
  • Odstępy

    1·3·7·21 dni do powtórki

    Fiszka z „Umiem” wraca po 1, 3, 7 i 21 dniach.

    Badanie

    Powtórki z przerwami dają trwalszą pamięć niż jedna po drugiej. Cepeda i in. 2006, Psychological Bulletin 132(3): 354-380

  • Przypominanie

    ukryta odpowiedź fiszki

    Najpierw odpowiedz z pamięci, potem odwróć.

    Badanie

    Próba przypomnienia dała lepszą pamięć po dwóch dniach i po tygodniu niż ponowne czytanie. Roediger i Karpicke 2006, Psychological Science 17(3): 249-255

  • Dwa kody

    4 kolory bloków

    Fakt, termin, mnemonik i pułapka mają swój kolor.

    Badanie

    Teoria: słowo i obraz zapisują się osobno. To teoria, nie gwarancja wyniku. Paivio 1991, Canadian Journal of Psychology 45(3): 255-287

  • Porcje

    167 krótkich sekcji

    Jedna sekcja to jedna porcja do zapamiętania.

    Badanie

    Pamięć robocza mieści około czterech porcji (Miller 1956: siedem, Cowan 2001: cztery). Cowan 2001, Behavioral and Brain Sciences 24(1): 87-114

  • Skróty

    134 zielone bloki

    Mnemonik zbiera kilka elementów w jeden, np. „SUR”.

    Badanie

    Skrót zbiera kilka elementów w jeden; przykład „SUR” jest w kursie o podstawach AI. Opis funkcji aplikacji, bez cytowanego badania

  • Testy

    20 z 24 pytań w quizie

    Po każdej odpowiedzi widzisz wyjaśnienie.

    Badanie

    Ćwiczenie, nie sprawdzian: pytania są w losowej kolejności i uzupełniają fiszki, nie zastępują ich. Efekt testowania: zob. „Przypominanie”

Szukaj

Postęp

Brak wyników. Rozwiąż pierwszy quiz.

Tu zbiera się Twój postęp: rekord i medal każdego quizu, seria dni i pytania do powtórki.

Gdzie jest zapis
Tylko w pamięci tej przeglądarki. Konta nie ma i wyniki nigdzie nie są wysyłane. Inna przeglądarka lub inne urządzenie zaczyna od zera.
Jak wyczyścić
W quizie: Historia, potem Wyczyść historię. Fiszki i serię dni usuniesz razem z danymi witryny w ustawieniach przeglądarki.
Wybierz quiz

Progi medali

Medal liczy się z najlepszego wyniku quizu: Brąz od 60%, Srebro od 75%, Złoto od 90%, Platyna przy 100%.