6. Komponenty i ataki łańcucha dostaw

Współczesne aplikacje internetowe tworzone są z wykorzystaniem wielu gotowych rozwiązań zarówno od strony backendu jak i frontendu. Wykorzystują także wiele bibliotek udostępniających różne funkcje, co z punktu widzenia reguły DRY (don’t repeat yourself) jest korzystne i skraca proces tworzenia oprogramowania. Jest jednak i ciemna tego strona – brak kontroli nad tym od czego zależy nasza aplikacja i w jakim stanie znajdują się te komponenty. Prowadzi to do szeregu problemów związanych z trwałością i stabilnością naszego projektu, w szczególności do powstania podatności będących poza naszą kontrolą. Problemy jakie mogą powstać obejmują:

  • wykorzystywanie komponentów, których wsparcie zostało zakończone (typowo w większości projektów node.js znajdziemy taki komponent),
    • komponent może ,,zniknąć'' - problem trwałości projektu,
    • komponent może zostać przejęty przez inny podmiot i podmieniony na szkodliwy,
    • mogą zostać znalezione nowe podatności, których nie ma kto usunąć,
  • komponent może mieć znane podatności,
  • przypadkowe aktualizacje do złośliwych wersji --- Supply chain attack --- atakujący przejmuje np. domenę, repozytorium źródłowe git, wstrzykuje złośliwą wersję komponentu do systemu dystrybucji, DNS poisoning przekierowuje do złośliwej wersji komponentu, atakuje system dystrybucji aktualizacji (Solarwinds, 2020), podatne są wszystkie systemy dystrybucji komponentów (node.js npm, python pip) [27],
  • wykorzystanie literówek w nazwach lub podobieństwa nazw do istniejących pakietów do umieszczenia ich złośliwych wersji [28].

Najsłynniejszym przykładem jak jeden znikający z repozytorium npm pakiet sparaliżował Internet jest historia pakietu left-pad mającego zaledwie 11 linijek kodu, a od którego zależało bardzo wiele innych pakietów w tym React, jeden z najpopularniejszych frameworków fronendowych. Całą historię można przeczytać w [29], ale pokazuje ona jak wielkie znaczenie ma wiedza o zależnościach naszego projektu.

Metody ochrony przed tego typu problemami obejmują:

  • przechowywanie własnych kopii zależności:
    • rodzi to ryzyko nieaktualności względem oryginału,
    • ale daje możliwość weryfikacji zmian w poszukiwaniu manipulacji,
  • korzystanie ze standardu Subresource Integrity (SRI) do definiowania skrótów ładowanych z zewnątrz zasobów – w przypadku nieoczekiwanej zmiany skrótu kryptograficznego zasobu przeglądarka odmówi jego załadowania do projektu:
<script src="https://example.com/example.js"
        integrity="sha384-doQSMg97CqWBL85CjcRwazyuUOAqZMqhangiSb/o78S37xzLEmJV0ZYEff7fF6Cp"
        crossorigin="anonymous"></script>

więcej na ten temat można przeczytać w [30],

  • używanie podpisanych cyfrowo zależności (jeśli można),
  • regularna weryfikacja stanu zależności (są do tego automatyzacje), jak np. npm audit [31], czy composer audit w przypadku języka PHP [32],
  • usunięcie wszystkich zależności, które nie są używane,
  • przeglądanie baz CVE, NVD lub subskrypcja powiadomień bezpieczeństwa (jeśli dostawca komponentu oferuje),
  • uważne ustalenie sprawdzonych wersji komponentów (np. package lock w npm).