Мытьё рук или безопасное экранирование данных в моем понимании...

Знаете как обычно происходит работа с данными пользователя в веб-приложении?

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

Просто? Но всё же есть мелкие нюансы и на волне, что в последнее время я столкнулся с тоннами чужого кода и новым шаблонизатором Twig www.twig-project.org/ который желает всё и вся обезопасить перед выводом пользователю, я бы хотел добавить своих 5 копеек в общую копилку размышлений.

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

Вот получили мы данные пользователя и хотим сохранить в базе данных, разумеется чтобы избежать всяческих SQL инъекций мы используем экранирование, кто как, но я люблю всякие placeholder'ы, ранее это были встроенные в DBI bind и prepare, теперь это или встроенные возможности ActiveRecord паттерна в CodeIgniter фреймворке или обычные addslashes… Кто конечно пользуется, тот понимает зачем и как. Но самое главное НО по тексту далее, SQL инъекции это еще не все беды, которыми может наградить нас пользователь, еще это могут быть попросту вставленный html код или еще интереснее XSS инъекция, которая каким-либо интересным образом и упирает наши cookies или еще другие пакости строит. Именно о том как мы привыкли предохранятся от этой напасти я бы и хотел поговорить, а именно КОГДА?

В последних тенденциях развития средств веб-разработки (извините, я слежу на данный момент только за PHP и Ruby), так большая часть фреймворков предпочитает работать по принципу: получили данные пользователя → обработали от SQL инъекций и положили в базу, а когда уже надо вывести пользователю в каком-либо виде или в форме, тогда мы и будем обрабатывать от HTML инъекций и JS XSS вставок. Ruby on Rails порадует нас конструкциями вида <%=h title %>, а например CodeIgniter а-ля
<?=form_prep($title)?>
. Ну так вот, эта простейшая функция делает две основных задачи:


    $str = str_replace(array("'", '"'), array("'", """), $str);
    $str = htmlspecialchars($str);


В принципе тоже самое делала моя Perl'овая функция:


sub safe_html{
    my $val = shift;
    $val =~ s/\'/'/gm;
    $val =~ s/\"/"/gm;
    $val =~ s/\&/&/gm;
    $val =~ s/\</</gm;
    $val =~ s/\>/>/gm;
    return $val;
}


Но суть в том, что нас приучают мыть руки после еды!!! Что конечно тоже хорошо, но не заменит мытья рук перед едой как избавление от расстройств желудка! А вдруг мы забудем где-то экранировать например вывод комментария в шаблоне и злоумышленник просто напишет нам отличную XSS инъекцию или огромными буквами напишет слово «ДОМ»? Не стоит надеяться на свою память, что вы не забудете, обязательно забудете это как Законы Мерфи!

Многие это тоже понимают, например создатели различных шаблонизаторов, которые предлагают такие средства как sandbox или «эскейпинг» по умолчанию всего вывода. Хорошо это или плохо, я не могу утверждать, я просто понимаю, что мне не нравится этот подход, когда я ничего не контролирую железно.
Мой подход это именно мыть руки в начале приема пищи, а в нашем случае перед «экскейпингом» SQL инъекций.

Моё решение под CodeIgniter создание хелпера params_helper.php с рядом функций:


    /**
     * Возвращает параметры отравленные пользователем
     *
     * @param  string $name
 	 * @param bool	$clean		флаг очистки от XSS
 	 * @param bool	$clean_html	флаг очистки от html ntujd
     * @return mixed
     */
    function param( $name, $xss = TRUE, $clean_html = TRUE  ){
      $CI = &get_instance();
    	if( is_array($name) ) return $this->params( $name, $xss, $clean_html );

        $value = '';
	  // получаем данные экранируем 
        $value = $CI->input->post( $name, $xss );

        // если нужно экранировать html
	  if( $clean_html ) $value = htmlspecialchars( $value, ENT_QUOTES );

        return $value;
    }

	/**
	* Взять параметры c помощью функции param для целого массива
	* по умолчанию фильтруется сразу же XSS и html теги
	*
	* @param array	$params		массив названий нужных параметров
	* @param bool	$xss		флаг очистки от XSS
	* @param bool	$clean_html	флаг очистки от html ntujd
	* @return array
	*/
    function params( $params='', $xss = TRUE, $clean_html = TRUE ){
    	$result = array();

    	if( empty($params) ) return $result;
    	if( !is_array($params) ) return $this->param($name, $xss, $clean_html);

    	foreach($params as $name){
    		$result[$name] = $this->param($name, $xss, $clean_html);
    	}
    	return $result;
    }


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


    $title = param( 'title' );
    // или $params = param( array('title','author') ); где вернуться параметры

    $this->article_model->title = $title; // это так, пример из головы
    $this->article_model->save();


Но теперь может случиться одна проблема, что данные будут экранироваться от HTML и XSS по два раза, например в CodeIgniter при выводе в VIEW шаблоне встретится конструкция <?=form_prep($title)?> а так же все функции из form_helper по умолчанию используют form_prep для внутреннего создания элементов html из присланных данных.
В нашем случае с CodeIgniter нам ничего не стоило создать в проекте расширение-замену этой функции своей в файле MY_form_helper.php скопировав код из функции form_prep в наш файл и закоментировав лишь одну строку htmlspecialchars, вот полный код:

if ( ! function_exists('form_prep'))
{
	function form_prep($str = '', $field_name = '')
	{
		static $prepped_fields = array();

		// if the field name is an array we do this recursively
		if (is_array($str))
		{
			foreach ($str as $key => $val)
			{
				$str[$key] = form_prep($val);
			}

			return $str;
		}

		if ($str === '')
		{
			return '';
		}

		// we've already prepped a field with this name
		// @todo need to figure out a way to namespace this so
		// that we know the *exact* field and not just one with
		// the same name
		if (isset($prepped_fields[$field_name]))
		{
			return $str;
		}

		// In case htmlspecialchars misses these.
		$str = str_replace(array("'", '"'), array("'", """), $str);
//		$str = htmlspecialchars($str);

		if ($field_name != '')
		{
			$prepped_fields[$field_name] = $str;
		}

		return $str;
	}
}


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

p.s. Интересно, а как вы предпочитаете мыть руки, до еды или после? ;-)
  • +2
  • 1 декабря 2009, 15:55
  • MpaK

Комментарии (8)

RSS свернуть / развернуть
+
0
Предпочитаю хранить данные в таком виде, в котором их создал пользователь и обрабатывать их перед выводом.
К тому же, здесь описан самый простой способ защиты от XSS — запрет на HTML. Но иногда нужно иметь возможность разрешить использовать HTML, но зачищать его от скриптов, которые ведут на другие домены. Тут уже вступает в действие более сложный алгоритм.
В более сложном алгоритме бывают ошибки, которые исправляются. Если обрабатывать данные им до вставки в БД, то соответственно после изменения алгоритма такие данные становятся опять «грязными». Поэтому функция логично отрабатывает при выводе данных пользователю.
avatar

akhmetov

  • 1 декабря 2009, 16:50
+
0
Ну два аргумента: производительность и забывчивость. Грузить например каждый раз на кучу комментов к примеру Jevix или свои регулярки при показе очень напряжно, имхо, чем один раз при добавлении обработать. Ну и забывчивость описал выше, зал в одном месте, так и находятся потом дыры.
avatar

MpaK

  • 1 декабря 2009, 17:49
+
0
Производительность отдаем кешированию. На 100 просмотров 2 комментария (изменения кэша) в лучшем случае. Как то так.
avatar

akhmetov

  • 1 декабря 2009, 18:10
+
0
Но вообще производительность это уже оффтопик. Оба способа имеют право на существование. Но односторонняя обработка данных (без возможности реконструкции первоначального их представления) — это не мой путь.
avatar

akhmetov

  • 1 декабря 2009, 18:13
+
0
отравленные пользователем параметры это без базара круто :)
avatar

waitekk

  • 2 декабря 2009, 20:51
+
0
йоу :) без базара :)

я где-то опечатался?
avatar

MpaK

  • 3 декабря 2009, 00:07
+
0
первый листинг для CI
avatar

waitekk

  • 3 декабря 2009, 10:31
+
0
:))) опечатка по фрейду :)
avatar

MpaK

  • 3 декабря 2009, 11:28

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