Czym jest Integer Overflow? Definicja / atak / prewencja

13 czerwca 2018, 16:00 | Teksty | komentarzy 12
: zin o bezpieczeństwie - pobierz w pdf/epub/mobi.

Pamięć komputera jest podzielona na maleńkie rejony – bajty. Jeden bajt to wystarczająca ilość pamięci, żeby przechować literę alfabetu lub małą liczbę. Każdy bajt jest podzielony na osiem mniejszych rejonów – bitów. Nazwa bitu pochodzi od połączenia słów binary digit. Bity można sobie wyobrazić jako przełączniki, które mogą być włączone lub wyłączone. W większości systemów komputerowych bity to miniaturowe komponenty elektroniczne mogące przechowywać pozytywny lub negatywny ładunek elektryczny. Pozytywny ładunek oznacza włączony przełącznik, a negatywny – wyłączony. Bajt pamięci to zestaw ośmiu bitów-przełączników ustawionych w dwóch możliwych położeniach. W efekcie wszystkie wartości numeryczne są zapisane binarnie: jako sekwencja wartości negatywnych – zer i pozytywnych – jedynek. Zaczynając od prawej strony, każda cyfra numeru binarnego ma przypisaną coraz większą wartość w postaci kolejnych potęg dwóch: 20, 21, 22, 23 itd., czyli 1, 2, 4, 8 itd. Aby określić wartość liczby binarnej, należy dodać wartości każdego „włączonego przełącznika”, czyli np. liczba binarna 10010111 posiadająca włączone pozycje o wartościach 1, 2, 4, 16 i 128, ma wartość 151. Kiedy wszystkie bity mają wartość 1, wtedy bajt przyjmuje najwyższą możliwą do przechowania w nim wartość, czyli 255. Jeżeli zachodzi potrzeba przechowania większej wartości, używa się kolejnego bajtu. Mając dwa bajty, czyli 16 bitów do dyspozycji, ostatni bit z lewej strony przyjmuje wartość 215. Dzięki temu największa wartość możliwa do przechowania w dwóch bajtach to 65535.

Każdy rodzaj danych w pamięci komputerowej musi być przechowywany jako liczba binarna. Jednak dwójkowy system liczbowy pozwala na zapisywanie jedynie liczb całkowitych (integer), rozpoczynając od zera. Liczby ujemne i ułamki mogą być zapisane przy dodatkowym użyciu schematów enkodowania. Liczby rzeczywiste są enkodowane w notacji liczb zmiennoprzecinkowych (float), a liczby ujemne wykorzystują kod uzupełnień do dwóch, co oznacza, że określenie znaku matematycznego zmiennej sprowadza się do wartości najbardziej znaczącego bitu, czyli lewego skrajnego „przełącznika”. Jeśli jest on ustawiony na 1 – zmienna jest ujemna. Jeśli na 0 – zmienna jest dodatnia. Jednak nie wszystkie zmienne używają najbardziej znaczącego bitu do określenia, czy liczba jest dodatnia, czy ujemna. Te zmienne nazywane są liczbami bez znaku (unsigned) i mogą przyjmować jedynie wartości dodatnie. Używają najbardziej znaczącego bitu jako części składowej liczby. Natomiast zmienne mogące być dodatnie jak i ujemne nazywa się liczbami ze znakiem (signed).

Typ danych Zakres (włącznie)
8-bitowy integer bez znaku 0 do 255
8-bitowy integer ze znakiem -128 do 127
16-bitowy integer bez znaku 0 do 65 535
16-bitowy integer ze znakiem -32 768 do 32 767
32-bitowy integer bez znaku 0 do 4 294 967 295
32-bitowy integer ze znakiem -2 147 483 648 do 2 147 483 647

Integer overflow

Liczba całkowita to zmienna – czyli właściwie obszar pamięci – mogąca reprezentować liczbę rzeczywistą bez ułamka. Nie-komputerowcy być może wyobrażają sobie liczby jako nieskończony ciąg, ułożony na linii prostej, jednak w branży IT można obrazowo powiedzieć, że komputer przechowuje liczby na okręgu – w ograniczonym zasobie miejsca. Większość języków programowania reprezentuje liczby całkowite jako grupy bitów o stałym rozmiarze. Stąd istnieje minimalna i maksymalna wartość, poprzez którą maszyna jest w stanie reprezentować liczbę. Kiedy liczba zostanie inkrementowana powyżej maksymalnej wartości, ta idzie dalej po okręgu i staje się minimalną. Przekroczenie zakresu liczb całkowitych (integer overflow) występuje, kiedy program próbuje przechować liczbę spoza zakresu, który docelowa zmienna może reprezentować. Na przykład 8-bitowy integer ze znakiem na większości architektur komputerowych ma maksymalną wartość 127 oraz minimalną -128. Jeśli do wartości 127 dodane zostanie 1, wynikiem będzie -128, jako że wartość przekroczy maksimum dla tego typu liczby całkowitej. W przypadku 8-bitowych liczb bez znaku, np. 186 (1011 1010) i 220 (1101 1100) dodanych i przechowywanych w 8-bitowym bajcie, wynik nie zmieści się w tym typie danych i zostanie określony na 150 zamiast na 406 (0001 1001 0110).

Próba zapisania mniejszej wartości niż liczba może przechować powoduje niedomiar (integer underflow). Zaskutkuje on tym, że interpretowana wartość przeskoczy do wartości maksymalnej dla danego typu liczby. W ten sposób można np. odejmując, w rezultacie dodać.

Kompilatory muszą czasem przekonwertować zmienną z jednego typu do drugiego. Ten proces zwany jest rzutowaniem (casting) i bywa potrzebny przy wykonywaniu pewnych operacji, takich jak porównanie obiektów różnych typów.

Podczas operacji arytmetycznych zmienna może “zatoczyć koło”, czyli największa dostępna wartość staje się najmniejszą lub na odwrót, natomiast przy zmianie typu liczb całkowitych, na skutek różnic w sposobie interpretowania bitów przez różne typy używanych danych, przekroczenie zakresu liczb całkowitych objawia się jako zmienna mająca zupełnie inną wartość niż powinna. Pośród wielu potencjalnych konsekwencji integer overflow może prowadzić do przepełnienia buforu w przypadkach, gdzie taka zmienna jest użyta przy obliczaniu ilości pamięci do zaalokowania.

Można wyróżnić trzy rodzaje błędnej interpretacji liczb całkowitych i konwersji ich typu:

  • Niedopasowanie liczb ze znakiem i bez znaku. W systemie dwójkowym, zestaw bitów reprezentujący ujemne liczby całkowite ze znakiem odnosi się też do dużych liczb bez znaku, np. ten sam 32-bitowy zestaw reprezentuje zarówno -1 jak i 4 294 967 295. Zmiana typu pomiędzy liczbą ze znakiem i bez znaku zaimplikować może drastyczną zmianę w interpretacji wartościi.
  • “Przycięcie” liczby całkowitej, kiedy liczba jest przypisana lub przekształcana na typ z krótszym zestawem bitów. Wtedy mniej znaczące bity większej liczby są używane do wypełnienia jak największej ilości bitów mniejszej liczby. Wszystkie bity, dla których nie ma już miejsca, są pominięte, zmieniając wartość wyniku.
  • Sign extension ma miejsce, gdy integer ze znakiem z krótszym zestawem bitów jest przekształcony na typ z dłuższym zestawem. Kiedy rezultat jest interpretowany jako liczba ze znakiem, wartość jest poprawna, lecz przy interpretowaniu jako liczba bez znaku, wartością będą duże liczby dodatnie.

Prewencja

Wsparcie dla zabezpieczenia przed opisywanymi defektami może zachodzić w procesorze, języku programowania lub w użytych bibliotekach. Na przykładzie Javy, należy stosować: testowanie warunków wstępnych, rzutowanie w górę, czy obiekty BigInteger. W przeciwieństwie do choćby .NET-u Java nie posiada wbudowanego mechanizmu wykrywania integer overflow, zatem czynności te wykonuje się ręcznie w kodzie. Z kolei programujący w assemblerze mają natychmiastowy dostęp do procesora i mogą sprawdzić flagę przeciążenia dostępną w większości procesorów.

Istotne jest, aby w trakcie programowania wybrać typ liczby całkowitej używany w zmiennej, który jest odpowiedni do danego zastosowania. Czasami w celu uniknięcia kłopotów, można wybrać typ, który jest zdolny do przechowania wszystkich możliwych wartości kalkulacji. W każdym razie, właściwy typ liczby całkowitej zmniejsza konieczność rzutowania typu – czołowego źródła defektów.

Atak

Integer overflow uwidoczni się, kiedy aplikacja wykona działanie arytmetyczne, którego rezultat przekroczy zakres typu danych. Błąd ten jest krytyczny np. dla aplikacji finansowych. Jego efektem może stać się DoS, uszkodzenie danych, zdalne wykonanie kodu.

W celu ujawnienia omawianej podatności służą narzędzia do analizy statycznej, fuzzingu i skanery podatności. Najpierw należy zidentyfikować punkty wejścia, poprzez które użytkownik może wprowadzić dane do przechowywania lub manipulowania nimi przez aplikację. Punkty te mogą istnieć wszędzie, gdzie aplikacja otrzymuje wartość numeryczną z zewnętrznych źródeł i stara się ją zachować lub wykonać na niej operacje matematyczne. Naturalne punkty wejścia dla testów to API, pliki wejściowe takie jak pliki danych czy konfiguracyjne, pola wejściowe UI, a także parametry URL. W dalszej kolejności dla każdego znalezionego punktu wejścia trzeba określić maksymalny rozmiar typu danych, który przechowuje wysłane tam dane. Przy użyciu tej informacji, można spróbować podstawowych testów:

  • Wysłać dane o rozmiarze identycznym z maksymalnym rozmiarem buforu, który aplikacja alokuje.
  • Wysłać jeden bajt więcej niż aplikacja alokuje.
  • Wysłać jeden bajt mniej niż aplikacja alokuje.
  • Wysłać wartość NULL.
  • Wysłać magiczne numery spod tego linku.

W rezultacie oczekiwać można zniekształconego numerycznego wyjścia danych, zawieszenia lub całkowitej awarii.

Pomimo tego, że integer overflow z reguły jest trudny do wykorzystania w złych zamiarach, może spowodować nieoczekiwane zachowanie, co zawsze jest zagrożeniem bezpieczeństwa systemu. Niebezpieczeństwo tej podatności kryje się również w tym, że wystąpienie przekroczenia zakresu liczb całkowitych jest niewykrywalne, tj. aplikacja nie jest w stanie stwierdzić, czy wynik kalkulacji jest poprawny, czy nie. Większość takich podatności nie jest do wykorzystania, jako że pamięć nie jest bezpośrednio nadpisywana ani przepływ sterowania nie jest zakłócony, jednak możliwe jest przeciążenie buforu.

Skutki ataku

Integer overflow to błąd programistyczny mogący nieść poważne konsekwencje dla bezpieczeństwa systemu komputerowego. Jego nikłość powoduje niezauważanie go podczas programowania, jednak często doprowadza on nawet do całkowitego zaprzestania działania systemu. W ostatnich 10 latach podatność na integer overflow bywała wykorzystana na dużą skalę m.in. do uzyskania uprawnień systemowych przez lokalnych użytkowników (Mac OS X Kernel), uzyskania uprawnień i dostępu do plików przez zdalnych użytkowników na docelowym systemie (Java Runtime Environment), czy wykonywania dowolnego kodu przez zdalnych użytkowników (Windows Embedded OpenType Font Engine, Windows TCP/IP, Yahoo Messenger).

Eksploatowanie integer overflow może dać następujące efekty:

  • Przy obliczaniu sumy zamówienia w sklepie internetowym integer overflow może pozwolić na zmianę wartości dodatniej na ujemną. To mogłoby nawet zakończyć się dodatkowymi środkami do wykorzystania przez atakującego po zakończonej transakcji, oprócz już zrobionych darmowych zakupów.
  • Przekroczenie zakresu liczb całkowitych podczas kalkulacji wielkości buforu przyniesie skutek w zaalokowaniu zbyt małego buforu do przetrzymania danych. Wtedy przy ich kopiowaniu może nastąpić przeciążenie buforu.
  • Wyciągnięcie 1 zł z konta z brakiem środków może wywołać niedomiar i ustalenie środków na koncie w liczbie 4 294 967 295.
  • Bardzo duża liczba dodatnia w przelewie bankowym może być przekształcona przez system backendowy na integer ze znakiem. W takim przypadku interpretowana wartość może stać się liczbą ujemną i odwrócić przepływ pieniędzy: z konta ofiary na konto atakującego.

Piotr Pawłowski

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



Komentarze

  1. Andrzej

    „Signed/unsigned” nie oznacza „podpisany/niepodpisany”, a „ze znakiem/bez znaku”. Co, jak co, ale od Was oczekiwałbym więcej profesjonalizmu. Wstyd… :(

    Odpowiedz
    • Tony Hołk

      Pytanie od amatora – tłumaczenie oznaczony lub oznakowany też jest poprawne?

      Odpowiedz
  2. Piotrek

    Niepodpisane liczby? To jest jakieś oficjalne tłumaczenie typów unsigned? Nie wierzę w to co widzę :)

    Odpowiedz
  3. Maciej Kmak

    Pierwszy raz słyszę o liczbach podpisanych i niepodpisanych (signed/unsigned). Najczęściej w literaturze spotyka się liczby ze znakiem (lub bez).

    Odpowiedz
  4. booggie

    „ostatni bit z lewej strony przyjmuje wartość 215”
    he?
    poprawcie to.

    Odpowiedz
  5. astoarti

    Może wprowadzić do artykułów oznaczenie poziomu zaawansowania – ten do kategorii przedszkole.

    Odpowiedz
    • BimBamBoom

      @astoarti,
      a może wprowadzić, checkbox podczas wypełniania komentarza:
      [x] – jestem z gimbazy

      Proponuje w czasie wolnym (a widać że masz go dużo) poczytać książkę, takie postępowanie wyjdzie dobrze dla wszystkich.

      Pozdrawiam

      Odpowiedz
      • astoarti

        Bardzo, przepraszam! Nie pomyślałem, że ten serwis czytają również osoby, które zaczynają swoją przygodę z informatyką czy elektroniką i trzeba im wytłumaczyć podstawy.

        P.S.
        W wolnym czasie co prawda nie czytałem książki ale szukałam co to jest „gimbaza”. Może wyjaśnisz bo w słowniku tego nie znalazłem.

        Odpowiedz
  6. tomasz2160927936

    Witam;Przedszkole,nie przedszkole;Po dyskusji widać coś innego. Być może jestem nadwrażliwy. Ale ja w trochę innej sprawie. Chodzi o system dwójkowy;dlaczego by nie spróbować systemu szesnastkowego?Z kąt ten pomysł?Interesuję się historią starożytną. Bardzo starożytną. I wychodzi że wtedy posługiwali się szesnastkowym system;I podobno zawsze im wychodziły liczby całkowite. Pozdrawiam.
    Ps.Wysłałem z Konqureora;posta i nie doszedł do was? został zablokowany.To nie jest pierwszy raz.Działanie jest wyraźnie wybiórcze.

    Odpowiedz
    • oioi

      impuls elektryczny jest, lub go nie ma – stąd system dwójkowy :)

      Odpowiedz
  7. tryttyk

    Bardzo fajny artykuł.

    Jak zwykle trafią się puryści językowi terminologii informatycznej po szkole humanistycznej z zacięciem czepliwego gracza, który za punkt honoru postawi sobie wskazanie niezrozumiałej dla niego składni. Spowoduje to drgawki i spazmy przed klawiaturą, skutkiem czego powstanie przelew jednej myśli i wystukanie postu w stylu – „jestem, znalazłem, mam was … jestem świetny”

    Odpowiedz
    • Yatta

      Dokładnie! Ludzie zamiast skupić się nad sensem artykułu, szukają igły w stogu siana…szkoda życia na takich ludzi…

      Odpowiedz

Odpowiedz