Jakie są zagrożenia / słabości bezpieczeństwa, o których powinien wiedzieć każdy programista C? [Zamknięte]

13

Istnieje wiele zagrożeń bezpieczeństwa związanych z bliskim kontaktem ze sprzętem, w przeciwieństwie do używania dobrze przetestowanych i sprawdzonych interfejsów API z języków programowania wysokiego poziomu. O wiele łatwiej jest spowodować przepełnienie bufora w C niż w języku takim jak Java.

Jakie są zagrożenia lub słabości (np. Przepełnienie bufora), o których powinien wiedzieć każdy programista C (luki IE istotne dla programistów C)? Do jakich problemów może to prowadzić? Jak ich uniknąć i jakie są typowe błędy powodujące ich występowanie w programach?

Anto
źródło
Co z tą listą: owasp.org/index.php/Category:OWASP_Top_Ten_Project Czego więcej potrzeba niż to?
S.Lott
2
@ S.Lott: Wydaje się, że chodzi o kwestie bezpieczeństwa w tworzeniu stron internetowych. Wydaje się, że ogólnie jest na to więcej zasobów niż o to, o co właściwie proszę.
Anto
@Anto: Zaktualizuj pytanie, aby odróżnić wszystkie zasoby związane z bezpieczeństwem od bezpieczeństwa, o które pytasz.
S.Lott
@ S.Lott: Nie jestem pewien, co masz na myśli. Proszę o bezpieczeństwo, które jest ważne dla większości programistów C, to znaczy takie rzeczy, jak przepełnienie bufora i inne rzeczy, które są możliwe w C.
Anto
@Anto: „Wydaje się, że w tym [zabezpieczeniu sieci jest więcej zasobów niż w rzeczywistości o to, o co właściwie pytam”. Wydaje się, że pytasz o pewne zabezpieczenia, które nie są zabezpieczeniami sieci. Prawdziwe? Jeśli tak, zaktualizuj pytanie, aby wyjaśnić, czego szukasz. Fałszywe? Wtedy prosząc o bezpieczeństwo stron internetowych, w tym przypadku, to dlaczego nie jest to lista OWASP wspomniano w pytaniu?
S.Lott

Odpowiedzi:

13

Przepełnienia bufora są duże. Domyślnie nic w C nie jest sprawdzane, więc bardzo łatwo jest nadpisać bufor. Istnieje standardowa funkcja biblioteki gets(), której nie można zatrzymać przed przepełnieniem bufora i prawie nigdy nie powinna być używana.

Istnieją pewne techniki na poziomie implementacji, które utrudniają wykorzystanie, takie jak szyfrowanie bloków sterty, ale to nie powstrzyma przepełnienia bufora w lokalnych buforach, które często mogą robić interesujące rzeczy, takie jak zmiana adresu, do którego funkcja wróci.

Nie ma dobrego ogólnego rozwiązania w C. Wiele funkcji bibliotecznych ma wersje, które ograniczą ilość, którą będą pisać. chociaż obliczanie tego może być niezdarne. Istnieje oprogramowanie, które może wykryć przepełnienie bufora sterty w teście, o ile przeprowadzany jest odpowiedni test, a przepełnienie stosu często pojawia się jako awaria podczas testowania. Poza tym jest to kwestia starannego kodowania i przeglądu kodu.

Pokrewnym problemem jest problem zapisywania do bufora zbyt małego o jeden znak, zapominając, że ze względu na '\0'terminator łańcuch C o długości n znaków wymaga n + 1 znaków w pamięci . Jeśli atakującemu uda się zapisać ciąg bez terminatora, dowolna funkcja C oczekująca, że ​​łańcuch będzie kontynuował przetwarzanie, aż osiągnie zero bajtów, co może spowodować skopiowanie lub wysłanie większej ilości informacji niż jest to pożądane (lub uderzenie w chronioną pamięć dla ataku DOS ). Rozwiązaniem są ponownie: świadomość, opieka i recenzje kodu.

Z printf()rodziną jest jeszcze inne ryzyko . Jeśli kiedykolwiek piszesz char * str; ... printf(str);, ustawiasz się na problemy, jeśli strzawiera „%” po wydrukowaniu. %nDyrektywa format pozwala printf()na zapis do pamięci. Rozwiązaniem jest printf("%s", str);lub puts(str);. ( snprintf()Zamiast tego użyj C99 zamiast sprintf().)

Używanie liczb całkowitych bez znaku, szczególnie jako indeksów pętli, może powodować problemy. Jeśli przypiszesz małą wartość ujemną do niepodpisanego, otrzymasz dużą wartość dodatnią. Może to podważyć rzeczy takie jak przetwarzanie tylko N wystąpień czegoś lub ograniczone funkcje, takie jak strncpy(). Sprawdź wszystkie liczby całkowite bez znaku. Możesz tego uniknąć unsigned short, ponieważ duża wartość jednej z nich zamieni się w dużą wartość dodatnią w int.

Nie zapominaj, że stała postaci w C jest w rzeczywistości an int. Pisanie czegoś podobnego char c; while((c = getchar()) != EOF) ...może łatwo zakończyć się niepowodzeniem, ponieważ EOFnie będzie reprezentowalne w char.

Są o wiele bardziej charakterystyczne błędy C, o których mogę myśleć, ale mogą powodować problemy z bezpieczeństwem.

David Thornley
źródło
Nie trzeba używać printf("%s", str)nagiego sznurka, gdy puts(str)wykona tę samą pracę.
Blrfl
@Blrfl, ale putsdodaje znak nowej linii, podczas gdy printfnie.
prawej
Mógłby również zrobić fputs(str, stdout), co nie.
Blrfl,
Jeśli chodzi o przepełnienie liczb całkowitych: użycie podpisanych liczb całkowitych nie jest rozwiązaniem, ponieważ ich przepełnienie spowoduje UB. Jedynym (bolesnym) rozwiązaniem jest albo formalne udowodnienie, że nigdy się nie przepełnisz, albo sprawdzenie w czasie wykonywania (ale sprawdzenie poprawnie, co również jest trudne, bez przepełnienia czeku).
sleske
@DavidThornley: Standard C11 i C ++ 14 został usunięty ze standardowej biblioteki ze względu na swoją niebezpieczność.
Destructor
5

Niektóre z ryzyk specyficznych dla C obejmują: przepełnienie bufora , formatowanie ataków łańcuchowych i przepełnienie liczb całkowitych .

Nemanja Trifunovic
źródło
1
W przepełnieniu bufora nie ma nic specyficznego dla C - może to mieć dowolny język ze wskaźnikami. Przepełnienia liczbami całkowitymi dotyczą niemal każdego języka i mogą się łatwo zdarzyć również w kodzie zarządzanym.
Steve
1
@ Steve, to nie tak naprawdę wskaźniki, które powodują ten problem, ale to, jak język nie wymusza granic tablicy.
Doug T.
2
@Steve pytanie nie dotyczyło rzeczy, które dotyczą tylko C, ale czegoś, o czym programiści C powinni być świadomi.
AttackingHobo
1
@ Steve: C jest wyjątkowo podatny na przepełnienie bufora, częściowo z powodu braku obsługi sprawdzania zakresu i liczby funkcji biblioteki, które chętnie przepełnią bufory.
David Thornley,
Rozumiem, że pytanie dotyczy w szczególności C, ale myślę, że warto to wyjaśnić, jeśli odpowiedź zostanie odczytana z kontekstu, że ryzyko to jest bardziej ogólne. W szczególności programiści kodu zarządzanego są zbytnio niezadowoleni z bezpieczeństwa, a przepełnienie liczb całkowitych w szczególności wpływa na większość języków.
Steve
4

Łatwo przeoczyć ryzyko, które może powodować problemy, których naprawienie zajmie wiele godzin.

Rozważ następujący kod, który skompiluje się bez żadnych problemów.

if(lpstr_current_state = CONST_EMERGENCY_STATE_HOLY_CRAP)
{
    do_warn_joint_chiefs_of_staff_of_nuclear_attack();
}

Kiedy sprawdzisz, czy lpstr_current_statejest w CONST_EMERGENCY_STATE_HOLY_CRAPśrodku, faktycznie przypisujesz. Lepiej zawsze umieszczać zmienną stałą po lewej stronie. Po umieszczeniu stałej po lewej stronie kompilator zawiedzie, ponieważ nie można przypisać wartości do zmiennej.

if(CONST_EMERGENCY_STATE_HOLY_CRAP = lpstr_current_state)
{
    do_warn_joint_chiefs_of_staff_of_nuclear_attack();
}

Następnie możesz łatwo powiedzieć sobie: „Kurcze, to mogło być złe”, jednocześnie poprawiając kod do odczytu ...

if(CONST_EMERGENCY_STATE_HOLY_CRAP == lpstr_current_state)
{
    do_warn_joint_chiefs_of_staff_of_nuclear_attack();
}
Kristofer Hoch
źródło
7
Kompilator może to łatwo uchwycić i oznaczyć jako ostrzeżenie, w przeciwieństwie do innych problemów. Niestety nie wszystkie kompilatory ułatwiają to.
David Thornley,
2
Może się to zdarzyć w językach innych niż C, w dowolnym języku używającym =i ==.
FrustratedWithFormsDesigner
3
To nie jest tak naprawdę luka w zabezpieczeniach, to błąd.
Chris Pitman
1
Są to tak zwane warunki Yoda.
2
@Kristofer Hoch: Jeśli nazwiemy jakikolwiek prawdopodobny błąd C ryzykiem i rozważymy to tutaj, będziemy potrzebować znacznie większego forum.
David Thornley,
0

Istnieje tylko jedno zagrożenie bezpieczeństwa: fakt, że na zewnątrz są ludzie, którzy dołożą wszelkich starań, aby złapać lukę w zabezpieczeniach oprogramowania i wykorzystać je dla własnych korzyści. Wszystko inne wynika stąd.

Kiedy więc myślisz, że „nikt przy zdrowych zmysłach nie zrobiłby ...”, musisz natychmiast pomyśleć „tylko ktoś, kto chce włamać się do komputerów innych ludzi, zrobiłby dokładnie to”.

Największą konsekwencją jest to, że za każdym razem, gdy reagujesz na zdarzenia na zewnątrz (na przykład przetwarzając dane dostarczane z zewnątrz), musisz założyć, że dane te były pod kontrolą twojego najgorszego wroga.

gnasher729
źródło
Choć zgodziłbym się z akapitem drugim i trzecim, cała odpowiedzialność za atakującego jest trochę gęsta w moich oczach. Do udanego ataku zawsze potrzeba dwóch: programisty, który spieprzy i atakującego, który złapie programistę na gorącym uczynku. Jednak luka w zabezpieczeniach istnieje, zanim osoba atakująca może ją wykorzystać. I za to należy winić programistę.
cmaster