Безопасное использование внешних компонентов - Академия Selectel

Безопасное использование внешних компонентов

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

Изображение записи

Безопасное использование внешних компонентов: общие вопросы

Практически ни одно приложение не работает изолированно — большинство так или иначе взаимодействует с чем-то еще. Например, они могут обращаться к локальным компонентам (модулям ОС, встроенным библиотекам, пакетам из репозиториев вроде npm) или сетевым сервисам. Современные системы часто обращаются к внешним API (REST и GraphQL), получая данные в форматах JSON и XML. При этом практически каждое из вызываемых приложений само использует другие компоненты. 

Часто такие связи неочевидны или скрыты сложной инфраструктурой. Возникает вопрос: «А как в таком случае обеспечить безопасность приложения?» Давайте разбираться.

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

Полезное правило: вызывайте функции только с допустимыми значениями. Если функция требует, чтобы число находилось в диапазоне от 0 до 9, атакующий не должен иметь возможности передать значение 50. Теоретически это просто, но на практике все сложнее, особенно когда ограничения недостаточно документированы. 

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

  • Функция выполняет произвольный код, полученный извне, включая данные, которые могут контролироваться злоумышленником. Методы с названиями вроде eval(), exec(), execute() или system() часто относятся к этой категории. Например, не используйте eval() в JavaScript для обработки JSON, вместо этого применяйте более безопасный JSON.parse().
  • Функция требует конкатенации строковых констант с данными из ненадежных источников. Такие данные обычно требуют экранирования, где легко допустить ошибку.
  • Входные данные функции описываются с помощью формальной спецификации языка.
  • Функция изначально предназначена для прямого взаимодействия с пользователем, а не для вызова из приложения.

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

Почему некоторые функции сложно применять безопасно? Проблема заключается в поддержке языков с метасимволами — специальными символами, которые изменяют интерпретацию других символов, вместо того чтобы быть данными. Например, двойная кавычка (“) часто является метасимволом (в том числе в SQL и командной оболочке). Также наличие формальной спецификации языка почти всегда свидетельствует о наличии метасимволов. Хотя они обеспечивают гибкость и при работе с доверенными данными проблем не возникает, ситуация меняется, когда часть ввода контролируется злоумышленником. Если атакующему удается внедрить метасимволы во входные данные, а их экранирование выполнено неидеально, это часто приводит к опасным и легко эксплуатируемым уязвимостям при интерпретации данных. Подобные атаки принято называть инъекционными (injection attacks).

Поэтому при передаче данных в другое приложение или их выводе необходимо обеспечить безопасность. Для этого можно использовать три инструмента:

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

По возможности используйте библиотеки и API, которые выполняют эти операции автоматически.

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

Разобравшись с этими двумя случаями, вы сможете правильно обрабатывать данные и для других интерпретаторов, которые мы не будем здесь рассматривать (например, для протокола LDAP). Начнем с отправки данных в системы БД, которые часто уязвимы к SQL-инъекциям.

Работа с SQL-инъекциями

Почти все системы управления базами данных (СУБД) поддерживают специализированные языки, позволяющие формировать произвольные запросы и выполнять другие операции, например, создание и изменение объектов. Наиболее распространенный из них — SQL, хотя есть и другие варианты. 

Что важно знать — такие языки оперируют метасимволами. Если злоумышленник получает возможность внедрить метасимволы в SQL-команду для нарушения безопасности, такая атака классифицируется как SQL-инъекция, а соответствующая уязвимость — как уязвимость к SQL-инъекциям. Для обозначения этого типа атак используется аббревиатура SQLi.

Даже когда СУБД использует не SQL, а другой язык запросов, атаки на нее часто также называют SQL-инъекциями.

Приведем простой пример. Вот фрагмент кода на Java, который пытается выполнить SQL-запрос, но делает это небезопасно:


      String QueryString = "select * from authors where lastname = ' " +
   search_lastname + " '; "; // VULNERABLE CODE
   rs = statement.executeQuery(QueryString); // VULNERABLE CODE

Идея кода понятна: если search_lastname содержит значение Fred, база данных получит запрос “select * from authors where lastname=’Fred’;” — корректный SQL-запрос. Но вспомним признаки проблемного кода: здесь используется конкатенация строк, часть данных может поступать от злоумышленника, и применяется функция выполнения команд. 

Если злоумышленник передаст значение “Fred’ OR ‘a’=’a”, это сформирует запрос:


      select * from authors where lastname='Fred' OR 'a'='a';

Теперь злоумышленник может получить всю БД. В зависимости от условий, он может даже изменять или удалять данные. Это простой пример SQL-инъекции: атакующий может добавить специальные символы и внедрить новые или измененные команды.

Существует множество способов осуществления SQL-инъекций. Злоумышленники могут вставлять:

  • одинарные кавычки (используемые для выделения строковых констант); 
  • точки с запятой (выступающие разделителями команд);
  • двойные дефисы «–» (обозначающие комментарии) и другие символы.

Это не исчерпывающий перечень — разные СУБД интерпретируют символы по-разному. Например, двойные кавычки часто являются метасимволами, но имеют различное значение. Даже разные версии одной СУБД или различные конфигурации могут влиять на интерпретацию символов. 

Не следует создавать список «запрещенных» символов (denylist). Можно было бы составить список «разрешенных» символов (allowlist), не являющихся метасимволами, и экранировать остальные, но для SQL этот подход сложно реализовать корректно.

Не используйте конкатенацию строк для создания запросов к СУБД — такой подход изначально небезопасен. Это относится к форматированным строкам, интерполяции строк, строковым шаблонам и любым другим механизмам, которые просто объединяют текст. Например, одинаковые уязвимости возникают при использовании форматированных строковых литералов Python (f-строк, таких как f'{year}-{month}’), метода .format в Python, интерполяции строк в Ruby (“#{year}-#{month}”), шаблонов Go или любых других строковых шаблонов и языков форматирования. Помните: мы хотим использовать методы, которые легко применять безопасно, а все эти подходы по умолчанию опасны при создании команд вроде SQL-запросов.

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


      String QueryString = "select * from authors where lastname = ' " + sql_escape(search_lastname) + " '; "; 

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

История: компания Heartland Payment Systems и SQL-инъекция

В конце 2007 года злоумышленники использовали SQL-инъекцию для компрометации базы данных Heartland Payment Systems (далее «Heartland»). На тот момент компания обрабатывала 100 миллионов транзакций по платежным картам ежемесячно для 175 000 торговых точек.

Злоумышленники использовали SQL-инъекцию для внедрения кода в веб-скрипты, используемые страницей входа. Это позволило им установить шпионское ПО (сниффер), которое в течение нескольких месяцев 2008 года перехватывало данные карт во время обработки платежей.

Из-за инцидента Heartland временно утратила соответствие стандарту безопасности индустрии платежных карт (PCI DSS), который необходим для ее работы. Согласно отчетам, компании пришлось выплатить 145 миллионов долларов в качестве компенсации за мошеннические операции (по данным исследования Diane Ritchey «Data Breach Directions: What to Do After an Attack»). С тех пор Heartland предприняла значительные меры для усиления защищенности и отказоустойчивости своих систем с целью предотвращения повторения подобных инцидентов.

Как мы уже отмечали, следует выбирать подход, который сложно применить неправильно — он должен быть безопасным по умолчанию. Для работы с базами данных существуют проверенные решения, которые значительно проще использовать безопасным способом.

Работа с SQL-инъекциями: Параметризованные запросы

Параметризованные выражения (parameterized statements), или параметризованные запросы (parameterized queries), — эффективный способ защиты от SQL-инъекций при формировании SQL-команд. 

Такие выражения позволяют использовать плейсхолдеры (часто в виде символа «?») для данных, требующих экранирования. Специальная библиотека обрабатывает эти параметры и выполняет корректное экранирование данных в соответствии с конкретной реализацией. При этом синтаксис плейсхолдеров зависит от используемой библиотеки и/или СУБД.

Предварительно подготовленный оператор (prepared statement) компилируется в СУБД заранее, что позволяет в дальнейшем эффективно выполнять запросы с конкретными данными. Предварительная компиляция оператора может повысить производительность при его многократном выполнении. Интерфейсы предварительно подготовленных операторов обычно поддерживают параметризованные запросы, поэтому многие разработчики и API используют термины «предварительно подготовленный оператор» и «параметризованный запрос» как синонимы.

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

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

В чем преимущество параметризованных запросов:

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

Параметризованные и предварительно подготовленные выражения широко поддерживаются, хотя API и синтаксис плейсхолдеров различаются в зависимости от языка программирования, библиотеки и СУБД. Мы рассмотрим несколько примеров.

В Python существуют отдельные библиотеки для работы с базами данных. Многие из них реализуют Спецификацию Python Database API v2.0 (PEP 249), где методы execute и executemany поддерживают параметризованные запросы. Синтаксис плейсхолдеров определяется атрибутом paramstyle библиотеки. Вот простой пример из документации библиотеки sqlite3:


      con = sqlite3.connect(...)
cur = con.cursor()
cur.execute("insert into test(d, ts) values (?, ?)", (today, now))

А вот пример запроса на Go из документации go.dev по выполнению запросов:


      rows, err := db.Query("SELECT * FROM album WHERE artist = ?", artist)

При работе с Node.js и JavaScript также существует множество способов использования параметризованных и предварительно подготовленных выражений. Приведем пример использования интерфейса обратных вызовов node-postgres, как описано в документации node-postgres:


      const text = 'INSERT INTO users(name, email) VALUES($1, $2) RETURNING *'
const values = ['brianc', 'brian.m.carlson@gmail.com']
client.query(text, values, (err, res) => { .... })

На сайте OWASP Query Parameterization Cheat Sheet и ресурсе Bobby Tables приведены примеры для различных экосистем.

Многие СУБД поддерживают «хранимые процедуры» — код, выполняемый непосредственно в базе данных. Если в таких процедурах динамически формируются SQL-команды, это создает уязвимость к SQL-инъекциям. Как и в предыдущих случаях, наиболее надежным решением остается использование параметризованных запросов при создании динамических команд (таких как SQL) внутри хранимой процедуры.

Важно учитывать: в некоторых системах правильная реализация этого подхода может быть сопряжена с определенными сложностями. Для получения дополнительной информации об использовании параметризованных запросов в хранимых процедурах обратитесь к документации вашей библиотеки, а также к руководствам OWASP Query Parameterization Cheat Sheet и OWASP SQL Injection Prevention Cheat Sheet.

Иногда параметризованные запросы (и подготовленные выражения) оказываются непригодными. Многие API таких запросов позволяют заменять только значения в SQL, но не поддерживают изменение таких элементов, как названия таблиц, имена столбцов или направление сортировки. Тогда может потребоваться сборка запроса через конкатенацию данных. В этом случае важно строго проверять данные (с применением белого списка), разрешая только конкретные безопасные значения. 

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

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

Например, в Python при работе с именем таблицы можно реализовать следующее:


      table_name_untrusted = request.get("table_name") # This is untrusted, don't put this directly in the query!
table_name_map = {"table1": "db.table1", "table2": "db.table2"}
table_name = table_name_map[table_name_untrusted]
con = sqlite3.connect(...)
cur = con.cursor() 
cur.execute(f"insert into {table_name}(d, ts) values (?, ?)", (today, now)) # This is safe because we know that table_name can only take trusted values from table_name_map

Многие приложения применяют объектно-реляционное преобразование (Object-Relational Mapping, ORM) — технологию для автоматического преобразования данных между реляционной базой и объектами в коде. Обычно библиотеки и фреймворки предоставляют эту функциональность (но не всегда). Это безопасный подход, если ORM использует параметризованные запросы или аналогичный механизм. Качественные реализации ORM действительно так и делают. Однако в системах с ORM иногда требуется выполнять прямые SQL-запросы. В таких случаях обязательно используйте параметризованные или предварительно подготовленные выражения.

В ряде приложений используются библиотеки «конструкторов запросов» (query builder). Они позволяют программно собирать команды (запросы) через цепочку методов, вместо того чтобы вручную формировать строку запроса. Некоторые ORM-фреймворки уже содержат в себе такую подсистему. 

Ключевой момент: грамотно реализованный конструктор запросов внутренне использует механизм параметризованных запросов (или его аналог). Следовательно, если вы применяете конструктор, убедитесь, что он работает именно через параметризацию, и всегда передавайте непроверенные пользовательские данные отдельными параметрами, только так он сможет корректно их обработать и обезопасить запрос.

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

Грамотное использование библиотек параметризованных запросов существенно упрощает создание безопасного кода. Кроме того, они обычно улучшают читаемость кода, автоматически учитывают различия в реализации экранирования между разными СУБД, а в некоторых случаях работают эффективнее ручного экранирования метасимволов.

Работа с SQL-инъекциями: обработка на стороне СУБД и на стороне приложения

Важный, но не всегда очевидный аспект безопасности при использовании параметризованных запросов — место обработки параметров. Существует два варианта: на стороне СУБД и на стороне приложения.

С точки зрения безопасности предпочтительнее, когда параметры параметризованных запросов обрабатываются непосредственно в СУБД. Этот подход часто называют «обработкой на стороне СУБД» (DBMS-side) или «серверной обработкой», поскольку многие СУБД используют клиент-серверную архитектуру, где клиент подключается к СУБД по сети.

Обработка параметров на стороне СУБД имеет ряд преимуществ. Система управления базами данных обладает актуальной информацией о правилах экранирования (и часто может использовать более эффективные механизмы, чем добавление экранирующих символов), а также имеет доступ к другим важным данным, таким как соответствующие кодировки символов и ожидаемые типы данных. Что особенно важно, разработчики СУБД обычно привлекают экспертов по безопасности для анализа этой части системы. Однако реализация обработки параметров на стороне СУБД может требовать больше усилий, поэтому некоторые библиотеки используют вместо этого обработку на стороне приложения (application-side).

Обработка параметров на стороне приложения происходит, когда экранирование параметров выполняется не в СУБД, а в библиотеке внутри самого приложения. Этот подход также называют «клиентской обработкой параметров». Системы обработки параметров на стороне приложения обычно реализуются путем прямой вставки экранирующих символов в необходимые места. Такой подход часто проще в реализации, поэтому многие библиотеки для работы с базами данных используют его. Библиотеки с обработкой на стороне приложения предпочтительнее ручного вызова механизмов экранирования при каждом использовании, поскольку выполняют экранирование автоматически.

К сожалению, обработка параметров на стороне приложения имеет фундаментальный недостаток: клиентская часть может интерпретировать данные иначе, чем СУБД. Это расхождение может приводить к уязвимостям. Например:

  • библиотека на стороне приложения может быть предназначена для другой версии СУБД, которая может требовать экранирования других символов или работать в иных условиях;
  • библиотека приложения может некорректно интерпретировать многобайтовые символы или неправильно их экранировать в конкретной среде СУБД;
  • если библиотека реализует параметризацию сложных типов данных (массивы, объекты, ассоциативные массивы или словари), возникает риск уязвимостей. Основная проблема заключается в том, что библиотека приложения не обязательно анализирует язык запросов так же, как это делает СУБД — обычно она выполняет простую текстовую подстановку. Поэтому при реализации такой функциональности библиотеке приходится предполагать, какие типы данных ожидаются. 

Например, она может считать, что ассоциативные массивы передаются только в соответствующих контекстах параметризованного SQL-запроса. К сожалению, эти предположения могут быть использованы злоумышленником. Особенно это актуально для языков без статической типизации (где типы неизвестны на этапе компиляции), поскольку в них значительно проще передать в библиотеку сложные типы данных, которые она не всегда может безопасно обработать. Например, широко используемая библиотека Node.js mysqljs/mysql часто оказывается уязвимой при передаче JavaScript-объекта в качестве параметра (см. исследование Maxwell Dulin «Finding an Authorization Bypass on my Own Website»).

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

Представим, что злоумышленник передает JavaScript-объект {password: 1} в качестве параметра пароля (это не строка, а полноценный JavaScript-объект). Допустим, этот объект используется в параметризованном SQL-запросе:


      SELECT * FROM accounts WHERE username = ? AND password = ?

Библиотека преобразует выражение после AND в password = ‘password’ = 1, не учитывая, что JavaScript-объект не имеет смысла в данном контексте запроса (где ожидалась строка или число). СУБД MySQL интерпретирует password = ‘password’ как 1 (истина), а затем определяет, что 1 = 1 также истинно. В результате это выражение всегда будет возвращать истину. Такого некорректного экранирования сложного типа данных достаточно для полного обхода аутентификации в некоторых сценариях.

К сожалению, решение этой проблемы сопряжено со сложностями. Безопасный подход предполагает, что сложные типы данных (отличные от чисел и строк) не обрабатываются библиотеками на стороне приложения без явного разрешения разработчика. Это может быть проблемой, если приложение уже зависит от такой функциональности, а библиотека не предоставляет возможности ее полного отключения. Например, mysqljs/mysql позволяет установить параметр stringifyObjects: true при создании подключения, но это лишь отключает экранирование обычных объектов — другие сложные типы (например, массивы) продолжают обрабатываться.

Универсальное решение — проверять тип каждого параметра перед передачей в библиотеку. Например, требовать, чтобы все ожидаемые строковые данные действительно были строками. В статически типизированных языках это обеспечивается самой системой типов, достаточно объявить правильный тип. В языках без статической типизации реализация такой проверки требует значительных усилий, а также сохраняется риск пропустить проверку при создании или изменении кода. Однако этот подход более гибкий( исследование «Обнаружение неочевидной SQL-инъекции путем обхода функций экранирования в mysqljs/mysql» от Flatt Security Inc.)

Бывает сложно понять, где именно библиотека обрабатывает параметры: в самой СУБД или в приложении. Иногда доступен только второй вариант. В некоторых случаях применение подготовленных выражений принудительно заставляет библиотеку использовать обработку на стороне СУБД, но рассчитывать на это не стоит, поэтому всегда изучайте документацию. Если есть выбор, используйте реализацию на стороне СУБД. Если нет — строго проверяйте типы данных, которые передаете в библиотеку.

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

Работа с Shell-инъекциями

Еще один вид инъекционных атак — инъекция в команды операционной системы (OS command injection), также называемая shell-инъекцией (shell injection). Эта уязвимость аналогична SQL-инъекции: данные, частично доверенные и частично контролируемые злоумышленником, передаются интерпретатору, который выполняет полученные инструкции. Отличие в том, что вместо СУБД эта смесь данных отправляется интерпретатору команд операционной системы, то есть командной оболочке.

Большинство систем располагают как минимум одной командной оболочкой. Многие Unix-подобные системы, в том числе стандартные дистрибутивы Linux, предлагают различные варианты оболочек: bash, dash, ksh, zsh и csh, причем как минимум одна из них всегда доступна по пути /bin/sh или /usr/bin/sh.  

Командная оболочка (shell) принимает команды и запускает соответствующие им приложения, а большинство языков программирования позволяют легко обращаться к оболочке. Проблема в том, что при включении полученных от пользователя данных в команды для оболочки легко допустить ошибки.

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

  • В C предпочтительнее использовать execve(3) (не использует оболочку) вместо system(3) (использует оболочку).
  • В Python лучше применять shell=False (значение по умолчанию) с subprocess.run() или subprocess.call() вместо shell=True или os.system().
  • В Node.js рекомендуется использовать shell=False (значение по умолчанию) с child_process.spawn() или child_process.execFile() вместо shell=True или child_process.exec()

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

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

При передаче данных через оболочку экранируйте все символы, кроме тех, что входят в белый список (символов, которые точно не являются метасимволами). Как правило, символы A-Z, a-z и 0-9 не являются метасимволами, но даже в этом случае необходима тщательная проверка. Убедитесь, что все данные должным образом заключены в кавычки.

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

Использование программных интерфейсов

При разработке приложений следует вызывать только те программные интерфейсы (Application Programming Interface, API), которые предназначены для использования приложениями.

Обычно приложение может вызывать любые другие, в том числе те, что изначально разрабатывались для взаимодействия с пользователем. Однако вызывать их так же, как это делает человек не лучшая идея. Проблема в том, что пользовательские интерфейсы специально насыщены функционалом, и зачастую их сложно полностью контролировать. Например, интерактивные приложения часто поддерживают escape-последовательности, которые могут позволить злоумышленнику выполнять нежелательные операции. Кроме того, такие приложения часто пытаются предугадывать «наиболее вероятные» параметры по умолчанию. Эти значения могут не совпадать с ожидаемыми, чем может воспользоваться злоумышленник. Как правило, существуют специальные параметры или отдельные API, которые предоставляют безопасный доступ к функционалу приложения, используйте именно их.

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

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