ApostropheCMS import-export tar guard misses './..' because it is a literal '../' substring test
Podsumowanie
Zabezpieczenie ekstrakcji archiwów tar dodane do @apostrophecms/import-export dla GHSA-mwxc-m426-3f78 stanowi dosłowny test podciągu na obecność ../, a ./.. nie zawiera tego podciągu. Wpis o nazwie ./.. przechodzi zatem przez zabezpieczenie, path.join normalizuje go do katalogu nadrzędnego katalogu eksportu, a wynikowy fs.WriteStream otwiera katalog i emituje EISDIR bez podpiętego odbiornika error, co kończy działanie procesu Node.
Potwierdzone na gałęzi domyślnej HEAD 1bd5d0a7a6fe880836fa75adad6964f1c577d640, wersja pakietu 3.6.2.
Szczegóły
packages/import-export/lib/formats/gzip.js, w obsłudze strumienia tar entry wewnątrz extract(filepath, exportPath):
const name = header.name.replace(/\\/g, '/');
if (name.includes('../')) {
// skipped
}
...
stream.pipe(fs.WriteStream(path.join(exportPath, name)));
W tych czterech wierszach znajdują się dwa niezależne problemy. Zabezpieczenie sprawdza surową nazwę wpisu pod kątem podciągu zamiast rozwiązywać połączoną ścieżkę i weryfikować ograniczenie do docelowego katalogu, a strumień zapisu nie posiada procedury obsługi error, przez co każdy błąd strumienia staje się nieobsłużonym zdarzeniem 'error'.
Aby wywołać ten błąd, wymagane jest jedynie konto z uprawnieniami do importu. packages/import-export/modules/@apostrophecms/import-export-page/index.js udostępnia post.importExportImport, które wywołuje lib/methods/import.js; ta procedura wymaga req.user, a następnie wywołuje format.input(exportPath), co dla przesyłania .gz oznacza wywołanie extract() przez gzip.js na przesłanym archiwum.
PoC
To nie jest kompletny exploit typu end-to-end. Poniżej przedstawiono każdy z trzech prymitywów, od których zależy awaria, uruchomionych indywidualnie, wraz z analizą trasy na poziomie kodu źródłowego. Łańcuch od post.importExportImport do extract() został wyczytany z kodu, a nie zaobserwowany w działaniu.
Zabezpieczenie przepuszcza ładunek:
node -e "console.log('./..'.includes('../')); console.log('..'.includes('../')); console.log('a/./..'.includes('../'));"
false
false
false
path.join rozwiązuje go następnie poza katalog eksportu:
node -e "const path=require('path'); const exportPath='/tmp/exportRoot'; console.log(path.join(exportPath,'./..'));"
/tmp
A strumień zapisu otwarty na katalogu, bez zarejestrowanego odbiornika error, powoduje zakończenie procesu zamiast zwrócenia błędu żądania:
node -e "const fs=require('fs'); const s=fs.createWriteStream('/tmp'); s.write('x'); s.end(); setTimeout(()=>console.log('still alive'),500);"
events.js:291
throw er; // Unhandled 'error' event
^
Error: EISDIR: illegal operation on a directory, open '/tmp'
Emitted 'error' event on WriteStream instance at:
at internal/fs/streams.js:361:12
at FSReqCallback.oncomplete (fs.js:156:23) {
errno: -21,
code: 'EISDIR',
syscall: 'open',
path: '/tmp'
}
Ramka events.js:291 wskazuje, że zrzut pochodzi z linii Node 14 w środowisku testowym. Zachowanie w przypadku nieobsłużonego zdarzenia error nie jest specyficzne dla danej wersji, jednak dokładny komunikat na wspieranych wersjach Node warto zweryfikować ponownie.
Potwierdzenie po stronie kodu źródłowego bez uruchamiania aplikacji:
grep -n "includes('../')" packages/import-export/lib/formats/gzip.js
grep -n "WriteStream" packages/import-export/lib/formats/gzip.js
Wpływ
Uwierzytelniony użytkownik mający dostęp do interfejsu importu może przesłać spreparowane archiwum i doprowadzić do awarii obsługującego je procesu Node. Każdy proces roboczy przetwarzający takie archiwum zakończy się w ten sam sposób.
Sugerowane rozwiązanie
Rozwiązać ścieżkę docelową, wymusić ograniczenie katalogu i dodać obsługę błędów:
const base = path.resolve(exportPath);
const target = path.resolve(path.join(exportPath, name));
if (target !== base && !target.startsWith(base + path.sep)) {
stream.on('end', next);
stream.resume();
return;
}
if (header.type === 'directory') {
fsp.mkdir(target).then(next).catch(reject);
} else {
const out = fs.WriteStream(target);
out.on('error', reject);
stream.pipe(out);
stream.on('end', next);
}
Procedura obsługi błędów jest uzasadniona niezależnie. Każdy wpis w archiwum tar wskazujący na istniejący katalog wywołuje ten sam skutek, niezależnie od występowania błędu przejścia przez katalogi (path traversal).
Dlaczego to nie jest GHSA-mwxc-m426-3f78
Sprawdzenie name.includes('../') to mitygacja wdrożona dla GHSA-mwxc-m426-3f78 / CVE-2026-32731. Blokuje ona nazwy zawierające dosłowną sekwencję ../. Nie kanonizuje jednak połączonej ścieżki, przez co ./.. oraz pojedyncze .. nadal przechodzą walidację, i nie naprawia brakującego odbiornika error. Niniejszy raport dotyczy luk pozostawionych przez weryfikację podciągu.
Podczas audytu kodu i sporządzania raportu korzystano ze wsparcia sztucznej inteligencji. Dokładne linie zabezpieczenia oraz WriteStream w packages/import-export/lib/formats/gzip.js w podanym punkcie HEAD zweryfikowano ręcznie, trasę importu z modułu strony przez lib/methods/import.js do obsługi wpisu prześledzono ręcznie, a trzy jednolinijkowe polecenia Node uruchomiono i skopiowano dosłownie.
Co trzeba zrobić
Zaktualizuj pakiet @apostrophecms/import-export do wersji 3.7.0 lub nowszej.
Czego dotyczy
Kliknij technologię, aby zobaczyć wszystkie dotyczące jej podatności.
| Platforma | Paczka | Podatne wersje | Poprawka w wersji |
|---|---|---|---|
| Paczka npm | @apostrophecms/import-export | < 3.7.0 | 3.7.0 |
Odnośniki
- https://github.com/apostrophecms/apostrophe/security/advisories/GHSA-97wv-p4xx-c7mg
- https://github.com/apostrophecms/apostrophe/commit/9500b8b0e1d630d5e07fbb5fd2c25ed0943845ce
- https://github.com/apostrophecms/apostrophe/commit/f84fd41cbf6c22eb8d0008fc9c25df1f7130b350
- https://github.com/apostrophecms/apostrophe
- https://github.com/apostrophecms/apostrophe/releases/tag/@apostrophecms/import-export@3.7.0
- Wektor CVSS
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N- Słabość (CWE)
CWE-22- Platforma
- npm
- Paczka
@apostrophecms/import-export- Ujawniono
- 20.10.2026
- Aktualizacja
- 20.10.2026 21:35
Źródła i licencje · Redakcja lub tłumaczenie: DockRay. Zachowano prawa źródeł i obowiązujące licencje.
OSV / GitHub Advisory Database - GHSA-97wv-p4xx-c7mg - CC BY 4.0
Historia podatności: @apostrophecms/import-export
Wszystkie opisane podatności tej paczki (Paczka npm), od najnowszej. Widać, czy to jednorazowa wpadka, czy paczka, której trzeba pilnować.
- Wszystkich wpisów
- 4
- Pierwsza → ostatnia
- 03.2026 10.2026
- Bez poprawki
- 0
- Wykorzystywana w atakach
- 0
Rozkład istotności
- Krytyczna 1
- Średnia 3
Ujawnienia w latach
- 2026: 4
-
ApostropheCMS import-export tar guard misses './..' because it is a literal '../' substring test ten wpis
Dowiedz się, które podatności dotyczą Twojej aplikacji
Po podłączeniu integracji DockRay i włączeniu raportowania oprogramowania DockRay porównuje zgłoszone wersje komponentów ze znanymi podatnościami i powiadamia o potwierdzonych dopasowaniach. Zakres zależy od obsługiwanej technologii i dostępnych biuletynów.