Krótko: Dla bezpieczeństwa API w PHP liczy się przede wszystkim pięć punktów: tylko HTTPS, uwierzytelnianie na każdym niepublicznym punkcie końcowym, kontrola uprawnień dla każdego pojedynczego obiektu, ścisłe granice dla metody, typu zawartości i rozmiaru żądania oraz zwięzłe komunikaty o błędach bez szczegółów wewnętrznych. Dochodzą ograniczanie liczby żądań i dziennik zdarzeń istotnych dla bezpieczeństwa. Przeciw lukom w samym interpreterze PHP, takim jak PHP2Shell, to nie pomaga; potrzebna jest do tego aktualizacja PHP.
Interfejs za stroną internetową
Wiele firm sprawdziło swoje strony w tygodniu po 21.10.2026. Interfejsy za nimi rzadziej znajdowały się na liście. A właśnie tam przebiegają wrażliwe procesy: aplikacja portalu klienta pobiera faktury, system zarządzania towarem zapisuje stany magazynowe w sklepie, partner zgłasza zlecenia przez webhook. Często te API budowano szybko, do jednego celu, a potem nigdy więcej na nie nie patrzono.
API nie ma interfejsu, który uwidacznia błędy. Brakująca kontrola uprawnień nie rzuca się nikomu w oczy na co dzień, dopóki wszyscy klienci pytają tylko o to, o co powinni. Atakujący próbuje natomiast dokładnie tego, co nie jest przewidziane.
Pierwszym krokiem jest więc inwentaryzacja. Jakie punkty końcowe w ogóle istnieją? Często znajdują się stare wersje, takie jak /api/v1/, które po przejściu na /v2/ nigdy nie zostały wyłączone, punkty testowe z etapu programowania albo publicznie dostępny opis interfejsu, wymieniający funkcje wewnętrzne. Każdy punkt końcowy, który nie jest już potrzebny, należy wyłączyć. Każdy, który zostaje, potrzebuje osoby odpowiedzialnej.
Bezpieczeństwo API w PHP: zasady podstawowe
Ściągawka OWASP REST Security podsumowuje sprawdzone zasady. Dla projektów PHP można je przełożyć na krótką listę:
- Tylko HTTPS. Tokeny i dane dostępowe nigdy nie mogą być przesyłane bez szyfrowania.
- Każdy punkt końcowy sprawdza sam. Uwierzytelnianie może odbywać się centralnie, decyzja o dostępie zapada w punkcie końcowym.
- Uprawnienie dla każdego obiektu. Nie tylko „jest zalogowany”, lecz „może dokładnie ten rekord”.
- Ustalić dozwolone metody. Co nie jest wyraźnie dozwolone, jest odrzucane kodem 405.
- Sprawdzać typ zawartości i rozmiar. Błędny typ daje 415, zbyt duże żądania 413.
- Walidować dane wejściowe. Typ, długość, format i zakres wartości, po stronie serwera.
- Żadnych sekretów w adresach URL. Tokeny i klucze należą do nagłówka, a nie do query stringu.
- Zwięzłe błędy. Żadnych stack trace, ścieżek ani komunikatów SQL dla klienta.
- Ograniczanie liczby żądań. Na zbyt wiele żądań odpowiada się kodem 429.
- Odgrodzić punkty administracyjne. Niedostępne publicznie lub tylko z silnym logowaniem.
Uprawnienie do obiektu: najczęstszy błąd
Przykład sprawia, że problem staje się namacalny. Punkt końcowy wydaje faktury pod adresem /api/rechnungen/{id}. Sprawdzane jest, czy istnieje ważny token. Nie jest sprawdzane, czy faktura należy do klienta posiadającego token. Kto zwiększy własny numer faktury o jeden, widzi fakturę sąsiada.
Rozwiązanie jest proste i mimo to musi zostać wdrożone w każdym miejscu: samo zapytanie ogranicza się do uprawnionego zakresu.
<?php
// Dopuścić tylko metodę GET
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
http_response_code(405);
header('Allow: GET');
exit;
}
$kundeId = $auth->kundeId(); // ze sprawdzonego tokenu
$id = filter_var($routeId, FILTER_VALIDATE_INT);
if ($id === false) {
http_response_code(400);
exit;
}
// Uprawnienie tkwi w zapytaniu: tylko własne faktury
$stmt = $pdo->prepare('SELECT nummer, betrag, datum FROM rechnungen WHERE id = ? AND kunde_id = ?');
$stmt->execute([$id, $kundeId]);
$rechnung = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$rechnung) {
http_response_code(404); // nie zdradzać, czy dany identyfikator istnieje
exit;
}
header('Content-Type: application/json');
echo json_encode($rechnung, JSON_THROW_ON_ERROR);
Zapytanie z placeholderami chroni jednocześnie przed SQL injection, zob. Zapobieganie SQL injection w PHP. Jak PHP udostępnia dane żądania, takie jak metoda, nagłówki i ciało, wyjaśnia artykuł Superglobals: $_GET, $_POST, $_FILES i inne.
Prawidłowe sprawdzanie tokenów
Wiele API pracuje z JSON Web Tokens. Ściągawka OWASP podaje dla nich jasne zasady: tokeny muszą być podpisane, niepodpisane tokeny są odrzucane, a algorytm sprawdzania ustala się na serwerze, a nie przejmuje z tokenu. Wystawca, odbiorca i czas wygaśnięcia również należą do kontroli. Użyj do tego utrzymywanej biblioteki zamiast własnych implementacji.
Własne tokeny, na przykład dla webhooków lub linków jednorazowych, muszą pochodzić z kryptograficznie bezpiecznego generatora liczb losowych i być porównywane w stałym czasie. Jak to zrobić w PHP, pokazuje artykuł Bezpieczne liczby losowe i tokeny w PHP.
Daj każdemu klientowi i każdemu partnerowi własny klucz z dokładnie takimi uprawnieniami, jakich potrzebuje. Wtedy wyciekły klucz można zablokować pojedynczo, nie przerywając wszystkich pozostałych połączeń.
Ustalać granice: rozmiar, typ i tempo
API powinno przyjmować tylko to, czego oczekuje. Zaczyna się to od typu zawartości: punkt końcowy, który przetwarza JSON, odrzuca wszystko inne. Maksymalny rozmiar żądania ograniczasz na kilku poziomach, w serwerze WWW i w PHP przez post_max_size. Ta granica należy także do środków przejściowych, które php.net podał w sprawie PHP2Shell. Jak ją sensownie dobrać, opisuje artykuł Rozsądne ograniczanie post_max_size i upload_max_filesize.
Ograniczanie liczby żądań chroni przed zgadywaniem identyfikatorów i przed przeciążeniem. Można je wdrożyć w serwerze WWW, w przedniej warstwie proxy lub w aplikacji. Ważne, aby granice obowiązywały dla każdego klucza lub użytkownika, a nie tylko dla każdego adresu IP.
W przypadku API wywoływanych bezpośrednio z przeglądarki dochodzi CORS, czyli reguły określające, które obce strony mogą wysyłać żądania. Ustawiaj te nagłówki tylko wtedy, gdy takie wywołania są naprawdę przewidziane, i wymieniaj dozwolone źródła pojedynczo, zamiast dopuszczać każde pochodzenie. Wieloznacznik dopuszczający wszystkie strony jest przy punktach końcowych z logowaniem prawie zawsze błędem.
Na koniec dziennik: nieudane logowania, odrzucone tokeny i odmowy dostępu należą do logu, same tokeny nie. Bez tych wpisów atak na API pozostaje niewidoczny.
Jak konkretnie pomaga PHP SOS?
- Spisujemy wszystkie interfejsy Państwa systemów PHP, także zapomniane i wewnętrzne, i sprawdzamy, które są publicznie dostępne.
- Czytamy logikę uprawnień i celowo testujemy, czy cudze rekordy można pobrać.
- Ustalamy granice rozmiaru, typu i tempa w serwerze WWW i w PHP oraz uzgadniamy je z Państwa klientami.
- Dla kompleksowej kontroli z zewnątrz łączymy to z audytem bezpieczeństwa i testem penetracyjnym.
Podsumowanie: API jest tak bezpieczne jak jego najbardziej niedbały punkt końcowy. Kto sprawdza uprawnienia dla każdego obiektu, ustala jasne granice i utrzymuje błędy zwięzłe, zamyka najczęstsze luki na długo, zanim ktoś zacznie ich celowo szukać.
Częste pytania
Czy klucz API wystarczy jako ochrona?
Dla wrażliwych danych nie. Klucz API identyfikuje zwykle aplikację, a nie użytkownika, i łatwo go przekazać dalej. Ściągawka OWASP REST Security radzi, aby nie stosować kluczy API jako jedynej ochrony wrażliwych zasobów.
Czym jest uprawnienie do obiektu i dlaczego tak często go brakuje?
Chodzi o kontrolę, czy zalogowany użytkownik może zobaczyć lub zmienić dokładnie ten rekord. Często jej brakuje, ponieważ programiści sprawdzają tylko, czy ktokolwiek jest zalogowany. Wtedy wystarczy zmienić identyfikator w adresie URL, aby pobrać cudze dane.
Czy dobrze zabezpieczone API chroni przed PHP2Shell?
Nie. PHP2Shell tkwi w interpreterze PHP i działa w dotkniętych trybach pracy, zanim Państwa kod w ogóle się uruchomi. Pomaga na to wyłącznie aktualizacja PHP z restartem. Limity rozmiaru żądań są mimo to sensowne i należą także do środków przejściowych podanych przez php.net.
Jakie kody statusu powinno zwracać API przy błędach bezpieczeństwa?
Typowe są 401 bez ważnego logowania, 403 przy braku uprawnień, 405 przy niedozwolonej metodzie, 413 przy zbyt dużym żądaniu, 415 przy błędnym typie zawartości i 429 przy zbyt wielu żądaniach. Sama odpowiedź pozostaje przy tym zwięzła i bez szczegółów wewnętrznych.