Стоит ли защищаться от неожиданных значений внешних API?

51

Допустим, вы кодируете функцию, которая получает данные от внешнего API MyAPI.

Этот внешний API MyAPIимеет контракт, в котором говорится, что он возвратит a stringили a number.

Является ли он рекомендовал , чтобы защититься от таких вещей , как null, undefined, booleanи т.д. , даже если это не часть API из MyAPI? В частности, поскольку у вас нет контроля над этим API, вы не можете получить гарантию с помощью статического анализа типов, поэтому лучше быть в безопасности, чем сожалеть?

Я думаю относительно принципа робастности .

Адам Томпсон
источник
16
Каковы последствия не обработки этих неожиданных значений, если они возвращаются? Можете ли вы жить с этими воздействиями? Стоит ли усложнять обработку этих неожиданных значений, чтобы избежать столкновения с последствиями?
Винсент Савард
55
Если вы ожидаете их, то по определению они не являются неожиданными.
Мейсон Уилер
28
Помните, что API не обязан возвращать вам только верный JSON (я предполагаю, что это JSON). Вы также можете получить ответ, как<!doctype html><html><head><title>504 Gateway Timeout</title></head><body>The server was unable to process your request. Make sure you have typed the address correctly. If the problem persists, please try again later.</body></html>
user253751
5
Что означает «внешний API»? Это все еще под вашим контролем?
дедупликатор
11
«Хороший программист - это тот, кто смотрит в обе стороны, прежде чем переходить улицу с односторонним движением».
jeroen_de_schutter

Ответы:

103

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

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


Из комментариев я вижу, что, возможно, мой ответ может быть немного расширен.

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

В комментариях я приведу один аргумент в качестве примера. В то время как да, вам нужно в какой-то степени доверять своей ОС, не исключено, например, отклонение результатов генератора случайных чисел, если вы попросите его ввести число от 1 до 10, и он ответит «bob».

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

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

Павел
источник
20
Что такое QV?
Джон
15
@JonH в основном «смотрите также» ... целевой хак является примером того, что он ссылается на en.oxforddictionaries.com/definition/qv .
andrewtweber
8
Этот ответ не имеет смысла. Невозможно предвидеть, что сторонняя библиотека может плохо себя вести. Если документация библиотечной функции явно гарантирует, что результат всегда будет иметь некоторые свойства, тогда вы должны полагаться на то, что дизайнеры обеспечили, чтобы это свойство действительно сохранялось. Это их ответственность , чтобы иметь набор тестов , который проверяет , что такого рода вещи, и представить исправление ошибки в случае ситуация встречается там , где его нет. Вы проверить эти свойства в собственном коде нарушает принцип DRY.
оставил около
23
@leftaroundabout нет, но вы должны быть в состоянии предсказать все действительные вещи, которые ваше приложение может принять, а остальные отклонить.
Пол
10
@leftaroundabout Речь идет не о недоверии ко всему, а о недоверии внешним источникам. Это все о моделировании угроз. Если вы еще не сделали, чтобы ваше программное обеспечение не было защищено (как это может быть, если вы даже не задумывались о том, от каких субъектов и угроз вы хотите обезопасить свое приложение?). Для запуска обычного делового программного обеспечения разумно по умолчанию предполагать, что вызывающие абоненты могут быть вредоносными, в то время как редко разумно предполагать, что ваша ОС представляет угрозу.
Во
33

Да , конечно. Но что заставляет вас думать, что ответ может быть другим?

Вы, конечно, не хотите, чтобы ваша программа работала непредсказуемым образом, если API не возвращает то, что говорит контракт, не так ли? Таким образом , по крайней мере , вы должны иметь дело с таким поведением как - то . Минимальная форма обработки ошибок всегда стоит (очень минимум!) Усилий, и нет абсолютно никакого оправдания тому, чтобы не реализовывать что-то подобное.

Тем не менее, сколько усилий вы должны потратить, чтобы разобраться с таким делом, в значительной степени зависит от конкретного случая, и на него можно ответить только в контексте вашей системы. Часто бывает достаточно короткой записи в журнале и постепенного завершения приложения. Иногда вам будет лучше реализовать некоторую детальную обработку исключений, имея дело с различными формами «неправильных» возвращаемых значений, и, возможно, вам придется реализовать некоторую резервную стратегию.

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

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

Док Браун
источник
Что делать, это другое решение. У вас может быть отказоустойчивое решение. Все асинхронное может быть повторено перед созданием журнала исключений (или мертвой буквы). Активное предупреждение продавцу или поставщику может быть вариантом, если проблема не устранена.
Маккензм
@mckenzm: тот факт, что ОП задает вопрос, где буквальный ответ, очевидно, может быть только «да», является ИМХО знаком того, что они могут не просто интересоваться буквальным ответом. Похоже, они спрашивают: «Нужно ли защищаться от различных форм непредвиденных значений в API и обращаться с ними по-разному» ?
Док Браун
1
хм, дерьмо / карп / умирают. Это наша вина за плохие (но законные) запросы? возможен ли ответ, но не пригоден ли он для нас, в частности? или ответ коррумпирован? Различные сценарии, теперь это звучит как домашнее задание.
Маккензм
21

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

РЕДАКТИРОВАТЬ: Оказывается, я ошибся в приведенном выше заявлении. Принцип надежности основан не на аппаратном обеспечении, а на архитектуре Интернета, в частности RFC 1958 . Говорится:

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

Это, прямо скажем, просто неправильно от начала до конца. По причинам, изложенным в этом посте, трудно представить себе более ошибочное представление об обработке ошибок, чем «молча отбросить ошибочный ввод без возврата сообщения об ошибке».

См. Также документ IETF «Вредные последствия принципа робастности» для дальнейшей проработки этого вопроса.

Никогда, никогда, никогда не выбирайте этот второй вариант, если у вас нет ресурсов, эквивалентных поисковой команде Google, для реализации вашего проекта, потому что это то, что нужно, чтобы создать компьютерную программу, которая делает что-то похожее на достойную работу в этой конкретной проблемной области. (И даже в этом случае предложения Google выглядят так, будто они выходят из левого поля примерно половину времени.) Если вы попытаетесь это сделать, то в итоге вы получите огромную головную боль, которую ваша программа будет часто пытаться интерпретировать. неверный ввод как X, когда отправитель действительно имел в виду Y.

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

Вот почему существует принцип Fail Fast; избавьте всех вовлеченных в головную боль, применив ее к своим API.

Мейсон Уилер
источник
7
Хотя я согласен с принципом того, что вы говорите, я думаю, что вы ошибаетесь в отношении принципа робастности. Я никогда не видел, чтобы это означало «принимать плохие данные», только «не будьте слишком осторожны с хорошими данными». Например, если входные данные представляют собой CSV-файл, принцип робастности не будет допустимым аргументом для попытки анализа дат в неожиданном формате, но будет поддерживать аргумент, что вывод порядка столбцов из строки заголовка будет хорошей идеей. ,
Морген
9
@Morgen: принцип надежности использовался для того, чтобы предлагать браузерам принимать довольно небрежный HTML, и это приводило к тому, что развернутые веб-сайты были гораздо более небрежными, чем если бы браузеры требовали надлежащего HTML. Тем не менее, большая часть проблемы заключалась в использовании общего формата для генерируемого человеком и генерируемого машиной контента, в отличие от использования отдельных форматов, редактируемых человеком и пригодных для машинного анализа, а также утилит для преобразования между ними.
суперкат
9
@supercat: тем не менее - или просто так - HTML и WWW были чрезвычайно успешными ;-)
Док Браун
11
@DocBrown: многие действительно ужасные вещи стали стандартами просто потому, что они стали первым подходом, который оказался доступным, когда кому-то с большим влиянием нужно было принять что-то, отвечающее определенным минимальным критериям, и к тому времени, когда он получил тягу, это было слишком поздно, чтобы выбрать что-то лучшее.
суперкат
5
@supercat Точно. Например, сразу приходит на ум JavaScript ...
Мейсон Уилер
13

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

  1. Когда дан правильный ввод, произведите правильный вывод.

  2. Когда дан верный ввод (который может или не может быть правильным), выведите действительный вывод (аналогично).

  3. Если задан неверный ввод, обработайте его без каких-либо побочных эффектов, кроме тех, которые вызваны нормальным вводом или тех, которые определены как сигнализация ошибки.

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

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

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

Supercat
источник
Затем все сводится к решению, является ли результат вызова API «входным».
мастов
@mastov: Ответы на многие вопросы будут зависеть от того, как определить «входные данные» и «наблюдаемое поведение» / «выходные данные». Если целью программы является обработка чисел, хранящихся в файле, ее ввод может быть определен как последовательность чисел (в этом случае вещи, которые не являются числами, не являются возможными входами), или как файл (в этом случае все, что может появиться в файле будет возможный ввод).
суперкат
3

Давайте сравним два сценария и попытаемся прийти к выводу.

Сценарий 1 Наше приложение предполагает, что внешний API будет работать в соответствии с соглашением.

Сценарий 2 В нашем приложении предполагается, что внешний API может работать некорректно, поэтому добавьте меры предосторожности.

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

Если наша программа написана при условии, что внешний API будет придерживаться соглашений и избегать добавления каких-либо мер предосторожности; кто будет стороной, которая столкнется с проблемами? Это мы, те, кто написал код интеграции.

Например, пустые значения, которые вы выбрали. Скажем, согласно соглашению API ответ должен иметь ненулевые значения; но если она будет внезапно нарушена, наша программа приведет к появлению NPE.

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

lkamal
источник
1

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

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

StarTrekRedneck
источник
1

В общем, да, вы всегда должны защищаться от некорректных входных данных, но в зависимости от типа API «охрана» означает разные вещи.

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

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

Питер
источник
0

Несколько иное мнение: я думаю, что может быть приемлемо просто работать с данными, которые вы предоставляете, даже если они нарушают их контракт. Это зависит от использования: это то, что ДОЛЖНО быть для вас строкой, или это то, что вы просто отображаете / не используете и т. Д. В последнем случае просто примите это. У меня есть API, который просто нуждается в 1% данных, передаваемых другим API. Мне было все равно, какие данные в 99%, поэтому я никогда не проверю.

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

Кристиан Зауэр
источник
2
«У меня есть API, которому нужен только 1% данных, передаваемых другим API». Затем возникает вопрос, почему ваш API ожидает в 100 раз больше данных, чем ему действительно нужно. Если вам нужно хранить непрозрачные данные для передачи, вам не нужно конкретно указывать, что это такое, и не нужно объявлять их в каком-либо конкретном формате, и в этом случае вызывающая сторона не будет нарушать ваш контракт ,
Во
1
@ Voo - я подозреваю, что они вызывают какой-то внешний API (например, «получить информацию о погоде для города X»), а затем выбирают нужные данные («текущая температура») и игнорируют остальную часть возвращаемых данных («дождь»). "," ветер "," прогноз температуры "," холод ветра "и т. д.)
Stobor
@ChristianSauer - я думаю, что вы не так уж далеки от того, что является более широким консенсусом - 1% данных, которые вы используете, имеет смысл проверять, но 99%, которые вы не делаете, не обязательно проверять. Вам нужно только проверить вещи, которые могут испортить ваш код.
Stobor
0

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

Причиной тестирования является то, что если по какой-то причине API / ввод неверен, моя программа не может полагаться ни на что. Может быть, моя программа была связана со старой версией API, которая отличается от того, во что я верю? Может быть, моя программа наткнулась на ошибку во внешней программе, которая никогда не случалась раньше. Или, что еще хуже, происходит все время, но никому нет дела! Может быть, хакер вводит в заблуждение внешнюю программу, чтобы она возвратила вещи, которые могут навредить моей программе или системе?

Два исключения для тестирования всего в моем мире:

  1. Производительность после тщательного измерения производительности:

    • никогда не оптимизируйте, прежде чем измерять. Тестирование всех вводимых / возвращаемых данных чаще всего занимает очень мало времени по сравнению с реальным вызовом, поэтому удаление его часто экономит мало или ничего. Я бы все равно сохранил код обнаружения ошибок, но закомментировал бы его, возможно, макросом или просто закомментировал его.
  2. Когда вы не знаете, что делать с ошибкой

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

Как тщательно проверить входные / возвращаемые значения - важный вопрос. Например, если говорят, что API возвращает строку, я бы проверил, что:

  • тип данных в действительности является строкой

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

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

Причина тестирования параметров, которые я отправляю в API, заключается в том, чтобы убедиться, что я получаю верный результат. Опять же, выполнение этих тестов перед вызовом API может показаться ненужным, но это требует очень мало производительности и может отлавливать ошибки в моей программе. Следовательно, тесты являются наиболее ценными при разработке системы (но в настоящее время каждая система, кажется, находится в постоянном развитии). В зависимости от параметров тесты могут быть более или менее тщательными, но я склонен обнаруживать, что вы часто можете устанавливать допустимые минимальные и максимальные значения для большинства параметров, которые может создать моя программа. Возможно, строка всегда должна содержать не менее 2 символов и иметь длину не более 2000 символов? Минимум и максимум должны быть внутри того, что позволяет API, поскольку я знаю, что моя программа никогда не будет использовать полный диапазон некоторых параметров.

ghellquist
источник