Тайминг атаки срещу уеб приложения: Практични ли са все още?

timingattacks-bg
Дата на публикуване 2018-11-20

Тайминг атаките са подценявани дълго време относно тяхната ефективност в областта на уеб приложенията. Широко се счита, че поради естеството на експлоатацията си и фактът, че мрежовата латентност играе важна роля за предизвикване на голямо количество шум, те не са практични. Нашето изследване, обаче показва, че при определени сценарии те могат да се окажат ефективни и да доведат до резултати, които биха могли да бъдат по-добри от тези на стандартна брут-форс атака.

1.0. Въведение

Първо трябва да приемем факта, че атакуваното приложение е с отворен код и, че можем лесно да коригираме нашата атака. На лице са следните предпоставки:

  • Наличие на достъп до изходния код;
  • Приложението имаше уязвимост от тип pass-the-hash;
  • Използвана е слаба хеш функция (и без salt);
  • Нямаше наличен WAF/IDS/IPS;
  • Нямаше наличен reverse proxy, CDN или кеширане;

Има няколко типа тайминг атаки и в този конкретен случай ще разгледаме най-честата им форма.

Така изглежда обикновено сравнение на низове в PHP:

<?php

$key = "2562c7ad3cd09366e9884a8c93747900";

if ($_GET['key'] !== $key) {
    echo 'Forbidden.';
} else {
    echo 'Welcome.';
}

Това което се случва тук е сравнение байт по байт (използвайки оператора !==). Така функционира is_identical_function() в C. Ако разгледаме изходния код на PHP, можем да видим, че сравнението се извършва чрез memcmp():

case IS_STRING:
    if (Z_STR_P(op1) == Z_STR_P(op2)) {
        ZVAL_BOOL(result, 1);
    } else {
        ZVAL_BOOL(result, (Z_STRLEN_P(op1) == Z_STRLEN_P(op2))
        && (!memcmp(Z_STRVAL_P(op1), Z_STRVAL_P(op2), Z_STRLEN_P(op1))));
    }
    break;

Функцията memcmp() е това, което отваря възможността за тайминг атака, тъй като операцията се изпълнява, не отнема константно време. Връща се false, след като символът не е правилен, тъй като сравнява по един символ. Изходният код на memcmp(), от друга страна, е следният:

int memcmp(const void *s1, const void *s2, size_t n)
{
    unsigned char u1, u2;

    for ( ; n-- ; s1++, s2++) {
        u1 = * (unsigned char *) s1;
        u2 = * (unsigned char *) s2;
        if ( u1 != u2) {
            return (u1-u2);
        }
    }
    return 0;
}
1.1. Общ сценарий

Основната идея беше да се разделят източниците на данни и тяхното извличане. Имаме няколко източника, които могат да предизвикат проблемен шум, ако не бъдат филтрирани. Първо, трябваше да се погрижим за RTT (Round-Trip Time), което се оказа едно от най-трудните измервания.

В крайна сметка, схемата изглеждаше по следния начин: 

general-scenario-bg

1.2. Нашият план

Въз основа на предишни изследвания в тази област, имахме чувството, че има много място за подобрение. Това се дължи главно на факта, че предишни изследователи са се съсредоточили върху по-общ подход, отколкото насочване към конкретно уеб приложение и получаване на допълнителна информация въз основа на неговите специфики, както и спецификата на средата, в която се намира приложението. Нашите първоначални опити да имаме общо решение за филтриране на шума не дадоха успешни резултати при практически обстоятелства - това означава да имаме надеждни средства за идентифициране на аномалии и справянето с редица други проблеми, които могат да повлияят на наблюденията до такава степен, че събраните данни да станат буквално безполезни. Основните аспекти, върху които се фокусирахме, са:

  1. Значително подобрение на събирането на данни по мрежата;
  2. Таргетиране на конкретно уеб приложение;
  3. Намиране на действително смислен начин за анализ на данните;

Когато започнахме да избираме цел, ние се стремихме към нещо широко използвано, тъй като това вероятно означава, че има добра сигурност. Целта на атаката беше конкретно приложение, написано на PHP, и последната му стабилна версия от август 2018 г. Изненадващо, изглежда, че приложението не е толкова сигурно, колкото първоначално смятахме.

Ако трябваше да подредим целия процес в последователност, той би изглеждал така:

work-flow-bg

Какво се оказа безполезно?

Няколко неща от които не успяхме да се възползваме бяха:

  • Профилиране на приложението (xdebug, xhprof и други);
  • Измерване на RTT посредством TCP timestamps;
  • Правене на наблюдения от user-space страна;

Какво се оказа полезно?

  • Измерване на времето на отделните функции на приложението;
  • Измерване на времето за изпълнение от страна на уеб сървъра;


Основната идея беше да използваме информацията за приложението в наша полза. Тъй като имахме достъп до изходния код, знаехме точно какви операции бяха изпълнени след заявка. За да се оцени колко време отнема всяка заявка, за обработка, е от изключително значение да се съберат данни за това колко време обикновено се изисква за обработка конкретни данни. След това бихме могли да използваме това време, за да го извадим от общото време, което е отнело на сървъра да върне отговор.

1.3. Подобрения при събирането и анализа на данни

В допълнение към това, отчитайки само забавянето на двупосочния маршрут, използвахме няколко други показателя, които биха подобрили цялостното намаляване на шума и ни помогнаха да постигнем по-надеждни резултати. Основните показатели, които използвахме, са:

Профилиране на приложението: профилиране на работния поток на приложението и оценка на общото време за изпълнение на механизмите за удостоверяване (например с xdebug) за различни версии на PHP. Това е необходимо, тъй като различни версии на PHP варират в производителността. Например, има значително подобрение на цялостната производителност в PHP 7 спрямо PHP 5. Използва се за идентифициране на аномалии.

Тайминг на функциите: Изчисляване на времето за изпълнение на PHP функциите, използвани от механизма за удостоверяване на приложението. Извършва се по време на изпълнение, вместо да се извършва unit testing, така че тестовете да са оптимални и максимално реалистични.

Изчисление на делта: Отдалечената оценка на разликата между успешното (възстановяване) и неуспешното (възстановяване) време за изпълнение, необходимо за това. Най-важен показател за идентификация на аномалиите и филтриране на данните. Използва се с Калман филтър като основно средство за сортиране на събраните данни и изброяване на резултатите. Използва се по време на събиране на данните.

Изчисляването на RTT трябваше да се извърши чрез записване на пакети, но за съжаление това не би било лесно за автоматизиране. Наблюденията не трябва да се правят от потребителско пространство, тъй като това включва и времето, необходимо на ядрото да получи данните, да ги обработи и да ги изпрати до крайния потребител.

Заслужаваше да се отбележи, че използването на TCP timestamps не даде надеждни резултати, тъй като най-ниската стойност е 1 ms (милисекунда) до 100 секунди (максимална) и в нашия случай ние се интересуваме от микросекунди (нано < микро < мили < секунди). Проследяването на системните повиквания (напр. с strace (1)) също не е толкова надеждно, тъй като трябва да се работи върху проследяването на всяко повикване и идентифицирането му. Не на последно място, различните протоколи могат да бъдат рутирани по различен начин. Например ICMP може да бъде рутиран по различен начин от TCP, като по този начин използването на всеки протокол може да доведе до напълно неприложими резултати.

Друга важна част е RTTM (измерване на времето за двупосочно пътуване). Тествахме няколко начина за изчисляване на RTT, но за съжаление почти всички са основно предположения, а не реално изчисление, основано на няколко фактора. По-долу е представен резултатът от няколко инструмента.

rtt-ping-icmp

rtt-ping-icmp-avg-only

А подобрената техника за събиране на данни ще използва времевата разлика между SYN и ACK пакетите. Това е представено по-долу, където можем да видим разликата в отклоненията в данните от подобни наблюдения (по отношение на време, продължителност и проби). Графиките също така илюстрират втори пример, при който филтърът на Калман е използван за изглаждане на данните и предоставяне на окончателния набор от данни, с които да работим.

rtt-syn-ack

rtt-syn-ack-kalman


Това е примерен резултат от записани пробни пакети в който са изложени най-важните редове:

0.000978360
X.X.X.X → Y.Y.Y.Y HTTP 146 GET / HTTP/1.1


0.001722918
Y.Y.Y.Y → X.X.X.X TCP 66 80 → 33930 [ACK] Seq=1 Ack=81 Win=29056 Len=0 TSval=1737072019 TSecr=3816704019


0.004035262
Y.Y.Y.Y → X.X.X.X HTTP 481 HTTP/1.1 301 Moved Permanently  (text/html)

В този пример получаваме времето, в което е изпратена GET заявката, пакетът ACK и първият отговор на уеб сървъра (пренасочването с 301). Това може да ни помогне да намерим времето за отговор на уеб сървъра и това на приложението. Времевата разлика между ACK пакета и първия отговор от уеб сървъра е времето за изпълнение на приложението.

Трябва също така да споменем, че разчитаме на Калман филтър, за да се отървем от шума, преди да започнем да работим с данните.

Калман филтрирането, известно също като линейна квадратична оценка (LQE), е алгоритъм, който използва серия от измервания, наблюдавани с течение на времето, съдържащи статистически шум и други неточности и произвежда оценки на неизвестни променливи, които са склонни да бъдат по-точни от онези, измерване само по себе си, чрез оценка на съвместно разпределение на вероятностите върху променливите за всеки времеви период.


Идентификация на аномалии

Така че едно общо нещо, което също трябва да признаем, е идентифицирането на аномалиите. Те могат значително да усложнят целия процес на събиране на данни и по-нататъшния анализ. Следователно, при идентифицирането на аномалии могат да се имат предвид няколко подхода:

  • Клониране на таргетираната среда
    Включва копиране на отдалечената среда локално, така че да се правят наблюдения върху времето за изпълнение на уеб сървъра според неговата версия и други надеждни измервания. Това ще помогне да се идентифицират потенциалните аномалии, причинени от уеб сървъра, тъй като ще разполагаме със статистически данни за това колко време (средно) отнема на Apache да обработва заявка с определен размер.

  • Тайминг на функции
    Определяйки колко време отнема (отново средно) за дадена функция / фрагмент от код да се изпълни, ни дава измерване, което по-късно можем да извадим от общото време за изпълнение.

  • Време за изпълнение при различните среди
    Експериментирахме с различни среди и резултатите варират значително. Взехме предвид и тези данни на база локални тестове.

  • Изчисляване на средното абсолютно отклонение
    Друга стандартна метрика е стандартното отклонение и средното абсолютно отклонение. Това е по-скоро подобрен начин за получаване на "средни" стойности, в сравнение обикновена стойност за средно аритметично.

Целта? По-конкретно

Нашата цел беше уеб приложение с налична уязвимост - pass-the-hash. Като имаме възможност да предоставим хеш вместо парола, това означава, че не е нужно да откриваме хешираната стойност, за да се идентифицираме в системата. Но преди това нека да вземем самия хеш. В нашия случай имаме достъп до изходния код, така че ето как се извършва проверката на полето за парола:

function verifyPassword($userPasswordHash, $storedPasswordHash)
{
    if ( !isValidHash( $userPasswordHash ) ) {
        return false;
    }

    return $userPasswordHash === $storedPasswordHash;
}


Функцията isValidHash() верифицира дали хешът е SHA-1:

function isValidHash($hash)
{
    if ( strlen ($hash) != 40 || !ctype_xdigit ($hash) ) {
        return false;
    }

    return true;
}


Въпросът, който си задаваме тук е колко време отнема на isValidHash() да обработи конкретни входящи данни и колко време е необходимо на функцията verifyPassword() да направи същото. Входящите данни за тестовете трябва да бъдат еднакви за всяка взета метрика от двете функции. Първо измерваме времето се изпълнение локално под различни версии на PHP и след това се опитваме да го прекараме през мрежата. В крайна сметка изваждаме локално изчисленото времето на функцията от общото време, което получаваме от записването на пакета и това което получаваме за данни за шума.

Няколко груби статистически данни за атаката:

Средно имаме около 14 000 заявки на един символ. Хешът е шестнадесетичен, така че използва набор от 16 елемента: a-f0-9 и дължината му е 40 символа. Това означава, че има приблизително ~ 224 000 заявки за всяка позиция или ~ 8 960 000 заявки за пълно възстановяване на хеш.

Намаляването на пространството за търсене също е от голямо значение, тъй като би намалило значително броя на заявките и това е нещо към което трябва да се стремим. Бихме могли, например, да потърсим част от хеша в rainbow таблица за частични съвпадения, вместо да възстановяваме цялия хеш. В такъв случай, важи следното (по отношение на изчисленията):

Формула за изчисление: [брой елементи] * [брой заявки на елемент] * [дължина на низа]

При което стигаме до следните числа:

- Първите 10 символа се очаква да бъдат открити с 2 240 000 заявки;

- Първите 13 символа се очаква да бъдат открити с 2 912 000 заявки;

- Първите 20 символа се очаква да бъдат открити с 4 480 000 заявки;

Защита от тайминг атаки

Не разчитайте единствено на мрежовата латентност да предизвика шум и да възпре тайминг атака. Това няма да попречи на тайминг атаката, но ще я направи по-трудна за изпълнение. Най-ефикасният начин за борба с тайминг атаките е да се използват функции с константно време на изпълнение за критични операции като автентификация, криптография и други. Прилагането на политика за ограничаване на броя заявки също би било добра идея и би направило атаките почти безполезни, но все още теоретично възможни.


Справки

Bernstein, Daniel J. (2005). ​ Cache-Timing Attacks on AES.
http://cr.yp.to/ antiforgery/cachetiming-20050414.pdf
Department of Mathematics, Statistics, and Computer Science, The University of Illinois at Chicago

Anthony Ferrara (2014) ​ It’s All About Time
https://blog.ircmaxell.com/2014/11/its-all-about-time.html


Timothy D. Morgan, Jason W. Morgan (2015) ​ Web Timing Attacks Made Practical
https://www.blackhat.com/docs/us-15/materials/us-15-Morgan-Web-Timing-Attacks-Made-Practi
cal-wp.pdf


Scott A. Crosby, Dan S. Wallach, Rudolf H. Riedi (2009), Rice University, ​ Opportunities and Limit of Remote Timing Attacks
https://www.cs.rice.edu/~dwallach/pub/crosby-timing2009.pdf


Padraic Brady (2010), ​ Nanosecond Scale Remote Timing Attacks On PHP Applications: Time To Take Them Seriously?
http://blog.astrumfutura.com/2010/10/nanosecond-scale-remote-timing-attacks-on-php-applicati
ons-time-to-take-them-seriously/


Arwin Reprakash (2013), ​ TCP Timestamps Demystified
http://ithitman.blogspot.com/2013/02/tcp-timestamp-demystified.html


Neal Cardwell, Yuchung Cheng, Eric Dumazet (2016), ​ TCP Options for Low Latency: Maximum ACK Delay and Microsecond Timestamps
https://www.ietf.org/proceedings/97/slides/slides-97-tcpm-tcp-options-for-low-latency-00.pdf


Lawson, Nate and Taylor Nelson (2010). ​ Exploiting Remote Timing Attacks. Research Presented at Blackhat USA 2010 ​ , ​ https://www.youtube.com/watch?v=hVXP8git7A4


Mayer, Daniel A. and Joel Sandin (2014a). ​ Time Trial. Open Source Tool Presented at Blackhat USA 2014
https://github.com/dmayer/time_trial​ . — (2014b). Time Trial: Racing Towards
Practical Remote Timing Attacks. Research Presented at Blackhat USA 2014,
https://www.nccgroup.trust/globalassets/ourresearch/us/whitepapers/TimeTrial.pdf

Коментари

Няма публикувани коментари.

Нов коментар