Лучшая практика для экранирования HTML от пользовательских данных с помощью PHP (и ZF) - PullRequest
4 голосов
/ 18 ноября 2009

Примечание: я использую Zend Framework, но я думаю, что большая часть этого относится к кодированию PHP в целом.

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

<?php echo $this->escape($this->myVariable); ?>

В дополнение к длинному коду, ИМХО автору шаблона не нужно помнить (и беспокоиться) о написании escape-вызова каждый раз, когда он хочет вывести переменную. Забывание вызова почти наверняка приведет к уязвимости XSS.

У меня есть два возможных решения этой проблемы:

Решение 1: шаблонизатор с автоматическим экранированием

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

Механизмы шаблонов на основе XML, такие как PHPTAL , также будут по умолчанию скрывать любые данные. Хотя для начинающего они могут показаться странными. Может быть, все еще стоит попробовать?

Решение 2. Сброс данных в модели

Конечно, другим вариантом будет экранирование необходимых данных, уже находящихся в модели (или даже в контроллере?). Модель уже должна знать тип содержимого (в основном простой текст или текст HTML) каждого поля, поэтому было бы логично экранировать данные там. Представление может рассматривать все данные как безопасный HTML. Это позволило бы, например. изменение типа данных поля с простого текста на HTML, не касаясь сценария представления - только путем изменения модели.

Но опять же, это не похоже на хорошую практику MVC. Кроме того, есть проблемы с этим подходом:

  • иногда представление хочет печатать только первые n символов, и мы не хотим в конечном итоге обрезать данные foo & bar как foo &am (сначала экранируя их как foo &amp; bar)
  • возможно, представление хочет создать URL-адрес с varName = $ varName в строке запроса - опять же, экранирование уже в модели будет плохим.

(Эти проблемы могут быть решены путем предоставления двух версий данных или удаления из шаблона в шаблоне. Мне это кажется плохим.)

Идеи? Я что-то пропустил? Что вы считаете «лучшей практикой»?

PS. Этот пост посвящен поиску общего решения для любых предоставленных пользователем текстовых данных, которые могут содержать < или > или любые другие символы. Таким образом, фильтрация данных перед их сохранением в базе данных не является решением.

Обновление:

Спасибо за все комментарии. Я провел еще несколько исследований и в следующий раз оценим Twig и, возможно, Open Power Template . И то, и другое кажется интересным: Twig выглядит очень просто, но проект молодой. На стороне XML синтаксис OPT выглядит немного лучше, чем синтаксис PHPTAL. И Twig, и OPT достаточно хорошо документированы.

Ответы [ 4 ]

10 голосов
/ 19 ноября 2009
  1. Фильтр как можно скорее. Вы должны убедиться, что весь ввод текста соответствует UTF-8, чтобы ваши функции манипулирования текстом работали предсказуемо.

    Но не пытайтесь отфильтровывать «опасные» символы или фрагменты! Это не работает Только исправляйте или отклоняйте неверные данные на входе. Нет ничего неправильного в < или ' символах.

  2. Побег как можно позже. Добавьте экранирование SQL в функцию SQL-запроса (или лучше - используйте подготовленные операторы). HTML-escape в ваших HTML-шаблонах. Quoted-Printable-escape в функциях генерации электронной почты, shell-escape при выполнении команд CLI и т. Д.

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

2 голосов
/ 18 ноября 2009

Но опять же, это не похоже на хорошую практику MVC.

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

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

(Многие отказываются от синтаксиса PHP и реализуют свои собственные языки ограниченных выражений, что означает, что вы теряете преимущество языка, который вы уже знаете, и застряли на языке noddy, который делает более сложную логику представления невозможной, поэтому вы в итоге делаете это себя в PHP со строками, полными HTML, что абсолютно не победа.)

Итак, на данный момент я бы предложил Решение 0a для добавления в кучу: определить глобальную функцию с коротким именем, чтобы избавиться от боли, связанной с выходом из HTML:

<?php
    function h($s) {
        echo(htmlspecialchars($s, ENT_QUOTES));
    }
?>
...

My lovely variable is <?php h($this->myVariable); ?>.

Я понятия не имею, почему PHP не определяет ярлык для этого, который, как вы говорите, является наиболее распространенным вариантом использования. Теперь они выбросили короткие теги для тегов в стиле XML-PI, почему нет одного с другим именем, которое бы правильно делало, например, скажем <?phph?

2 голосов
/ 18 ноября 2009

Это не полное решение, но одна чрезвычайно полезная вещь в такой ситуации - нотация в венгерском стиле. Венгерская нотация, используемая все время, просто раздражает, но именно в таком месте метаданные в имени переменной очень ценны. Хорошей практикой является присвоение вашим переменным префикса, который говорит, что от него ожидать ... т.е. $ rawUserInput, $ escapedUserInput и т. д.

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

'SELECT * from table where username = ' + $rawUserName

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

0 голосов
/ 20 ноября 2009

Существует дюжина способов сделать это. Вот несколько из них:

  • Вы можете написать свой собственный вид класс, как описано в Zend Framework Framework и избегайте любых переменные, когда они назначены или запрошенный из вида.
  • В случае наборов данных, вы можете обернуть их в пользовательский ArrayIterator который выводится при выходе извлечение предметов из него, а также любые другие вещи, которые вы хотите автоматизировать на выходе.
  • Или вы можете использовать Просмотр скрипта подход .
  • Или, если вы не хотите, чтобы ваш авторы шаблонов пишут любой PHP или Синтаксис шаблона вообще, вы могли бы попросите их написать только структурированный HTML, а затем вставьте значения через DomDocument расширение.

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

Вы также можете использовать Альтернативный синтаксис PHP и NowDoc или HereDoc в своих шаблонах, чтобы избавиться от вызовов <?php и echo, чтобы вы могли получить что-то как

<?php
// get some partial block done first
foreach($this->books as $book):
$loopdata = << LOOPDATA
<li> {$book->title} -  {$book->author} - {$book->publisher}</li>
LOOPDATA;
endforeach;

// render entire template
echo << HTML
<h1>{$this->title}</h1>
<ul>{$loopdata}</ul>
HTML;

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

...