Пуленепробиваемая система.

Как и подобает каждому приличному секуритяну, я задумываюсь иногда о большом, белом и добром — об абсолютной защите от уязвимостей. То есть, какая должна быть система, чтобы с математической точки зрения к ней нельзя было сделать exploit’ы. Сразу оговорюсь, мы говорим именно о технической стороне, а не о social engineering’е, когда человек сам отдает ключи от квартиры, где лежат деньги.

А кстати, навеяло это мне то, что блог взломали и поместили невидимые линки на какой-то сайт с рекламой всякого хлама. Забавно, что жил на старинном WordPress 2.3 больше года и не был взломан. Стоило перейти на распоследний 2.8.5 c Security updat’ами и не прошло и недели, как взломали. Но, это, так… к делу не относится.

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

Вероятнее всего, имеет смысл даже более жестко вводить разделение. Вообще, управление программой не должно быть возможно с помощью «сырых» данных. То есть, все методы условного и безусловного ветвления могут работают только на основе того, что храниться в флагах или коде. А уж только флаги в свою очередь могут быть выставлены какими-то операциями с обычными данными.

Так, что — долой архитектура фон Неймана и да здравствует архитектура Ронина (м… нужно какой-нибудь титул раздобыть).

Вторая идея, фактически продолжение первой — запрет всех интерпретируемых языков. Долой всякий SQL-injection. Увы, пока я не смог придумать, как с технической стороны можно ограничить возможность интерпретирования.

Увы, пока на этом мысль останавливаются.

Есть у кого-нибудь еще идеи и предложения по этому поводу?

P.S.1. Основная идея, не какой-то метод написания кода, который будет более стабильный, а система в которой потенциально нельзя написать код позволяющий сделать exploit. Понятно, что против SQL injection и buffer overflaw есть вполне четко описанные практики борьбы, весь вопрос, что программист должен об этом думать и может забыть/не знать и поэтому везде и есть куча дыр.

P.S.2. Отличная идея от Alex UK. Конечная state machine действительно хорошо справляется с поставленной задачей.

40 комментариев to “Пуленепробиваемая система.”

  1. e_mir:

    Слушай, а как сломали-то? И как ты это обнаружил? Мне как пользователю Вордпресса очень интересно.

  2. Аноним:

    Гарвардская архитектура это.

  3. Яски:

    Ну программную защиту можно реализовать тем способом, что ты категорично отмел — это создавать интерпретируемый или компилируемый DSL язык на котором будет писаться весь выполняемый код. Тогда машина исполнения сможет проверять все данные и фильтровать операции во время выполнения. Вторая защита нужна от человеческих ошибок (SQL инъекции), тут уж нужно составлять язык или вести разработку таким образом, чтобы код с ошибками не мог быть выполнен.

    • Victor Ronin:

      Специальный язык НЕ гарантирует на самом деле разделение кода и данных. Во первых дело в компиляторе (то есть реализации). А во вторых, в случае компиляции, если физически нету разделения данных и кода, то всегда можно найти дыры в языке или реализации, которые использовать.

      >код с ошибками не мог быть выполнен.

      Ну, это еще более нереальная задача, чем ту, которую я поставил 😉
      Одно дело, пытаться на низком уровне убрать какие-то возможности, другое дело на высоком уровне проаналировать код и оценить, что является ошибкой, а что нет.

      • Victor Ronin:

        Кстати, собственно интерпретацию я отмел именно из-за того, что ключевая идея интерпретации как раз и является то, что данные (текст программы) = исполняемый код программы. Соответственно сделать защиту получается сложнее.

        • Яски:

          Специальный язык НЕ гарантирует на самом деле разделение кода и данных.
          Ну хорошо, если физически разделить код и данные, то проблемы не будет. Но нельзя будет запускать динамически программы, раз программа это данные. А если реализовать возможность запускать программы, то эта система будет ненамного лучше того, что было без нее. В моем варианте можно запускать программы и можно будет даже сделать так, чтобы одна программа не имела доступа к данным другой программы.
          А во вторых, в случае компиляции, если физически нету разделения данных и кода, то всегда можно найти дыры в языке или реализации, которые использовать.
          Три языка — Java, .Net, ActionScript (Flash player). Все три языка являются компилируемыми сначала в байт код, затем при выполнении в ассемблер того процессора на котором выполняется программа. Предлагаю вам найти дыры в реализации любого из этих языков и выполнить произвольный код.
          Во-вторых в процессорах тоже бывают ошибки и их немного сейчас, потому что разработка стоит дофига баков и в тестирование вкладывается огромное кол-во денег.
          Ну, это еще более нереальная задача, чем ту, которую я поставил
          Одно дело, пытаться на низком уровне убрать какие-то возможности, другое дело на высоком уровне проаналировать код и оценить, что является ошибкой, а что нет.

          Если брать общий случай, то скорей всего этого невозможно допиться, но если создавать компактные DSL, близкие к решаемой задаче, то не так уж сложно.
          Например, чтобы защитить от SQL инъекций, на уровне языка должно быть известно о базе данных и о том, что такое запросы к базе.

          declarations
          // Обявляем запрос
          login = [SELECT * FROM table WHERE id={int} AND pwd={Pwd_type}]
          // Объявляем тип
          Pwd_type = \/d+{@%*!}*/d*\

          implementations
          // Делаем запрос, когда данные стали известны
          result = REQ login: GET[id], GET[pass]
          if (result) {
          // Пользователь есть в базе
          }

          И плюсом еще в языке объявлено, что каждая переменная должна содержать в себе одно значение.
          В этом виде компилятор языка следит, чтобы человек не мог ошибится, компилятор знает о проблеме инъекций и не позволяет человеку забивать на написание защиты от нее.

          • Владимир:

            В моем варианте можно запускать программы и можно будет даже сделать так, чтобы одна программа не имела доступа к данным другой программы.
            Говорят ACL делает тоже самое. Но надежнее все же не запускать программы.

            Предлагаю вам найти дыры в реализации любого из этих языков и выполнить произвольный код.
            http://wasm.ru/article.php?article=unsafe_ii

            И плюсом еще в языке объявлено, что каждая переменная должна содержать в себе одно значение.
            Первый минус:
            declarations
            login = [{Query}]
            Query = \.*\

            Минус второй: существование дубликатов типов. Все типы уже объявлены в базе и дублирование это еще один способ выстрелить себе в ногу.
            Как видно компилятор совсем не следит, но считает, что знает о проблеме инъекций.

            PS: письма приходят в текстом с html-тегами.

            • Яски:

              http://wasm.ru/article.php?article=unsafe_ii
              Это не взлом Java, это работа с ней на очень низком уровне. Точно также никто не запрещает писать на программы на ассемблере, но даже с ассемблером вы не сможете прочитать данные из защищенной области памяти.
              Первый минус:
              declarations
              login = [{Query}]
              Query = \.*\

              Минус второй: существование дубликатов типов. Все типы уже объявлены в базе и дублирование это еще один способ выстрелить себе в ногу.
              Как видно компилятор совсем не следит, но считает, что знает о проблеме инъекций.

              А, я забыл дописать, что компилятор проверяет запросы на валидность и типы столбцов тоже учитываются. Т.е. писать тип в программе не обязательно, но желательно. Также запросы, которые нельзя проверить на этапе компиляции не поддерживаются.
              Надеюсь, вы понимаете, что можно придумать любые условия на что только фантазии хватит, поэтому давайте обсуждать сам способ, а не нюансы реализации.

              • Владимир:

                Сам способ хорош. Главное что компилятор может отличить имя поля от параметра (а значит с сортировкой проблем не будет) и разбирается во вложенных запросах.
                Правда не ясен способ генерации им запросов если запрос зависит от параметров (мало ли вдруг поле для поиска пустое или диапазон цен не задан).

                Если я правильно понимаю, компилятор что-то делает с базой такое, что к ней кроме как через него не подключиться и на неизвестные ему ситуации он сильно ругается. Так как единственный способ защититься это запретить все, что не разрешено.

          • Victor Ronin:

            >В моем варианте можно запускать программы и можно будет даже сделать так, >чтобы одна программа не имела доступа к данным другой программы.

            Честно говоря, я не слишком понял как предложенная вами идея связанна с разделением доступа.

            Относительно примера, который вы привели.
            Например в Pwd_type не указана длинна. Что произойдет, если пользователь передаст длину больше чем максимальный размер буфера?

            Собственно говоря, я пытаюсь придумать систему, которая _врожденно_ не будет иметь уязвимостей. То есть, такая система, в которой программисту не нужно думать ничего ли он не упустил в Pwd_type, что можно будет использовать во зло.

            • Яски:

              Честно говоря, я не слишком понял как предложенная вами идея связанна с разделением доступа.
              Основная суть в том, что программа выполняется в написанном нами виртуальном мире и физически не может выполнить того, что мы не написали.
              Например в Pwd_type не указана длинна. Что произойдет, если пользователь передаст длину больше чем максимальный размер буфера?
              Это не Си++, это написанный нами специальный язык. Работу со строками выполняет наша виртуальная машина, которой всегда известна длина строк и размер буфера тоже. Мы не даем пользователю возможность управлять внутренней реализацией строк и он не может ничего сломать. Посмотрите как устроен Java, C# или еще какой подобный язык — там совсем нет прямой работы с памятью и со строками, вся работа с памятью и со строками ведется «умной» виртуальной машиной, чтобы тупой программист ничего не мог сломать.

              • Victor Ronin:

                Да, согласен. Проблему переполения буфера это решает. Что-то я зарапортавался…

  4. Владимир:

    Давайте все писать на С++. Хотя нет… На Хаскеле или Аде.
    Хотя для веба можно обойтись набором оберток над входными данными и бритвой Оккама.
    Например так: http://api.drupal.ru/api/function/db_query/6

    • Victor Ronin:

      Чуть выше в комментариях уже писал, что специальный язык не гарантирует защиту от уязвимостей.

      • Владимир:

        Не специальный язык, а соглашение.

        • Victor Ronin:

          Ну, соглашение — это еще хуже.

          Физическая платформа не дает никаких возможностей ошибиться и заставляет делать все определенным (нужным) образом.

          Язык дает достаточно много возможностей ошибиться, но имеет хоть какие-то возможности заставить делать определенным образом.

          Соглашение — это все лишь, то как хорошо бы делать, но заставить оно никого не может.

          • Владимир:

            Согласен. Если есть деньги можно купить все на стороне. А если денег нет придется изобретать велосипеды.

  5. genberg:

    Лучшая защита это отключить сетевой кабель и выключить вайфай 🙂

    Проблема, имхо, будет существовать всегда, поскольку чем больше возможностей представляет система для легального пользователя, те больше возмоностей использования системы в нелегетимном режиме.

    Также как проблема воровства так и не решена од сих пор, не смотря на сотни лет развитой человеческой цивилизации.

    • Victor Ronin:

      Еще надо отключить USB порты, клавиатуру, мышь, CD-ROM, а лучше всего кабель питания 🙂

      Да естественно я понимаю, что решить окончательно — нереально. Это ж я все за потрепаться.

      Хотя я не согласен, что это эквивалентно проблеме воровства. Все же компьютер можно сделать детерминированной системой с конечным количеством состояний и фиг такое взломаешь. А персональная собственность (которую воруют) существует совсем не в детерминированной системе.

  6. genberg:

    Как много ошибок, сорри, только проснулся.

    • Яски:

      А я и не заметил, когда читал в первый раз. Мозг не воспринимает пропущенные буквы.

  7. Возможно более безопасным будет путь обьединения данных и действий над этими данными по типу coachdb (или ООП БД типа Cache) — есть строго определенная последовательность действий, которые программа может делать над уже определенными типами данных в БД. Если для данных не определена последовательность действий которую злоумышленник хочет выполнить, то он идет лесом. Для того чтобы записать новую последовательность действий (SQL-injection) уже нужно быть админом и т.п.

    • Victor Ronin:

      Если это дело обобщить — то мы получаем машину состояний. И по большему счету только по ней можно двигаться.

      Кстати, вынесу ка я это в пост.

      • Владимир:

        А если еще дальше продолжать то машина состояний есть ничто иное как виртуальная машина. Останется подождать пока будет изобретены структуры данных и основы программирования для такой машины. А потом и библиотеки позволяющие делать тоже, что и обычные языки программирования, но в пространстве машины состояний. История к тому моменту уже покажет «необходимость» машины состояний и отказаться от нее будет нельзя.

  8. По поводу SQL-injection (уж больно знакома тема): эту проблему решает целиком и полностью использование т.н. bind-переменных. Т.е. когда запрос составляется не конкатенацией строк с подстановкой туда параметров, а путем объявления в запросе переменных с дальнейшей подстановкой их значений.

    • Victor Ronin:

      Проблема состоит в том, что МОЖНО написать код, который позволяет сделать SQL-injection. А если его можно написать, то его кто-то напишет. И получается, что защищенность система зависит от умений программиста. Вот от этого и хочется уйти. Чтобы система сама по себе была защищенной.

      • Владимир:

        И мне хочется. Я бы столько мартышек нанял. Эх мечты мечты…
        В сторону: О найме их заметка вроде уже была.

        • Яски:

          Да, блин, я уже это внедряю даже. Посмотрите в сторону http://en.wikipedia.org/wiki/Language-oriented_programming. Суть в том, что серьезный программист (Системный Архитектор) пишет язык близкий к решаемой задаче, а менее опытные программисты решают с помощью него практические задачи. Я хочу использовать для этого Tamarin — это виртуальная машина с открытым кодом, выполняющая программы на ActionScript 3. Ядро системы буду писать я сам, другие программисты будут писать на этом ядре остальную часть большого приложения.

          • Владимир:

            Это уже другая тема. В которой «менее опытные программисты» вообще-то более опытные (или как минимум такие же).
            Здесь же речь идет о том, что взять тех кого наняли в этом посте http://victorronin.com/2009/01/30/o-tupyx-programmistax/ и заставить их писать стабильные программы. Создание еще одной прослойки лишь поднимет уровень ошибок на новый уровень, а не исключит их.

            • Яски:

              По мне так тупые программисты не нужны, это не та область, где им место. Но я знаю неглупых людей, которые не являются лучшими программистами, но в тоже время способные писать работающие программы. Я таким образом вижу логичное разделение труда — мне важно, чтобы не было ошибок в программе и соблюдались все тонкости работы с системой, поэтому я пишу ядро и настраиваю виртуальную машину. Другим программистам важно выполнять свою работу в срок и писать работающие программы, поэтому они пишут прикладные программы с использованием моего ядра. При этом каждый выполняет ту работу, которая ему ближе по душе и которая у него получается лучше.

      • на удивление, есть такому пример: OpenBSD — 5 лет без уязвимостей. Причем самое забавное в том, что уязвимости есть в отдельных модулях ядра, системном и прикладном ПО, но система построена таким образом, что в целом система не уязвима.
        А насчет SQL-injection, можно сделать прекомпилятор, который будет анализировать код на наличие динамического SQL и, в случае наличия такового, давать ошибку.

        • Victor Ronin:

          С сайта Wiki по поводу отсутствия уязвимостей.
          «This statement has been criticized because little is enabled in a default install of OpenBSD and releases have included software that was later found to have remote holes»

          Ну и само собой, одно даже secure операционная система не обозначает secure программ на ней выполняющихся.

  9. Полностью безопасную систему построить невозможно, ошибки в коде как самой ОС, так и в прикладном программном обеспечении были, есть и будут всегда. Все мы люди, все мы глючим, исключений нет. Пулей не пробьют- подтащат 152-мм гаубицу на прямую наводку.

    Вопрос надо ставить не так (пуленепробиваемая система), правильная постановка задачи такова: хорошо, произошёл пробой нашей IT-инфраструктуры, как мы можем минимизировать либо полностью исключить потери с нашей стороны? И вот тут как раз непаханное поле…

    • Victor Ronin:

      В целом конечно согласен. Но все таки, если делать систему из стекла, то даже пуленепробиваемую ее тяжко сделать, если делать из стали, то при определенной толщине и гаубица не пробьет.

      В целом, таки важно делай как можно более безопасную основу, а дальше уже можно заботиться и о минимизации потерь.

      • Да, безусловно стоит делать свой софт как можно более безопасным способом. А если софт (Windows, WordPress,…) не твой? И никакого влияния на разработчиков оказать невозможно? Тогда остаётся только делать надстройки для минимизации ущерба (чем я, собственно, и занимаюсь).

  10. Alexey:

    Был такой проект, Verisoft называется. Вот он непробиваемый. Там формально доказывалась корректность работы всего — начиная от процессора, компилятора и тд, заканчивая софтом. Как реальное приложение — бортовой комп bmv x5.

    • Victor Ronin:

      Интересно.

      Правда, для бортового компьютера честно говоря пуленепробиваемость не слишком и нужна. Количество входов и выходов достаточно ограниченное.

  11. genberg:

    Еще недавно в сети проскакивала статья про то, как разрабатывается софт в одной из космических программ (увы, не могу найти ссылку).

    Так там именно, что каждое выражение математически доказывается. При этом ресурсы на разработку увеличиваются на порядок по сравнению с со средним значением в коммерческом секторе.

    Так да, любой дурак безопасную систему напишет 🙂

    • Victor Ronin:

      То, что я читал, что там скорее не математически доказывается, а просто есть сумашедшее количество стандартов и просмотров кода.