Совместное использование принципов MVC и ООП. Возвращаемся к реализации MVC. Эксплуатация удалённого внедрения файлов

Представьте сайт, состоящий из двух страниц. На главной старице отображается некоторый текст:

Вторая страница представляет собой его редактор:


На ней мы можем изменять содержание главной страницы и сохранять изменения.

В верхней части страниц расположена "шапка" сайта, которая состоит из двух элементов. Первый из них неизменен и отражает название сайта. Второй содержит название текущей страницы: либо "Чтение", либо "Редактирование".

Ниже шапки расположено меню, которое одинаково для всех страниц сайта и содержит ссылки на эти страницы.

Также неизменным остается "подвал" страницы, в котором отображается дополнительная информация о сайте, например, адрес, телефон, информация о создателе страницы.

Центральную часть станицы "Чтение" занимает текст, который мы можем задавать на странице "Редактирование", используя обычную HTML-форму с элементом textarea

Исходные положения

Нам предстоит объединить два подхода - MVC и ООП. Для этого при разработке архитектуры системы будем руководствоваться следующими правилами.

  • Согласно шаблону проектирования MVC файлы контроллеров, моделей и представлений будут разнесены по трем отдельным каталогам.
  • Каждый контроллер будет представлен отдельным файлом, в котором будет объявлен класс контроллера, инкапсулирующий логику работы контроллера и предоставляющий методы для обработки HTTP-запроса пользователя.
  • Каждой странице сайта будет соответствовать отдельный контроллер, отвечающий за ее вывод.
  • Модели системы также будут реализованы в виде классов.
  • Представления будут реализованы в виде обычных PHP-файлов с HTML- разметкой с вкраплениями PHP-кода для отображения значения переменных сценария.
  • HTTP-запросы к системе будут осуществляться через специальную точку входа - файл index.php.
  • Хоть эта тема уже и обсуждалась, повторим, что следует четко представлять различия между назначением классов контроллеров и классов модели. Классы контроллеров служат для того, чтобы обеспечить обработку HTTP-запроса пользователя, т. е. получить параметры входного запроса, и подготовить результирующую HTML-страницу. При выполнении этой работы контроллеры задействуют классы модели, которые представляют отдельные функциональные блоки: работу с базой данных, работу с файлами, работу с аккаунтами пользователей и т. п. Каждый такой логически связный блок представляется отдельным классом. Например, в вашей системе может быть класс для работы с базой данных, который будет хранить дескриптор текущего соединения с БД и предоставлять набор методов для выполнения запросов к БД и получения различных наборов данных.

    Точка входа

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

    В нашем примере мы также будем пользоваться единой точкой входа - файлом index.php. Давайте рассмотрим его содержание:

    Давайте разберем работу данного сценария построчно.

    Include_once("inc/C_View.php"); include_once("inc/C_Edit.php");

    Первые две строчки подключают файлы контроллеров для страниц "Чтение" и "Редактирование".

    Switch ($_GET["c"])

    Здесь происходит анализ GET-параметра с именем c :

    Case "edit": $controller = new C_Edit(); break;

    Если данный параметр хранит строку "edit ", тогда переменной $controller присваивается объект класса C_Edit , описание которого находится в файле inc/C_Edit.php , подключенном в самом начале сценария.

    В случае если параметр с не задан или имеет значение, отличное от "edit " , выполняется другая ветка оператора switch :

    Default: $controller = new C_View();

    Здесь переменной $controller присваивается экземпляр класса C_View .

    Наконец, последняя строка вызывает метод Request() у созданного объекта:

    $controller->Request();

    Обратите внимание, здесь мы можем увидеть типичный пример полиморфизма . Переменная $controller может хранить как экземпляр класса C_Edit , так и экземпляр класса C_View . Поэтому мы не можем заранее знать, метод Request() какого именно класса будет вызван в сценарии. Важно лишь то, что для корректной работы сценария оба класса должны иметь данный метод.

    В целом же задача точки входа проста - передать управление одному из контроллеров. Какому именно, определяется с помощью параметра GET-запроса с именем с .

    Иерархия контроллеров

    Давайте приступим к проектированию системы и для начала определим, какие контроллеры будут в ней присутствовать. Из файла index.php видно, что в системе есть как минимум два контроллера, представленные в виде классов C_Edit и C_View . Задача класса C_Edit - организовать вывод страницы "Редактирование", класса C_View - страницы "Чтение". Обработка входных параметров и вывод страницы инкапсулируется внутри метода Request() , который должен присутствовать как в классе C_View , так и в C_Edit . Это может натолкнуть на мысль о создании общего родительского класса для этих двух контроллеров. И действительно, реализация С_View будет во многом совпадать с C_Edit , поэтому выделение родительского класса базового контроллера - это полезное действие.

    Таким образом, мы пришли к мысли о необходимости создания базового класса контроллера. Назовем его C_Base . Он будет абстрактным и не будет иметь конкретных реализаций. Мы вводим его для того, чтобы другие классы контроллеров наследовались от него. Класс C_Base будет хранить базовые свойства и методы, характерные для каждого контроллера данного сайта.

    Давайте поразмышляем , какие параметры являются общими для каждой страницы сайта? Во-первых, это название сайта. Во-вторых, текст, расположенный внизу каждой страницы. Сюда же можно отнести пункты меню, которые одинаковы для каждой страницы сайта. Обобщая, можно сказать, что контроллер C_Base отвечает за базовый шаблон сайта.

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

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

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

    Не будем томить и продемонстрируем иерархию классов контроллеров, которую мы вам предлагаем принять за образец:

    Данная диаграмма используется в описательном языке UML и называется диаграммой классов. Каждый прямоугольник на ней соответствует отдельному классу. В верхней части прямоугольника жирным выделено название класса. Стрелками показаны отношения наследования. Курсивом выделены свойства классов, прямым шрифтом - его методы. Знак – перед свойствами или методами говорит о том, что свойство или метод объявлены с модификатором private , знак # соответствует модификатору protected ,+ - модификатору public .

    Класс Controller

    Controller является базовым классом контроллера, который призван инкапсулировать самые общие элементы, характерные для контроллеров любых сайтов. Сюда мы вынесли ряд вспомогательных функций:

  • Template() - для подстановки в шаблон набора переменных и вывода его на экран;
  • IsGet() - для проверки, был ли выполнен GET-запрос;
  • IsPost() - для проверки, был ли выполнен POST-запрос.
  • В классе Controller присутствует объявление уже известного нам метода Request() .
    Давайте посмотрим на его реализацию:

    Public function Request() { $this->OnInput(); $this->OnOutput(); }

    В методе Request() мы делим обработку запроса на две части: обработку входных данных (метод OnInput() ) и формирование результирующей страницы (метод OnOutput() ). Такое разделение довольно удобно, и мы предлагаем его использовать и в будущих проектах. Методы OnInput() и OnOutput() должны быть переопределены в дочерних классах.

    Класс C_Base

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

    Кроме того, в классе C_Base нужно переопределить методы OnInput() и OnOutput() и вынести в них действия, общие для всех контроллеров сайта. Контроллеры, которые будут наследоваться от класса C_Base , будут переопределять эти методы, но они смогут вызвать родительские методы с помощью ключевого слова parent .

    Мы предлагаем следующую реализацию методов OnInput() и OnOutput() в классе C_Base .

    Protected function OnInput() { $this->title = "Главная страница"; $this->content = ""; } protected function OnOutput() { $vars = array("title" => $this->title,"content" => $this->content); $page = $this->Template("main", $vars); echo $page; }

    В методе OnInput() мы можем инициализировать переменные шаблона значениями по умолчанию. В методе OnOutput() мы сначала формируем массив переменных шаблона, а затем используем метод Template() , который определен в классе Controller и отвечает за подстановку переменных в шаблон. Его реализация остается за вами. По нашей задумке метод Template() принимает два параметра: первый из них - название подключаемого шаблона, второй параметр - это массив переменных, которые должны быть подставлены в шаблон. В завершение сформированная страница выводится на экран. Как вы понимаете, это последняя операция, которую должен выполнить сценарий. Поэтому функция OnOutput() класса C_Base должна вызываться в самом конце нашего PHP-сценария.

    Контроллеры C_View и C_Edit

    C_Viev и C_Edit - контроллеры конкретных страниц сайта, они переопределяют методы OnInput() и OnOutput() , но также передают управление контроллеру C_Base .

    Давайте рассмотрим пример реализации этих методов для класса C_View .

    Protected function OnInput() { parent::OnInput(); $this->title = $this->title . " :: Чтение"; $this->text = $this->getText(); } protected function OnOutput() { $vars = array("text" => $this->text); $this->content = $this->Template("theme/v_view.php", $vars); parent::OnOutput(); }

    В методе OnInput() сначала идет вызов родительского метода, затем определяется заголовок страницы и извлекается ее текст с помощью метода getText() . Конкретная реализация метода getText() для нас сейчас не важна. Он может получать текст из базы данных или из файла, или же вовсе возвращать некоторую предопределенную константу. Важно то, что в результате мы сохраняем текст в некотором поле класса.

    Метод OnOutput() начинается с формирования массива, который передается в функцию формирования шаблона Template() . Результат ее выполнения сохраняется в поле content . После чего вызывается родительский метод OnOutput() . Вернитесь к тексту метода OnOutput() класса C_Base и обратите внимание на то, что в нем как раз-таки используется значение поля content , который мы формируем в текущем методе OnOutput() класса С_View .

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

    Цикл обработки запроса

    Теперь давайте посмотрим на общий вид обработки запроса, который мы получили. Выполнение сценария начинается с файла index.php. Здесь выбирается нужный контроллер, и ему передается управление вызовом метода Request() .

    Этот метод запускает две фазы обработки запроса:

  • Обращение к модели (OnInput());
  • Генерация HTML (OnOutput()).
  • Первая фаза должна отработать в такой последовательности:
  • Базовый контроллер (C_Base);
  • Конкретный контроллер (C_View или C_Edit).
  • Далее начинается фаза генерации HTML, она должна пройти в обратной последовательности:
  • Конкретный контроллер (C_View или C_Edit);
  • Базовый контроллер (C_Base).
  • На диаграмме данный цикл можно представить так:


    В заключение

    На этом мы завершаем рассказ о применении ООП совместно с концепцией MVC . Оставшиеся функциональные блоки сайта вам предстоит реализовать самостоятельно. Не пугайтесь высокой сложности задания. Постепенно новые концепции станут для вас знакомыми, и вы сможете намного более уверенно в них ориентироваться.

    Программирование еще никому не давалось без усилий. С каждым днем практики, с каждым новым PHP-сценарием вы набираете собственный бесценный опыт, который в будущем позволит вам быстро, грамотно и эффективно разрабатывать интернет-системы любой сложности. Залог такой возможности - это профессиональный подход к веб-разработке.

    LFI - это возможность использования и выполнения локальных файлов на серверной стороне. Уязвимость позволяет удаленному пользователю получить доступ с помощью специально сформированного запроса к произвольным файлам на сервере, в том числе содержащую конфиденциальную информацию.

    Проще говоря, это уязвимость открытия файлов с сервера + недостаточная фильтрация, что позволяет открывать произвольный файл.

    Поиск file inclusion

    Вначале разберемся как определить параметры которые отвечают за инклуд файлов.

    Поиск параметров
    Для поиска параметров есть два варианта: автоматический или ручной поиск.

    Автоматический поиск
    Автоматический поиск можно осуществить тем же spider"ом в burpsuite. Вы можете у нас в вики найти статью burpsuite.

    Ручной поиск
    Сейчас я поговорю о ручном поиске. Предположим, что мы нашли GET параметр:

    Http://site.ru/folder/index.php?file=gallery

    Подставим под параметр строку "index":

    Http://site.ru/folder/index.php?file=index

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

    Именно такие параметры нам и нужны.

    Определение фильтров
    После того, как мы получили список параметров, нужно проверить, есть ли у них фильтрация.

    Нулевая фильтрация
    Попробуем подкачать файлы, которые не рассчитывали показывать=)

    Аналогом такого файла в линкусе является файл /etc/passwd

    данном случае за строку с параметром мы взяли http://site.ru/folder/index.php?file=index.html)

    Попробуем его подкачать:

    Http://site.ru/folder/index.php?file=/../../../../../../etc/passwd

    Объясняю что происходит - переход в папку /../ означает поднятие по иерархии вверх (точнее это уязвимость path traversal). Т.к. папка etc лежит в корневой папке, то мы должны ее достичь угадыванием: то есть чем чаще мы поднимаемся вверх, тем выше шанс, что мы окажемся в корневой папке (то мы должны написать несколько раз /../).

    Если файл показался. То считайте, что вы нашли LFI. В этом случае фильтр вообще отсутствует.

    Нулевой байт
    В данном случае за строку с параметром мы взяли http://site.ru/folder/index.php?file=index , то есть с отсутствующим окончанием.
    Но даже при отсутствии фильтра могут быть проблемы. Например в конце параметра может приписываться окончание.

    Например с запросом /../../../../../../etc/passwd может преобразоваться в

    /../../../../../../etc/passwd.php

    Но и на этот раз есть вариант исправить строку.В старых версиях PHP остался такой недостаток, как Null Byte Injection .
    Один из них - это приписывание нулевого байта. Параметры, при передаче по http, зашифровываются в url шифрование. И в этой кодировке нулевой байт выглядит именно в %00.

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

    В данном случае если мы впишем в параметр /../../../../../../etc/passwd.php%00 , то получим следующую строку:

    Http://site.ru/folder/index.php?file=/../../../../../../etc/passwd%00

    И строка в памяти сервера будет выглядеть как:

    /../../../../../../etc/passwd%00.php ==> /../../../../../../etc/passwd

    И в итоге мы смогли отбросить окончание и получить нужный файл.

    String limit
    Еще один вариант отбрасывания окончания возможен при String Limit - укорачиванию строки.
    И какая же от этого польза? А что если мы отбросим часть строки с окончанием, то получится нужная нам строка,но уже без окончания.

    Уже на этот раз нам может помочь строка /./ . Объясняю, что происходит:

    /./././././index === index

    Если точнее, то в bash эти две строки идентичны. Приведу пример, как это может помочь

    1) У нас есть параметр, строка которого укорачивается до 100 символов 2) Попробуем вывести файл index.txt, при условии, что приписывается окончание.php 3) Попробуем ввести index.txt - в итоге выводится index.txt.php 4) Чтобы обойти защиту, нужно ввести index.txt/././././../....../././ 5) В итоге получается, что к этой длинной строке приписывается.php, которое в последствии отбрасывается 6) Profit!

    php filter
    По мне самый интересный вариант lfi является lfi с php filter. Сразу привожу пример

    Http://site.ru/folder/index.php?file=php://filter/convert.base64-encode/resource=index

    В итоге у нас в браузере не запустится php файл, а выведется его base64 исходников.
    Это в последнее время появляется на соревнованиях все чаще и чаще.

    Эксплуатация уязвимости

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

    Получение важных файлов (не скриптов)
    Ну самое типичное для lfi - скачивание файлов, которые мы, по задумке администратора, не должны были скачивать. Приведу пример задания:

    Задание: Дан файлообменник, есть несколько аккаунтов - admin и user(и паролем test). Нужно скачать файл flag.txt, хранящийся у аккаунта admin. Решение: 1) Загрузим свой файл, и посмотрим на ссылку на его скачивание. Она будет вида http://site.ru/download.php?file=user/image.png 2) И в правду папка user существует. Но при скачивании по ссылке http://site.ru/user/image.png идет ошибка 403, что вполне логично. 3) На всякий случай составим ссылку с однозначно отсутствующим файлом в папке user, и если ответ будет 404, то понимаем, что ответ 403 == ответу 200. 4) Проверим, верно ли, что наш файл должен быть по пути admin/flag.txt: http://site.ru/admin/flag.txt возвращает 403 (вспоминаем предыдущий пункт). 5) А почему бы, раз скачать не можем, не направить скрипт download.php на нужный нам файл? Пробуем перейти по http://site.ru/download.php?file=admin/flag.txt и получаем файл. 6) Profit!

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

    LFI -> RCE
    Т.к. этот раздел уже переходит в более опасный раздел RCE, то вполне логично перенести дискуссию по этому поводу в нее. Ищите на сайте =)

    Множество примеров

    SharifCTF 2016 - technews

    Решение

    1. Смотрим путь до картинок, открываем напрямую папку /files/ и видим папку /flag/ 2. Проверяем существование файла /files/flag/flag.txt (403 ошибка) 3. Проверив burpsuit"ом замечаем, что некоторые картинки подкачиваются как images.php?id=files/images/heart.jpg 4. При images.php?id = php://filter/convert.base64-encode/resource=files/images/heart.jpg картинка возвращается в незашифрованом виде, что наводит на мысли регулярного выражения. 5. Изучаем форму и обходим регулярку с помощью запроса: images.php?id=php://abcdresource=files/flag/heart.jpg/resource=files/flag/flag.txt

    В этом руководстве Вы узнаете, как построить простую систему по архитектуре MVC (Model-View-Controller, Модель-Отображение-Контроллер) на PHP 5.1 с использованием возможностей библиотеки SPL (Standard PHP Library, Стандартная Библиотека PHP).

    Введение

    Добро пожаловать в первое полноценное руководство для PHP 5. Вам понадобится PHP 5.1 с установленной библиотекой SPL, так как мы воспользуемся некоторыми из самых последних возможностей PHP 5.

    В этом руководстве я собираюсь показать, как построить простую MVC-систему (архитектура MVC является наиболее распространённым шаблоном проектирования для больших web-приложений). Я проведу Вас через все шаги от начала и до конца создания полноценной MVC-системы.

    Одна точка входа

    Одной из важных вещей в MVC является одна точка входа в приложение вместо кучи PHP-файлов, делающих примерно следующее:

    У нас будет один файл, обрабатывающий все запросы. Это значит, что нам не придётся мучиться с подключением global.php каждый раз, когда нам нужно создать новую страницу. Эта «одна точка входа» будет называться index.php и на данный момент будет такой:

    Как Вы можете заметить, этот скрипт пока ещё ничего не делает, но погодите минутку.

    Чтобы направить все запросы на главную страницу, мы воспользуемся mod_rewrite и установим в.htaccess директиву RewriteRule. Вставим следующий код в файл.htaccess и сохраним его в той же директории, что и index.php:

    RewriteEngine on RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ index.php?route=$1

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

    Если у Вас нет возможности использовать.htaccess или mod_rewrite, то Вам придётся вручную адресовать все запросы к index.php. Другими словами, все ссылки должны будут иметь вид «index.php?route=[здесь-идёт-запрос]». Например, «index.php?route=chat/index».

    Теперь, когда все запросы идут через одну точку входа, мы можем начать написание скрипта index.php. Первая вещь, которую мы должны сделать, это инициализация системы. Создадим директорию includes, а в ней файл startup.php (он будет у нас файлом инициализации). Вставим следующий код в index.php:



    
    Top