Самый дорогой антипаттерн

Рассказывает автор блога m1el.github.io
В этой статье я расскажу вам о самом дорогом антипаттерне, который я знаю — управление структурированными данными с использованием строковых методов. Я буду называть это «антипаттерн printf».
«Самый дорогой антипаттерн» — не пустое сотрясание воздуха
Я подсчитал количество уязвимостей по их типам, используя данные с cve.mitre.org, и вот, что я получил:
- rexec: 19268
- DoS: 14849
- xss: 9236
- memory: 8212
- sqlinj: 6230
- privilege: 3321
- dirtraversal: 2762
- arith: 1260
- csrf: 1117
Вы можете критиковать меня за то, как я это считал, и попробовать сделать это самостоятельно.
Вы можете заметить, что XSS (Cross Site Scripting — «межсайтовый скриптинг») и SQL инъекции представляют собой значительную часть списка. Я виню в большинстве уязвимостей этих типов именно антипаттерн printf. Пихать какие-то строки прямо в HTML — ужасная идея. С SQL то же самое.
Примеры использования на разных языках
Теперь, когда вы знаете значение этого антипаттерна, я докажу вам, что это буквально повсеместное явление. Вы можете думать, что он характерен только для HTML и SQL — именно поэтому и появились все эти уязвимости.
- Буквально каждый сайт на PHP содержит что-то вроде:Hello, !
- SQL запрос сформированый с помощью строковых операций (см. документацию по mysql_query):$query = sprintf("SELECT firstname, lastname, address, age FROM friends WHERE firstname='%s' AND lastname='%s'", mysql_real_escape_string($firstname), mysql_real_escape_string($lastname));
- Многие программисты создают динамические элементы в JavaScript, используя что-то отдалённо напоминающее innerHtml (например, здесь):var OriginalContent = $(this).text();$(this).html("");
- Какой-нибудь бедный программист, которого заставили писать динамический сайт на C, может выдать:sprintf(buffer, "
", ...)%s24時間 - Генерирование JSON с использованием строкового форматирования (например, здесь):git log --pretty=format:'{ %n "commit": "%H",%n "abbreviated_commit": "%h",%n "tree": "%T",%n "abbreviated_tree": "%t",%n "parent": "%P",%n "abbreviated_parent": "%p",%n "refs": "%D",%n "encoding": "%e",%n "subject": "%s",%n "sanitized_subject_line": "%f",%n "body": "%b",%n "commit_notes": "%N",%n "verification_flag": "%G?",%n "signer": "%GS",%n "signer_key": "%GK",%n "author": { %n "name": "%aN",%n "email": "%aE",%n "date": "%aD"%n },%n "commiter": { %n "name": "%cN",%n "email": "%cE",%n "date": "%cD"%n }%n},'
- Использование
sed
для работы с XML:# http://askubuntu.com/questions/442013/using-sed-to-search-and-replace-text-in-xml-filesed -i 's##UpdateAndExit #' File.XML# http://askubuntu.com/questions/284983/print-text-between-two-xml-tagssed -n '/- Генерирование кода с помощью ExtJS (например, здесь): '
', ' dest["{name}"] = value === undefined ? __field{#}.convert(__field{#}.defaultValue, record) : __field{#}.convert(value, record);n', ...// использование эксплоита:// Ext.define('m',{extend:'Ext.data.Model',fields:['id']});// var store = Ext.create('Ext.data.Store',{model:m});// store.loadRawData({metaData:{fields:['"+alert(1)+"']}}); - MAL (Make-A-Lisp) задаёт
load-file
, как результат конкатенции строк:
Define a load-file function using mal itself. In your main program call the rep function with this string: “(def! load-file (fn* (f) (eval (read-string (str “(do ” (slurp f) “)”)))))”. - Генерирование кода с помощью ExtJS (например, здесь): '
Как же правильно обрабатывать данные?
И так, если я использую строковые функции для генерации HTML, что мне нужно использовать вместо этого?
Как вы можете видеть ниже – все предложенные решения имеют один смысл — использовать специальные методы создания объектов, а лишь потом сериализовать их. Главное – не обрабатывать сериализованные данные, как строки.
Для HTML
- hiccup (больше примеров здесь):(html [:ul (for [x (range 1 4)] [:li x])])(defn index [] [:div {:id "content"} [:h1 {:class "text-success"} "Hello Hiccup"]])
- lxml e-factory:html = page = ( E.html( # create an Element called "html" E.head( E.title("This is a sample document") ), E.body( E.h1("Hello!", CLASS("title")), E.p("This is a paragraph with ", E.b("bold"), " text in it!"), E.p("This is another paragraph, with a", "n ", E.a("link", href="http://www.python.org"), "."), E.p("Here are some reservered characters:
."), ) )) - Работайте с DOM (Document Object Model), потом сериализуйте; простейший пример:html = etree.parse('template.html')name_node = html.xpath('//div[@id="user-name"]')[0]name_node.text = user.nameprint(etree.tostring(html))
- Используйте серверный AngularJS или React.
- Управляйте DOM напрямую, например, через jQuery:var OriginalContent = $(this).text();$(this).empty().append($('').val(OriginalContent))// Ужасный код! Ужасный!// $(this).html("");
Для JSON
- Используйте литералы или list compehensions для создания объекта JSON, потом сериализуйте его;
- Генерируйте JSON объекты, а потом сериализуйте.
Для SQL
- Используйте placeholder’ы (параметры запроса);
- Работайте с Абстрактными Деревьями Синтаксиса (Abstract Syntax Trees – AST) SQL, а потом делайте запрос.
Для XML
- Работайте с DOM, потом переводите в XML;
- Пользуйтесь XSLT.
Для мета-программирования
- Используйте Lisp;
- Работайте с AST для вашего языка.
В чём корень проблемы?
На мой взгляд, дело в человеческой лени.
И вот, мы генерируем HTML, используя объединение строк. Конечно, если вы будете писать код очень, очень аккуратно, вы избежите уязвимостей. Но зачем вам такие проблемы? Вы же не используете ручное управление памятью для создания веб-сайта, правда? Нет смысла ходить по острию ножа, когда вы можете просто писать в безопасном ключе.
Немного о браузерах
У всей этой истории есть более весёлая сторона — браузеры. Их разработчики приспосабливали свои парсеры для работы с неправильным HTML, т.к. многие сайты содержали кривой код. Люди выбирали браузеры, которые могли воспринимать как HTML буквально произвольные наборы байтов и корректно отображали их любимые веб-сайты. Так же были разработаны XSS фильтры, т.к. сайты записывали необработанные запросы пользователей прямо в HTML. Суть таких фильтров проста — если браузер может предотвратить 90% XSS атак на пользователя, пользователь будет доволен. Так что, эти фильтры не могут защитить от всех атак.
Браузеры борются лишь с симптомами проблемы — сама проблема в головах программистов, которые думают, что строковые функции — подходящий инструмент для создания динамических веб-страниц.
В заключение
Структура HTML ужасна потому, что с самого начала люди стали управлять ей как строками. Очень много проблем (включая, но не ограничиваясь: XSS, некорректная разметка, разный парсинг браузеров) обусловлено неправильным пониманием сути формата HTML.
Предположим, — просто предположим, — что доступные инструменты не подталкивали бы людей к созданию HTML в виде строк — интернет тогда был бы лучше.
Предположим, — просто предположим, — что мы выбрали бы другой путь для обработки веб-страниц — тогда бы мы не воспринимали его как строку, которую можно записать через printf.
Но можно с абсолютной уверенностью сказать, что уязвимостей было бы гораздо меньше, если бы программисты не воспринимали бы структурные форматы данных как что-то, для работы с чем можно использовать строковые функции.
Перевод выполнил Пётр Соковых
Оригинал: «The Most Expensive Anti-Patternf»