Średnia

ApostropheCMS import-export tar guard misses './..' because it is a literal '../' substring test

  • Ujawniono: 20.10.2026
  • Aktualizacja: 20.10.2026 21:35

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):

js
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:

bash
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:

bash
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:

bash
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:

bash
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:

js
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.

PlatformaPaczkaPodatne wersjePoprawka w wersji
Paczka npm @apostrophecms/import-export < 3.7.0 3.7.0

Odnośniki

Ocena CVSS -
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

  1. 2026: 4
  1. ApostropheCMS import-export tar guard misses './..' because it is a literal '../' substring test ten wpis
    Średnia CVE-2026-113664 < 3.7.0 3.7.0

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.

Podłącz swoją aplikację
Newsletter DockRay

Bezpieczeństwo aplikacji w Twojej skrzynce

Najważniejsze podatności, konkretne zalecenia i praktyczne wskazówki dla Twoich aplikacji. Wybierz technologie i śledź to, co ma znaczenie dla Twojego projektu.

  • Codzienne zestawienie nowych podatności, także krytycznych.
  • Powiadomienia o ważnych aktualizacjach.
  • Praktyczne porady o bezpieczeństwie i monitoringu aplikacji.

Pozostaw puste, aby otrzymywać informacje o wszystkich technologiach.

Zapis jest bezpłatny. Wypiszesz się jednym kliknięciem w wiadomości.