parser

Небольшой ликбез по taint/untaint

Misha v.3 [09 августа 2002]

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

Прежде всего следует упомянуть, что есть данные разной степени «испачканности». Буквы, которые написаны в теле документа (класса, auto.p) — являются «чистыми», а пришедшие из формы, кук, файлов, sql запросов и т.д. — «грязными» (но без указания, на каком они языке).

Итак: эти операторы не производят никаких замен символов в месте их написания.

И хотя следующие два метода при их вызове выведут в окно броузера разные результаты, при их сравнении они будут равны!

@test1[sText]
$result[$sText]

@test2[sText]
$result[^taint[html][$sText]]

@main[]
# буквы '<b>test</b>' написаны руками в коде, поэтому они являются «чистыми»,
# т.е. парсер им доверяет как самому себе, пока вы не скажите ему действовать иначе
$sText[<b>test</b>]

test1: '^test1[$sText]'<br />
test2: '^test2[$sText]'<br />

^if(^test1[$sText] eq ^test2[$sText]){ОДИНАКОВЫЕ}{РАЗНЫЕ}

Так почему в окне броузера мы видим разные результаты?

Все просто: оператор taint в методе test1 «пачкает» переданный ему текст, и в дальнейшем парсер знает, что эти буквы написаны на языке html. А буквы, когда мы изначально присваивали переменной в теле страницы и которые впоследствии вернул метод test2 — «чистые».

«—И что?» — спросите вы... «—Когда происходит замены символов '<' и '>' на '&lt;' и '&gt;'?»

Замена происходит в самый последний момент: перед передачей парсером данных наружу (в броузер, SQL серверу, отправке по почте...).

В примере выше в самый последний момент (перед выдачей страницы в броузер посетителя) парсер нашел все «грязные» кусочки и заменил их, таким образом «грязные» буквы возвращенные методом test1 преобразовались, а «чистые», возвращенные методом test2 — нет.

И зачем нам это нужно?

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

$tСoder[^table::sql{SELECT name FROM people WHERE description LIKE '%кодер%'}]

У вас SQL серверу запрос будет передан именно в таком виде, как вы написали, потому что буквы 'кодер' вами набраны в документе, парсер им доверяет и никаких преобразований делать с ними не будет.

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

Вы напишите:

$tСoder[^table::sql{SELECT name FROM people WHERE description LIKE '%$form:search%'}]

И тут вступает в работу механизм тайнтинга... Данные, пришедшие из формы не являются чистыми! Поэтому перед выдачей их в sql запрос парсер произведет замену специфических символов в тексте, пришедшем из формы. Например, он поставит префиксы перед кавычками и др. (см. документацию)

Что нам это дало? У нас запрос сохраняет работоспособность даже если пользователь введет в форме слово с одиночной кавычкой (или очень нехороший пользователь напишет там ' OR 1 = 1 OR '). Если бы не было механизма тайнтинга, то вы получили бы ошибку о неверном синтаксисе запроса, и чтобы устранить ее пришлось бы писать дополнительный код.

Теперь немного про отличия taint от untaint.

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

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

$sText[<br />]
^untaint[html]{$sText}

В некоторых случаях использование taint и untaint приводит к получению одинакового результата, например:

tainted: ^taint[as-is][$form:text]
untainted: ^untaint[as-is]{$form:text}

В этом примере мы работаем с буквами, пришедшими из формы («грязными» без указания языка), поэтому ^untaint{} изменит их язык на as-is. А taint и так «пачкает» их целиком.

А вот пример, где эти операторы приводят к разным результатам:

$sText[$form:text]
^if(def $sText){
	$sText[^sText.match[\n][g]{^taint[as-is][<br />]}]
}
Пользователь ввел текст: ^taint[html][$sText]
$sText[$form:text]
^if(def $sText){
	$sText[^sText.match[\n][g]{^taint[as-is][<br />]}]
}
Пользователь ввел текст: ^untaint[html]{$sText}

Первый пример работать будет не очень хорошо — перед выдачей в броузер все теги <br /> будут заменены на &gt;br /&lt; (а не только те, что ввел пользователь в форме), т.к. оператором taint для всей строки принудительно задан в язык html (т.е. при выводе строки используемый taint[html] перебил ранее назначенный язык для кусочков с буквами <br />), во втором случае только теги, введенные пользователем в поле формы будут заменены, а теги <br /> которые парсер сам добавили в строку с помощью match заменяться не будут, т.к. он их добавил «чистыми»!

Теперь внимание: я написал ^taint[as-is][<br />] в match только для того, чтобы было очевиднее, на самом деле писать можно (а точнее нужно) так:

^if(def $sText){
	$sText[^sText.match[\n][g]{<br />}]
}

Так нужно писать по той причине, что буквы, которые вы пишите в теле страницы, всегда «чистые», и к ним никакие замены перед выводом применены не будут (если вам нужно иное — taint вам в руки!).

Вернемся опять ненадолго к нашим бара... ой, taint-ам... У них есть параметр — «язык», который может быть опущен... Например: в коде можно увидеть строку ^untaint{$sText}. Так вот, без указания языка у taint и untaint разные значения языков по умолчанию: taint без параметров (^taint[$sText]) «пачкает» весь текст, переданный ему, в неопределенный язык (можно представить себе, что на выходе мы получим буквы, как бы пришедшие из form/cookie/...). untaint без параметров (^untaint{$sText}) изменяет язык всех кусочков с неопределенным языком в переданном ему тексте, на язык as-is, т.е. ^untaint{$sText} полностью эквивалентен ^untaint[as-is]{$sText} (taint без указания языка не имеет подобного эквивалента).

Про пляски с бубнами вокруг этих операторов: никакое шаманство не нужно. Не стоит сразу начинать писать всюду taint/untaint. Ни в коем случае не стоит всюду без разбора писать ^taint[as-is][...] или ^untaint[as-is]{...}. По умолчанию всё хорошо, все замены производятся, и парсер максимально защищает вас от ваших ошибок или нехороших действий пользователей (вы не получите ошибок, вызваных какими-либо данными пришедшими из формы при попытке добавить их в базу данных). Не надо в случае, если что-либо работает не так, менять taint на untaint и обратно. Надо подумать почему написаный код работает так, а не иначе.

Типовые примеры, где применение taint/untaint может быть полезно:

Выводим ссылку, при этом нам нужно, чтобы русские буквы в URL преобразовались (в parser2 это делал оператор mangle)

@main[]
<a href="http://ya.ru/yandsearch?text=^taint[uri][простой и удобный объектно-ориентированный язык]">найди меня</a>

Вывод частично заполненной формы посетителю

@main[]
$bIsShowForm(1)
^if(def $form:do){
	^rem{ *** попытка обработать данные формы *** }
	^if(def $form:email){
		... выполняем действия, например добавляем тексты в БД или отправляем по email ...
		$bIsShowForm(0)
	}{
		... не заполнено обязательное поле email ...
	}
}
^if($bIsShowForm){
	<form method="post">
		Ваше имя:<br />
		<input type="text" name="name" value="$form:name" />
		Ваш e-mail:<br />
		<input type="text" name="email" value="$form:email" />
		Комментарий:<br />
		<textarea name="comment">$form:comment</textarea>
		<input type="submit" value="Отправить" />
	</form>
}

Как видите в этом случае никакие taint/untaint не использованы, т.к. они не нужны. Парсер сам при выводе из формы букв, подставленных в форму сделает так, чтобы они не сломали эту форму (при выводе в документ у парсера html — язык «по умолчанию»).

Вывод сообщения форума из базы данных (в базе данных хранятся теги, отвечающие за разметку документа)

@main[]
$tMessage[^table::sql{
	SELECT
		title,
		body
	FROM
		forum_message
	WHERE
		forum_message_id = ^form:id.int(0)
}[$.limit(1)]]
# тут мы НЕ доверяем тому, что пришло из базы данных, туда запостил данные посетитель,
# он мог написать туда кучу тегов, которые поломают страницу, поэтому никаких taint/untaint НЕ НАДО
<h1>$tMessage.title</h1>
$tMessage.body

Вывод новости с тегами из базы данных (в базе данных хранятся теги)

@main[]
$tNews[^table::sql{
	SELECT
		title,
		body
	FROM
		news
	WHERE
		news_id = ^form:news_id.int(0)
}[$.limit(1)]]
# тут мы доверяем тому, что пришло из базы данных, т.к. сами туда это забили через интерфейс администрирования
$sFeedbackUri[mailto:vasya_poupkin@asdf.com?subject=^taint[uri][Круто!]]
^untaint[as-is]{
	<h1>$tNews.title</h1>
	<hr size="1" noshade="noshade" />
	$tNews.body
	<hr size="1" noshade="noshade" />
	<p class="small">
		<a href="$sFeedbackUri">Вася Пупкин</a> не несет никакой ответственности за содержание данной новости.
	</p>
}

Обратите внимание, что в этом примере untaint не трогает текст, пришедший из переменной $sFeedbackUri, т.к. часть этой переменной «чистая», для другой же части уже задан язык uri.

Вывод данных с тегами из базы данных для редактирования в textarea

@main[]
$tNews[^table::sql{
	SELECT
		title,
		body
	FROM
		news
	WHERE
		news_id = ^form:news_id.int(0)
}[$.limit(1)]]
<form method="post">
	<input type="text" name="title" value="^untaint[html]{$tNews.title}" />
	<textarea name="body">^untaint[html]{$tNews.body}</textarea>
	<input type="submit" value="Сохранить" />
</form>

В последнем примере можно использовать как untaint так и taint, т.к. мы применяем эти операторы к тексту пришедшему из БД (буквы грязные, без указания языка), и нам надо получить эти тексты на языке html.