Mega Sekurak Hacking Party w Krakowie! 20.10.2025 r. Bilety -30%
Podatności w Filesystem MCP Server od Anthropica
Tematy związane z agentami LLM jeszcze przez długi czas będą na czele gorących zagadnień. Głównie za sprawą usprawnień i automatyzacji. Jednak wprowadzany na prędce standard MCP oraz wiele rozwiązań na nim bazujących to wciąż świetne miejsce do nadużyć. Ostatnio opisywaliśmy moduł mcp-remote i zagrożenia z nim związane. W skrócie jest to proxy pozwalające na podpięcie zdalnych serwerów MCP. Wykryta podatność pozwalała na wykonanie kodu na maszynie, która korzystała z tego proxy (a więc w założeniu na systemie, w którym odpalony był klient agenta np. Claude Desktop). W niniejszym tekście przybliżymy błędy zidentyfikowane w jednym z serwerów MCP.
Niejednokrotnie można spotkać się z twierdzeniem, że standard MCP to takie “usb-c” dla LLMów, ponieważ ułatwia rozwijanie rozwiązań opartych o LLMy w ustandaryzowany sposób. Serwery MCP z kolei nazywane są “rękami i nogami” modeli językowych. Jest w tych przenośniach trochę prawdy, to dzięki serwerom MCP, AI może dokonywać interakcji ze światem zewnętrznym. Serwery takie mogą odpowiadać za dostarczanie informacji o pogodzie, pozwalać przeszukiwać relacyjne bazy danych i wiele wiele innych. Z powyższego opisu pewnie nie za wiele wynika, prawda? A to dlatego, że samo MCP nie jest technologią ani wybitną, ani szczególnie odkrywczą. To standard strukturyzujący sposób wymiany informacji między LLM a systemami zewnętrznymi, innymi słowy – aplikacjami takimi jak np. serwery bazodanowe, które mogą dostarczać kontekst dużym modelom językowym. Ale to nie wszystko, wykorzystując MCP, LLMy mogą wywoływać określone akcje i uruchamiać narzędzia. Protokół, do wymiany informacji, wykorzystuje “pod spodem” JSON-RPC. Więcej na jego temat można znaleźć tu.
Anthropic, firma stojąca między innymi za Claude, stawia mocno na rozwój serwerów MCP, utrzymując między innymi listę przykładowych oraz rozwijanych przez community serwerów. Jednym z nich jest Filesystem MCP Server, którego zadaniem, jak nietrudno się domyślić, jest umożliwienie interakcji z systemami plików. Stworzony w technologii Node.js ma być bezpieczną metodą na dokonywanie bezpiecznych operacji na plikach. Korzystać z tego może np. Cursor czyli IDE wspierane przez sztuczną inteligencję, do zbierania informacji o projekcie czy też generowaniu struktury i odpowiednich plików.
Serwer umożliwia skonfigurowanie dozwolonych miejsc pracy (ograniczenie działania tylko do określonego zbioru ścieżek). Ograniczenie to wprowadza się w pliku konfiguracyjnym w formacie JSON. Autorzy badania z firmy cymulate.com pokazali, że te ograniczenia nie są wystarczające, ponieważ ich implementacja pozwala na złamanie narzuconych reguł, prowadząc w pierwszej kolejności do wyskoczenia z ograniczonego środowiska. Ale to nie wszystko, korzystając z tego gadżetu, badacze pokazali jak osiągnąć zdalne wykonanie kodu na serwerze MCP.
CVE-2025-53110 – wyjście po za zdefiniowaną ścieżkę
AI powinno operować tylko w przygotowanym dla niego środowisku, aby nie doprowadzić do sytuacji, w której agenty (tak, to poprawna odmiana, prosimy o powstrzymanie się z niesprawdzonymi komentarzami) wprowadzają nieautoryzowane zmiany do plików systemowych.
Niestety autorzy kodu serwera popełnili stosunkowo trywialny błąd w metodzie sprawdzającej poprawność ścieżek. Zamiast testować pełne dopasowanie ścieżki, użyli funkcji startsWith()
.
// Check if path is within allowed directories
const isAllowed = allowedDirectories.some(dir => normalizedRequested.startsWith(dir));
if (!isAllowed) {
throw new Error(`Access denied - path outside allowed directories: ${absolute} not in ${allowedDirectories.join(', ')}`);
}
Listing 1. Błędne sprawdzenie dozwolonej ścieżki (źródło)
Wnikliwy Czytelnik domyśla się zapewne, do czego prowadzi taki sposób porównywania ścieżek. Otóż możliwe jest stworzenie podobnej lokalizacji poza miejscem dozwolonym. Jeśli np. serwer pozwala operować (np. tworzyć) pliki w /home/sekurak/data, a tajne informacje na serwerze przechowywane są w /home/sekurak/data_secret to mimo wprowadzenia restrykcji, aby dozwoloną ścieżką była tylko ta pierwsza, atakujący będzie w stanie, przy pomocy modelu LLM, uzyskać dostęp do sekretów (ponieważ /home/sekurak/data_secret rozpoczyna się od /home/sekurak/data)
PoC ataku został przygotowany przy pomocy Claude Desktop:

W następnym prompcie atakujący nakazuje modelowi odczytanie informacji z innej ścieżki, która ma ten sam prefix:

Już sam wyciek informacji może być niebezpieczny dla bezpieczeństwa systemu, a co dopiero manipulacja plikami przez model LLM.
Jednak na tym testowanie granic możliwości serwera MCP się nie zakończyły.
CVE-2025-53109 – symlinki oraz podniesienie uprawnień
Implementując operacje na systemach plików, przezorny programista będzie pamiętał o obsłudze ciekawych bytów, które nie raz pozwoliły hakerom osiągnąć swój cel. Jak można się domyślić chodzi o dowiązania symboliczne (twarde i miękkie), zwane potocznie symlinkami. Autorzy omawianego serwera MCP również pamiętali o tej strukturze i chcieli nie dopuścić do możliwości wyskoczenia poza dozwolone ścieżki.
Niestety, jak od dawna twierdzą hakerzy, diabeł tkwi w szczegółach – nieodpowiednia obsługa wyjątków doprowadziła de facto do możliwości obejścia zaimplementowanego ograniczenia.
// Security utilities
async function validatePath(requestedPath: string): Promise<string> {
const expandedPath = expandHome(requestedPath);
const absolute = path.isAbsolute(expandedPath)
? path.resolve(expandedPath)
: path.resolve(process.cwd(), expandedPath);
const normalizedRequested = normalizePath(absolute);
// Check if path is within allowed directories
const isAllowed = allowedDirectories.some(dir => normalizedRequested.startsWith(dir));
if (!isAllowed) {
throw new Error(`Access denied - path outside allowed directories: ${absolute} not in ${allowedDirectories.join(', ')}`);
}
// Handle symlinks by checking their real path
try {
const realPath = await fs.realpath(absolute);
const normalizedReal = normalizePath(realPath);
const isRealPathAllowed = allowedDirectories.some(dir => normalizedReal.startsWith(dir));
if (!isRealPathAllowed) {
throw new Error("Access denied - symlink target outside allowed directories");
}
return realPath;
} catch (error) {
// For new files that don't exist yet, verify parent directory
const parentDir = path.dirname(absolute);
try {
const realParentPath = await fs.realpath(parentDir);
const normalizedParent = normalizePath(realParentPath);
const isParentAllowed = allowedDirectories.some(dir => normalizedParent.startsWith(dir));
if (!isParentAllowed) {
throw new Error("Access denied - parent directory outside allowed directories");
}
return absolute;
} catch {
throw new Error(`Parent directory does not exist: ${parentDir}`);
}
}
}
Listing 2. Obsługa dowiązań symbolicznych (źródło)
Co jest nie tak z tym fragmentem? Oczywiście spostrzegawczy Czytelnik zauważa już, że programiści nie zapomnieli o obsłudze rzeczywistej (rozwiązanej) ścieżki symlinka:
const isRealPathAllowed = allowedDirectories.some(dir => normalizedReal.startsWith(dir));
Jednak rzucany wyjątek powoduje uruchomienie bloku catch, który robi fallback i zaczyna obsługiwać znów ścieżkę nierzeczywistą (bez rozwiązania symlinka): const parentDir = path.dirname(absolute);
Autorzy badania, korzystając z poprzedniej luki (zgodności prefixów) pokazali, że taki mechanizm można łatwo nadużyć. Jak?
mamy ścieżkę /home/sekurak/data
. Tworzymy lokalizację /home/sekurak/data_bad
, a wewnątrz tworzymy symlink ./test
do /etc/shadow
. Wstępna walidacja z listingu 2 nie powiedzie się, ponieważ plik docelowy, wskazywany przez symlink /etc/shadow leży poza dozwolonym miejscem pracy agenta. Jednak obsługa błędu sprawdzi czy prefixy ścieżek (bez rozwiązywania do rzeczywistych) się zgadzają – /home/sekurak/data_bad/test rzeczywiście zaczyna się od /home/sekurak/data. Więc zwrócona zostanie ścieżka absolutna i możliwe będzie uzyskanie dostępu do pliku 🙂(jeśli oczywiście uprawnienia serwera na to pozwalają).

Ale to nie wszystko – wykorzystując przedstawione wyżej mechanizmy, udało się uzyskać wykonanie kodu. W tym celu nadużyto mechanizmu Launch Agents (czyli metody automatyzacji zadań w macOS). W efekcie wskazania przez symlink i zapisania dowolnych komend przez agenta, do pliku konfiguracyjnego automatycznie startującego zadania, w momencie np. logowania użytkownika, zdefiniowana przez atakującego komenda zostanie wykonana w kontekście logującego się usera.
Cały atak można obejrzeć na przedstawionym filmiku:
Reasumując, pokazane podatności stanowią niebagatelne ryzyko dla systemu oferującego usługi serwera MCP. Niestety mimo umieszczenia mechanizmów zabezpieczających, udało się osiągnąć cel, doprowadzając do wykonania poleceń w kontekście innego użytkownika. A wszystko za sprawą błędnej implementacji zabezpieczeń. Błędy te zostały naprawione i jeśli jesteście użytkownikami, a właściwie Wasze systemy udostępniają Filesystem MCP Server, to koniecznie zaktualizujcie go do wersji 2025.7.1
Na koniec zostawiamy Was z ciekawostką, otóż agent LLM sam zorientował się, że coś może być nie tak:

~Black Hat Logan