{"id":4417,"date":"2023-07-19T10:57:21","date_gmt":"2023-07-19T09:57:21","guid":{"rendered":"https:\/\/soc.dag.pl\/?p=4417"},"modified":"2023-08-30T16:31:07","modified_gmt":"2023-08-30T15:31:07","slug":"dziura-w-firewallu-linuxa","status":"publish","type":"post","link":"https:\/\/soc.dag.pl\/dziura-w-firewallu-linuxa\/","title":{"rendered":"Dziura w firewallu Linuxa"},"content":{"rendered":"

W artykule\u00a0Yet another bug into Netfilter<\/em><\/a>\u00a0om\u00f3wiono luk\u0119 znalezion\u0105 w podsystemie netfilter j\u0105dra Linux. Podczas kolejnego \u015bledztwa autor tamtego tekstu natrafi\u0142 na dziwne por\u00f3wnanie, kt\u00f3re nie chroni w pe\u0142ni kopii wewn\u0105trz bufora. Prowadzi\u0142o to do przepe\u0142nienia stosu bufora, kt\u00f3re zosta\u0142o wykorzystane do uzyskania uprawnie\u0144 roota na Ubuntu 22.04.<\/p>\n

 <\/p>\n

Ma\u0142y skok w przesz\u0142o\u015b\u0107<\/strong><\/p>\n

 <\/p>\n

W poprzednim artykule autor dotar\u0142 do b\u0142\u0119du w strukturze\u00a0nft_set (\/include\/net\/netfilter\/nf_tables.h)<\/em>.<\/p>\n

 <\/p>\n

Poniewa\u017c\u00a0nft_set<\/em>\u00a0zawiera du\u017co danych, niekt\u00f3re inne pola tej struktury mog\u0142yby zosta\u0107 wykorzystane do uzyskania lepszego prymitywu zapisu. Przeszukano wi\u0119c pola d\u0142ugo\u015bci (udlen<\/em>,\u00a0klen<\/em>\u00a0i\u00a0dlen<\/em>), poniewa\u017c mog\u0142y si\u0119 one okaza\u0107 pomocne do wykonania kilku przepe\u0142nie\u0144.<\/p>\n

 <\/p>\n

Badanie kodu i anomalii<\/strong><\/p>\n

 <\/p>\n

Badaj\u0105c r\u00f3\u017cne dost\u0119py do pola\u00a0dlen<\/em>, uwag\u0119 autora przyku\u0142o wywo\u0142anie funkcji\u00a0memcpy<\/em>\u00a0(1) w\u00a0nft_set_elem_init (\/net\/netfilter\/nf_tables_api.c).<\/em><\/p>\n

To wywo\u0142anie jest podejrzane, poniewa\u017c u\u017cywano tutaj dw\u00f3ch r\u00f3\u017cnych obiekt\u00f3w. Bufor docelowy jest przechowywany w obiekcie\u00a0nft_set_ext, ext<\/em>, natomiast rozmiar kopii jest pobierany z obiektu\u00a0nft_set<\/em>. Obiekt\u00a0ext<\/em>\u00a0jest dynamicznie przydzielany w (0) z\u00a0elem<\/em>, a zarezerwowany dla niego rozmiar to\u00a0tmpl->len<\/em>. Chciano sprawdzi\u0107, czy warto\u015b\u0107 przechowywana w\u00a0set->dlen<\/em>\u00a0jest u\u017cywana do obliczania warto\u015bci przechowywanej w\u00a0tmpl->len<\/em>.<\/p>\n

 <\/p>\n

 <\/p>\n

Nieprawid\u0142owe miejsce<\/strong><\/p>\n

 <\/p>\n

nft_set_elem_init<\/em>\u00a0jest wywo\u0142ywany (5) wewn\u0105trz funkcji\u00a0nft_add_set_elem<\/em>\u00a0(\/net\/netfilter\/nf_tables_api.c),<\/em>\u00a0kt\u00f3ra odpowiada za dodanie elementu do zbioru netfilter.<\/p>\n

 <\/p>\n

Jak mo\u017cna zauwa\u017cy\u0107,\u00a0set->dlen<\/em>\u00a0nie s\u0142u\u017cy do zarezerwowania miejsca na dane zwi\u0105zane z\u00a0id\u00a0NFT_SET_EXT_DATA<\/em>, zamiast tego jest to\u00a0desc.<\/em>len\u00a0(5).\u00a0desc\u00a0jest inicjalizowany w ramach funkcji\u00a0nft_setelem_parse_data (\/net\/netfilter\/nf_tables_api.c)<\/em>\u00a0wywo\u0142anej w punkcie (3) Przede wszystkim\u00a0data<\/em>\u00a0i\u00a0desc<\/em>\u00a0s\u0105 wype\u0142niane w\u00a0nft_data_init (\/net\/netfilter\/nf_tables_api.c)<\/em>\u00a0zgodnie z danymi dostarczonymi przez u\u017cytkownika (6). Krytyczn\u0105 cz\u0119\u015bci\u0105 jest sprawdzenie pomi\u0119dzy\u00a0desc->len<\/em>\u00a0i\u00a0set->dlen<\/em>\u00a0w (7), wyst\u0119puje ono tylko wtedy, gdy dane zwi\u0105zane z dodawanym elementem maj\u0105 typ inny ni\u017c\u00a0NFT_DATA_VERDICT<\/em>.<\/p>\n

 <\/p>\n

Jednak\u00a0set->dlen<\/em>\u00a0jest kontrolowany przez u\u017cytkownika, gdy tworzony jest nowy zestaw. Jedynym ograniczeniem jest to, \u017ce\u00a0set->dlen<\/em>\u00a0powinien by\u0107 mniejszy ni\u017c 64 bajty, a typ danych powinien by\u0107 r\u00f3\u017cny od\u00a0NFT_DATA_VERDICT<\/em>. Ponadto, gdy\u00a0desc->type<\/em>\u00a0jest r\u00f3wny\u00a0NFT_DATA_VERDICT<\/em>,\u00a0desc->len<\/em>\u00a0jest r\u00f3wny 16 bajtom.<\/p>\n

 <\/p>\n

Dodanie elementu typu\u00a0NFT_DATA_VERDICT<\/em>\u00a0do zbioru z typem danych\u00a0NFT_DATA_VALUE<\/em>\u00a0zwykle prowadzi do tego, \u017ce\u00a0desc->len\u00a0<\/em>jest r\u00f3\u017cne od\u00a0set->dlen<\/em>. Dlatego mo\u017cliwe jest wykonanie przepe\u0142nienia stosu bufora w\u00a0nft_set_elem_init<\/em>\u00a0przy (1). To przepe\u0142nienie bufora mo\u017ce by\u0107 rozszerzone do 48 bajt\u00f3w d\u0142ugo\u015bci.<\/p>\n

 <\/p>\n

Zosta\u0144 tutaj! Nied\u0142ugo wr\u00f3c\u0119!<\/strong><\/p>\n

 <\/p>\n

Niemniej jednak, nie jest to standardowe przepe\u0142nienie bufora, kiedy u\u017cytkownik mo\u017ce bezpo\u015brednio kontrolowa\u0107 przepe\u0142nione dane. W tym przypadku losowe dane zostan\u0105 skopiowane z zaalokowanego bufora.<\/p>\n

 <\/p>\n

Je\u015bli sprawdzimy wywo\u0142anie\u00a0nft_set_elem_init<\/em>\u00a0(5), mo\u017cna zauwa\u017cy\u0107, \u017ce kopiowane dane s\u0105 pobierane ze zmiennej lokalnej\u00a0elem<\/em>, kt\u00f3ra jest obiektem\u00a0nft_set_elem<\/em>.<\/p>\n

 <\/p>\n

Obiekty\u00a0nft_set_elem (\/net\/netfilter\/nf_tables.h)<\/em>\u00a0s\u0142u\u017c\u0105 do przechowywania informacji o nowych elementach podczas ich tworzenia. Jak mo\u017cna zauwa\u017cy\u0107, 64 bajty s\u0105 zarezerwowane na tymczasowe przechowywanie danych zwi\u0105zanych z nowym elementem. Jednak w momencie wywo\u0142ania przepe\u0142nienia bufora do\u00a0elem.data<\/em>\u00a0zapisywanych jest najwy\u017cej 16 bajt\u00f3w. Dlatego w przepe\u0142nieniu wykorzystywane s\u0105 losowe bajty.<\/p>\n

 <\/p>\n

Wreszcie, nie tak losowo<\/strong><\/p>\n

 <\/p>\n

Znaleziono przepe\u0142nienie bufora bez kontroli na danych u\u017cywanych do korupcji. Zbudowanie exploita z niekontrolowanym przepe\u0142nieniem bufora to prawdziwe wyzwanie.<\/p>\n

elem.data<\/em>\u00a0u\u017cyta w przepe\u0142nieniu nie jest zainicjalizowana (2). Mog\u0142oby to zosta\u0107 wykorzystane do kontroli przepe\u0142nienia.<\/p>\n

 <\/p>\n

Sp\u00f3jrzmy do callera, mo\u017ce wcze\u015bniej wywo\u0142ana funkcja mo\u017ce pom\u00f3c w kontroli danych u\u017cytych do przepe\u0142nienia.\u00a0nft_add_set_elem<\/em>\u00a0jest wywo\u0142ywany w\u00a0nf_tables_newsetelem<\/em>\u00a0(\/net\/netfilter\/nf_tables_api.c)<\/em>\u00a0dla ka\u017cdego elementu, kt\u00f3ry u\u017cytkownik chce doda\u0107 do zbioru.<\/p>\n

 <\/p>\n

nla_for_each_nested<\/em>\u00a0jest u\u017cywane do iteracji po atrybutach przes\u0142anych przez u\u017cytkownika, wi\u0119c u\u017cytkownik jest w stanie kontrolowa\u0107 liczb\u0119 iteracji, kt\u00f3re zostan\u0105 wykonane.\u00a0nla_for_each_nested<\/em>\u00a0u\u017cywa tylko makr i funkcji inline, wi\u0119c wywo\u0142anie\u00a0nft_add_set_elem<\/em>\u00a0mo\u017ce by\u0107 bezpo\u015brednio poprzedzone innym wywo\u0142aniem\u00a0nft_add_set_elem<\/em>. Jest to bardzo przydatne, poniewa\u017c pozwala na u\u017cycie danych poprzedniego elementu w przepe\u0142nieniu, gdy\u017c\u00a0elem.data<\/em>\u00a0nie jest inicjalizowany. Ponadto mo\u017cna zignorowa\u0107 losowo\u015b\u0107 uk\u0142adu stosu. Dlatego spos\u00f3b kontroli przepe\u0142nienia b\u0119dzie niezale\u017cny od kompilacji j\u0105dra.<\/p>\n

 <\/p>\n

Poni\u017cszy schemat podsumowuje r\u00f3\u017cne etapy\u00a0elem.data<\/em>\u00a0w obr\u0119bie stosu w celu wytworzenia kontrolowanego przepe\u0142nienia.<\/p>\n

 <\/p>\n

Losowe dane s\u0105 przechowywane w obr\u0119bie stosu, dodanie nowego elementu z danymi\u00a0NFT_DATA_VALUE<\/em>\u00a0prowadzi do danych kontrolowanych przez u\u017cytkownika w obr\u0119bie stosu. Wreszcie dodanie drugiego elementu z danymi\u00a0NFT_DATA_VERDICT<\/em>\u00a0wywo\u0142a przepe\u0142nienie bufora, a pozosta\u0142o\u015bci danych ostatniego elementu zostan\u0105 skopiowane podczas przepe\u0142nienia.<\/p>\n

 <\/p>\n

Wyb\u00f3r pami\u0119ci podr\u0119cznej<\/strong><\/p>\n

 <\/p>\n

Ostatni\u0105 rzecz\u0105, kt\u00f3ra nie zosta\u0142a om\u00f3wiona przed opracowaniem strategii eksploatacji, jest pami\u0119\u0107 podr\u0119czna, w kt\u00f3rej dochodzi do przepe\u0142nienia.\u00a0elem<\/em>, zaalokowany na (0), jest zale\u017cny od r\u00f3\u017cnych opcji wybranych przez u\u017cytkownika, jak pokazano w poprzednim fragmencie funkcji\u00a0nft_add_set_elem<\/em>, jego rozmiar mo\u017ce by\u0107 r\u00f3\u017cny. Istnieje kilka opcji, kt\u00f3re mo\u017cna wykorzysta\u0107 do jego zwi\u0119kszenia, takich jak\u00a0NFT_SET_ELEM_KEY<\/em>\u00a0i\u00a0NFT_SET_ELEM_KEY_END<\/em>.<\/p>\n

 <\/p>\n

Pozwalaj\u0105 one zarezerwowa\u0107 w\u00a0elem<\/em>\u00a0dwa bufory o d\u0142ugo\u015bci do 64 bajt\u00f3w. Tak wi\u0119c to przepe\u0142nienie mo\u017ce wyra\u017anie wyst\u0105pi\u0107 w kilku cache.\u00a0elem<\/em>\u00a0jest przydzielany na Ubuntu 22.04 z flag\u0105\u00a0GFP_KERNEL<\/em>. Dlatego interesuj\u0105ce pami\u0119ci podr\u0119czne to\u00a0kmalloc-{64,96,128,192}.<\/em><\/p>\n

 <\/p>\n

Teraz pozostaje tylko wyr\u00f3wna\u0107\u00a0elem<\/em>\u00a0na rozmiar obiektu cache, aby wykona\u0107 najlepsze przepe\u0142nienie. Nast\u0119pny schemat reprezentuje budow\u0119\u00a0elem<\/em>\u00a0w celu wyr\u00f3wnania go na 64 bajty.<\/p>\n

 <\/p>\n

U\u017cyto nast\u0119puj\u0105cej konstrukcji, aby celowa\u0107 w pami\u0119\u0107 podr\u0119czn\u0105\u00a0kmalloc-64<\/em>:<\/p>\n

 <\/p>\n