Чи слід оберігати від несподіваних значень зовнішніх API?


51

Скажімо, ви кодуєте функцію, яка бере вхід із зовнішнього API MyAPI.

Цей зовнішній API MyAPIмає контракт, в якому зазначено, що він поверне а 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

Якщо цього не зробити, то в кращому випадку ви залишите інформацію про сміття, яку вам доведеться пізніше очистити, але, в гіршому випадку, ви залишите можливість для зловмисних подвигів, якщо ця служба по течії порушиться деяким чином (qv цільовий злом). Коло проблем, що стосуються між собою, включає отримання заявки у неповерненому стані.


З коментарів я бачу, що, можливо, моя відповідь могла використати трохи розширення.

Під "ніколи не довіряйте вхідним матеріалам", я просто маю на увазі, що ви не можете припустити, що ви завжди отримуватимете достовірну та достовірну інформацію з висхідної та нижньої течії систем, і тому ви завжди повинні обгрунтувати цей внесок якнайкраще, або відхилити це.

Один аргумент спливав у коментарях, які я розгляну на прикладі. Хоча так, вам доведеться певною мірою довіряти вашій ОС, але це нерозумно, наприклад, відхиляти результати генератора випадкових чисел, якщо ви запитаєте його на число від 1 до 10, і воно відповідає "bob".

Аналогічно, у випадку з ОП, ви, безумовно, повинні переконатися, що ваша заявка приймає лише дійсний вхід від верхньої служби. Що робити, коли це не в порядку, залежить від вас, і багато що залежить від фактичної ділової функції, яку ви намагаєтеся виконати, але мінімально ви будете реєструвати її для подальшої налагодження та іншим чином переконатися, що ваша програма не працює перетворитись у незворотний або невпевнений стан.

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


20
Що таке qv?
JonH

15
@JonH в основному "дивись також" ... Хакер цілі - це приклад того, що він посилається на en.oxforddic slova.com/definition/qv .
andrewtweber

8
Ця відповідь, як вона є, просто не має сенсу. Неможливо передбачити кожного і кожного способу, коли стороння бібліотека може погано поводитися. Якщо документація функції бібліотеки явно запевняє, що результат завжди матиме деякі властивості, то ви повинні мати можливість розраховувати на те, що дизайнери забезпечили, що ця властивість буде фактично утримуватися. Це їхня відповідальність мати тестовий набір, який перевіряє подібні речі, і надсилати виправлення помилок у разі виникнення ситуації, коли цього немає. Ви перевірити ці властивості в власному коді порушує принцип DRY.
близько

23
@leftaroundabout ні, але ви повинні мати можливість передбачити всі дійсні речі, які ваша програма може прийняти, а решту відхилити.
Павло

10
@leftaroundabout Йдеться не про недовіру до всього, а про недовіру зовнішнім недовірливим джерелам. Це все про моделювання загроз. Якщо ви ще не зробили це з тим, що ваше програмне забезпечення не захищене (як це може бути, якщо ви ніколи навіть не замислювалися проти того, які саме суб'єкти та загрози хочете захистити свою програму?). Для запуску програмного забезпечення заводу в бізнесі розумною умовою є припущення, що абоненти можуть бути шкідливими, тоді як рідко доцільно вважати, що ваша ОС - це загроза.
Ву

33

Так , звичайно. Але що змушує вас думати, що відповідь може бути іншою?

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

Однак скільки зусиль ви повинні вкласти, щоб вирішити подібний випадок, дуже залежить від конкретних випадків, і відповісти на них можна лише в контексті вашої системи. Часто короткого входу в журнал і дозволу програми закінчуватися граціозно може бути досить. Іноді вам буде краще застосувати деяку детальну обробку винятків, мати справу з різними формами "неправильних" повернених значень, і, можливо, доведеться реалізувати якусь стратегію резервного копіювання.

Але це має певну різницю, якщо ви пишете лише якийсь додаток для форматування електронних таблиць, який використовується менше 10 людей, і фінансовий вплив аварії програми досить низький, або якщо ви створюєте новий автономний автомобіль система, де збій програми може коштувати життя.

Тож немає ярлика проти роздумів про те, що ви робите , використання здорового глузду завжди обов'язкове.


Що робити - це ще одне рішення. У вас може виникнути збій у вирішенні. Будь-яке асинхронне можна повторити перед створенням журналу винятків (або мертвої літери). Активне сповіщення постачальника чи постачальника може бути можливим, якщо проблема не зникає.
mckenzm

@mckenzm: той факт, що ОП задає питання, де буквальна відповідь, очевидно, може бути лише "так" - це IMHO знак, що вони можуть бути не просто зацікавлені в буквальній відповіді. Схоже, вони запитують "чи потрібно захищати від різних форм несподіваних значень з API і поводитися з ними по-різному" ?
Док Браун

1
хм, підхід лайно / короп / мертвий. Чи ми винні у передачі поганих (але законних) запитів? чи можлива відповідь, але не корисна для нас зокрема? чи реакція корумпована? Різні сценарії. Зараз це звучить як домашнє завдання.
mckenzm

21

Принцип надійності - конкретно, "бути ліберальним у тому, що ти приймаєш" половину - є дуже поганою ідеєю в програмному забезпеченні. Спочатку він був розроблений в умовах апаратних засобів, де фізичні обмеження роблять інженерні допуски дуже важливими, але в програмному забезпеченні, коли хтось надсилає вам неправильну інформацію або іншим чином неправильний вклад, у вас є два варіанти. Ви можете або відхилити це (бажано з поясненням того, що пішло не так), або ви можете спробувати розібратися, що це мало означати.

EDIT: Виявляється, я помилився у наведеному вище твердженні. Принцип надійності походить не зі світу апаратних засобів, а з Інтернет-архітектури, зокрема RFC 1958 . У ньому зазначено:

3.9 Будьте суворими при надсиланні та толерантними при отриманні. Реалізація повинна точно відповідати технічним характеристикам при надсиланні в мережу і допускати несправне введення з мережі. Коли ви сумніваєтесь, мовчки відкиньте несправний ввід, не повертаючи повідомлення про помилку, якщо цього не вимагає специфікація.

Це, зрозуміло кажучи, просто неправильно від початку до кінця. Важко уявити більш неправильне поняття поводження з помилками, ніж "мовчки відкинути несправний вхід без повернення повідомлення про помилку" з причин, наведених у цій публікації.

Дивіться також документ IETF «Шкідливі наслідки принципу стійкості» для подальшої деталізації цього питання.

Ніколи, ніколи, ніколи не вибирайте цей другий варіант, якщо у вас не є ресурси, еквівалентні пошуковій команді Google, щоб задіяти ваш проект, адже саме для цього потрібно розробити комп’ютерну програму, яка робить щось, що наближене до гідної роботи в цій конкретній проблемній області. (І навіть тоді пропозиції Google відчувають, що вони виходять прямо з лівого поля приблизно в половину часу.) Якщо ви спробуєте це зробити, те, що у вас вийде, - це сильний головний біль, коли ваша програма часто намагатиметься інтерпретувати поганий вклад як X, коли те, що насправді мав на увазі відправник, було Y.

Це погано з двох причин. Очевидним є те, що тоді у вас є погані дані у вашій системі. Менш очевидним є те, що в багатьох випадках ні ви, ні відправник не усвідомлюєте, що щось пішло не так, як набагато пізніше вниз по дорозі, коли щось вам підірветься в обличчя, і раптом у вас з’явиться великий, дорогий безлад, який виправити, і поняття не маєте що пішло не так, оскільки помітний ефект поки що віддалений від першопричини.

Ось чому існує принцип Fail Fast; збережіть усіх, хто залучає головний біль, застосувавши його до своїх API.


7
Хоча я згоден з принципом того, що ви говорите, я думаю, що ви помиляєтесь WRT у намірі Принципу надійності. Я ніколи не бачив, щоб це означало, "прийміть погані дані", лише "не будьте надмірно вибагливі щодо хороших даних". Наприклад, якщо вхід - це файл CSV, Принцип надійності не був би вагомим аргументом для спроби розбору дат у несподіваному форматі, але підтримував би аргумент про те, що висновок стовпчика із стовпця буде гарною ідеєю .
Морген

9
@Morgen: Принцип надійності використовувався для того, щоб переконатися, що браузери повинні приймати досить неохайний HTML, і призвело до того, що розгорнуті веб-сайти набагато неохайніші, ніж це було б, якби браузери вимагали належного HTML. Однак велика частина проблеми полягала у використанні загального формату для вмісту, створеного людиною та створеного машиною, на відміну від використання окремих форматів, що редагуються людиною, і машинозмінюваних, а також утилітів для перетворення між ними.
supercat

9
@supercat: все-таки - або просто звідси - HTML і WWW були надзвичайно успішними ;-)
Doc Brown

11
@DocBrown: Багато справді жахливих речей стали стандартами просто тому, що це був перший підхід, який, можливо, був доступний, коли комусь з великою кількістю клопоту потрібно було прийняти щось, що відповідає певним мінімальним критеріям, і до моменту, коли вони отримали тягу, це було занадто пізно, щоб вибрати щось краще.
supercat

5
@supercat Рівно. JavaScript одразу приходить в голову, наприклад ...
Мейсон Уілер

13

Загалом, код повинен будуватися таким чином, щоб підтримувати принаймні такі обмеження, коли це можливо:

  1. При правильному введенні виробляти правильний вихід.

  2. Якщо дано дійсне введення (що може бути, а може і не бути правильним), видайте дійсний результат (аналогічно).

  3. Якщо дано недійсний вхід, обробіть його без будь-яких побічних ефектів, крім тих, що викликані звичайним введенням, або тих, які визначені як сигналізація про помилку.

У багатьох ситуаціях програми по суті будуть проходити через різні фрагменти даних, не особливо дбаючи про те, чи дійсні вони. Якщо такі фрагменти містять недійсні дані, результат програми, ймовірно, містить недостовірні дані. Якщо програма спеціально не призначена для перевірки всіх даних і не гарантує, що вона не дасть недійсний вихід навіть при наданні недійсного введення , програми, які обробляють його вихід, повинні передбачати можливість недійсних даних всередині нього.

Хоча перевірка даних на ранніх етапах часто є бажаною, вона не завжди є особливо практичною. Крім усього іншого, якщо обґрунтованість одного фрагмента даних залежить від вмісту інших фрагментів, і якщо більшість даних, що подаються в певній послідовності кроків, будуть відфільтровані по ходу, обмежуючи перевірку на дані, які роблять це через всі етапи можуть давати набагато кращі показники, ніж намагатися перевірити все.

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


Тоді все зводиться до того, щоб вирішити, чи є результат виклику API "входом".
мастов

@mastov: Відповіді на багато питань залежатимуть від того, як визначати "входи" та "спостережувані поведінки" / "результати". Якщо метою програми є обробка чисел, збережених у файлі, її введення може бути визначене як послідовність чисел (у цьому випадку речі, які не є номерами, не є можливими вводами), або як файл (у такому випадку нічого, що Можливо, у файлі з'явиться можливий вклад).
supercat

3

Порівняємо два сценарії та спробуємо прийти до висновку.

Сценарій 1 Наша програма передбачає, що зовнішній API буде поводитися відповідно до угоди.

Сценарій 2 Наш додаток передбачає, що зовнішній API може поводитися неправильно, отже, додайте запобіжні заходи.

Загалом, є шанс будь-якого API або програмного забезпечення порушити домовленості; може бути наслідком помилки або несподіваних умов. Навіть API може мати проблеми у внутрішніх системах, що призводить до несподіваних результатів.

Якщо наша програма написана з припущенням, що зовнішній API буде дотримуватися угод і уникати додавання будь-яких запобіжних заходів; хто буде партією, яка зіткнеться з проблемами? Це будемо ми, ті, хто написав інтеграційний код.

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

Тож я вважаю, що буде краще переконатися, що у вашій програмі є якийсь додатковий код для вирішення несподіваних сценаріїв.


1

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

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


1

Загалом, так, ви завжди повинні захищатись від несправних входів, але залежно від виду API "охорона" означає різні речі.

Що стосується зовнішнього API до сервера, ви не хочете випадково створити команду, яка вибиває з ладу або ставить під загрозу стан сервера, тому ви повинні захиститись від цього.

Для такого API, як, наприклад, клас контейнерів (список, вектор тощо), викидання винятків є прекрасним результатом, компрометація стану екземпляра класу може бути прийнятною до певної міри (наприклад, відсортований контейнер, наданий несправним оператором порівняння, не буде сортувати), навіть збої програми можуть бути прийнятними, але погіршення стану програми - наприклад, запис у випадкові місця пам'яті, не пов'язані з екземпляром класу, - швидше за все, це не так.


0

Для того, щоб дати дещо іншу думку: я вважаю, що може бути прийнятним просто працювати з даними, які вам надаються, навіть якщо це порушує його контракт. Це залежить від використання: це щось, що ОБОВ'ЯЗКОВО має бути для вас рядком, або це щось, що ви просто відображаєте / не використовуєте тощо. В останньому випадку просто прийміть це. У мене є API, який просто потребує 1% даних, переданих іншим api. Мені було б менше байдуже, які дані є у 99%, тому я ніколи його не перевірятиму.

Повинно бути баланс між "помилками, тому що я не перевіряю свої дані достатньо" та "я відкидаю дійсні дані, тому що я занадто сувора".


2
"У мене є API, який просто потребує 1% даних, переданих іншим api." Потім це відкриває питання, чому ваш API очікує в 100 разів більше даних, ніж насправді потрібно. Якщо вам потрібно зберігати непрозорі дані для передачі, вам не потрібно конкретизувати, що це таке, і не потрібно декларувати їх у будь-якому конкретному форматі; у такому випадку абонент не порушить ваш контракт .
Ву

1
@Voo - Я підозрюю, що вони викликають якийсь зовнішній API (наприклад, "отримуйте детальну інформацію про погоду для міста X"), а потім вибирайте потрібні їм дані ("поточна температура") та ігноруючи решту повернених даних ("дощ "," вітер "," прогнозована температура "," холодний вітер "тощо)
Стобор

@ChristianSauer - Я думаю, ви не так далеко від ширшого консенсусу - 1% даних, які ви використовуєте, має сенс перевірити, але 99%, які вам не обов’язково потрібно перевіряти. Вам потрібно лише перевірити речі, які можуть відключити ваш код.
Стобор

0

Я беру на себе це завжди, завжди перевіряти кожен вхід у мою систему. Це означає, що кожен параметр, повернений з API, повинен бути перевірений, навіть якщо моя програма не використовує його. Я схильний також перевіряти кожен параметр, який я надсилаю в API на правильність. З цього правила є лише два винятки, див. Нижче.

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

Два винятки для тестування всього в моєму світі:

  1. Продуктивність після ретельного вимірювання продуктивності:

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

    • буває, не часто, коли ваш дизайн просто не дозволяє обробляти таку помилку, яку ви знайдете. Можливо, ви повинні зробити це помилка в журналі, але в системі немає помилок. Практично завжди можна знайти спосіб «запам’ятати» помилку, що дозволить принаймні ви як розробник пізніше перевірити її. Лічильники помилок - це одна гарна річ, яка має бути в системі, навіть якщо ви вирішили не вести журнал.

Важливо питання, наскільки ретельно перевірити значення входів / повернень. Наприклад, якщо сказано, що API повертає рядок, я би переконався, що:

  • тип даних actully - це рядок

  • і ця довжина знаходиться між значеннями min та max. Завжди перевіряйте рядки на максимальний розмір, з яким може розраховувати моя програма (повернення занадто великих рядків - це класична проблема безпеки в мережевих системах).

  • Деякі рядки слід перевірити на "незаконні" символи чи вміст, коли це доречно. Якщо ваша програма може надіслати рядок, щоб потім повідомити базу даних, непогано перевірити наявність атак на бази даних (пошук ін'єкції SQL). Ці тести найкраще робити на кордонах моєї системи, де я можу точно визначити, звідки прийшов напад, і я можу провалитися рано. Зробити повний тест на ін'єкцію SQL може бути складним, коли рядки згодом поєднуються, тому тест слід зробити перед тим, як викликати базу даних, але якщо ви рано знайдете якісь проблеми, це може бути корисним.

Причина тестування параметрів, які я надсилаю в API, полягає в тому, щоб переконатися, що я отримаю правильний результат. Знову ж таки, тестування перед викликом API може здатися непотрібним, але це вимагає дуже малої продуктивності та може призвести до помилок у моїй програмі. Отже, тести є найціннішими при розробці системи (але сьогодні, здається, кожна система постійно розвивається). Залежно від параметрів тести можуть бути більш-менш ретельними, але я схильний вважати, що ви можете часто встановлювати допустимі значення min та max для більшості параметрів, які моє програма могла створити. Можливо, рядок завжди повинен містити принаймні 2 символи та мати максимум 2000 символів? Мінімум і максимум повинні бути всередині того, що дозволяє API, оскільки я знаю, що моя програма ніколи не використовуватиме повний діапазон деяких параметрів.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.