Безпечне програмування

Безпечне програмування — одна з форм безпечного проектування програм, мета якої забезпечити тривале функціонування певної частини коду програми під впливом непередбачуваних обставин. Його ідея полягає у зменшенні або навіть ліквідації можливості появи прояву закону Мерфі. Техніка безпечного програмування використовується особливо часто коли потрібно звести до мінімуму використання коду не за призначенням.

Мета безпечного програмування покращити програмний код в таких його аспектах:

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

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

Захищене програмування

[ред. | ред. код]

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

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

int risky_programming(char *input){
  char str[1000+1];     // one more for the null character
  // ...
  strcpy(str, input);   // copy input
  // ...
}

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

Натомість програміст, знайомий з безпечним програмуванням не допустить цього.  Оскільки згідно закону Мерфі, якщо в коді є помилка, то вона все одно виявить себе. Помилка в даному прикладі демонструє вразливість, що допускає переповнення буфера.

Проблему можна вирішити так:

int secure_programming(char *input){
  char str[1000];
  // ...
  strncpy(str, input, sizeof(str)); // copy input without exceeding the length of the destination
  str[sizeof(str) - 1] = '\0'; // if strlen(input) == sizeof(str) then strncpy won't NUL terminate
  // ...
}

Техніки безпечного програмування

[ред. | ред. код]

Ось декілька технік безпечного програмування:

Розумне повторне використання коду

[ред. | ред. код]

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

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

Унаслідувані проблеми

[ред. | ред. код]

Перед повторним використанням старого коду, бібліотек, API та інших компонент програм потрібно переконатися, що цей код можна використовувати повторно. Цілком можливо, що він може викликати проблеми наслідування.

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

Багато програмних продуктів мають проблеми з старим унаслідуваним кодом, наприклад:

  • Унаслідуваний код може не відповідати принципам безпечного програмування, і тому може бути значно нижчої якості ніж нещодавно створений код.
  • Успадкований код був написаний і ввідтестований для умов, які більше не є актуальними. Старі тести можуть бути не дійсними.
    • Приклад 1: успадкований код розроблявся для ASCII вводу, але зараз ввід UTF-8.
    • Приклад 2: успадкований код компілювався і тестувався на 32-бітний архітектурі, але під час компіляції на 64-бітній архітектурі можути виникнути проблеми з арифметикою(наприклад помилки при визначенні знаку числа, недійсне приведення типів).
    • Приклад 3: успадкований код розроблявся для комп'ютерів без доступу до мережі. Тому цей код стає вразливим на комп'ютері з мережею.
  • Успадкований код розроблявся без урахування теперішніх загроз. Наприклад, написаний в 90-х роках минулого століття швидше за все буде схильний до включення коду, оскільки в той час ця проблема не була до кінця усвідомленою.

Відомі приклади проблем з успадкованим кодом:

  • BIND 9, представлений Полом Віксі та Девідом Конрадом «Безпека була ключовим фактором в дизайні» * [Архівовано 31 січня 2015 у Wayback Machine.], безпека назв, надійність, маштабованість і нові протоколи були ключовими проблемами при переписувані старого коду.
  • Microsoft Windows страждав від Windows Metafile вразливості та іншими проблемами використання WMF формату. Центр Безпеки Майкрософт пояснює особливості WMF так «В 1990 було додано підтримку WMF… Тоді безпека виглядала по-іншому… ми довіряли їй» *
  • Oracle також бореться з проблемами успадкованого коду. Наприклад, старий код написаний без адресації, не обробляв SQL-включення. В результаті виникали вразливості безпеки, на виправлення яких йшов час а також виникали неповні виправлення. Це призвело до різкої критики з боку експертів безпеки. Окрім того дефолтні інсталятори не відповідали власним вимогам безпеки. Наприклад, Oracle Database Security Checklist [Архівовано 6 серпня 2009 у Wayback Machine.], оскільки багато програм вимагають менш безпечних налаштувань безпеки, щоб функціонувати правильно.

Безпечна обробка вводу і виводу

[ред. | ред. код]

Канонізація

[ред. | ред. код]

Зловмисники можуть придумувати нові види представлення некоректних даних.

Для прикладу, якщо ви перевіряєте чи запит на файл не має вигляд «/etc/passwd», зловмисник може придумати інший варіант назви цього файлу, наприклад «/etc/./passwd».

Щоб уникнути помилок через неканонічні вхідні дані, використовуйте канонізаційні бібліотеки.

Нетолерантність до потенційних помилок

[ред. | ред. код]

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

Інші техніки

[ред. | ред. код]
  • Однією з найпоширеніших є проблема неперевіреного використання структур фіксованого розміру та функцій для роботи з даними різної довжини(проблема переповнення буфера). Це особливо поширено для стрічок в мові С. Не можна використовувати такі функції бібліотеки С як gets, оскільки функція не знає розміру об'єкта, з яким вона працює. Такі функції як scanf можуть використовуватися, але програміст має вибрати вірний формат стрічки.
  • Шифрування й аутентифікація всіх важливих даних, що передаються мережею. Не потрібно придумувати власну систему шифрування, просто використовуйте перевірену.
  • Всі дані важливі, поки не буде доведено протилежне
  • Всі дані зіпсовані, поки не доведено протилежне
  • Весь код небезпечний, поки не доведено протилежне
    • Безпечність коду не можна довести з боку користувача, або іншими словами: «ніколи не довіряй користувачу»
  • Якщо дані будуть перевірятися на коректність, то потрібно перевіряти їх коректність, а не зіпсованість.
  • Контрактне програмування
    • Контрактне програмування використовує передумови, післяумови і інваріанти, щоб переконатися, що дані і стан програми в цілому є коректним. Це може включати перевірку аргументів функції або методу перед виконанням тіла функції. Після тіла функції перевіряється стан об'єкта або інших даних, після цього повертається результат роботи і функція завершується.
  • Припущення
    • В тілі функції деколи потрібно перевірити чи об'єкт є валідним(не вказує на null), чи довжина масиву правильна, і т. д. і т. ін. Краще не довіряти бібліотекам, які ви не створювали. Тому кожного разу коли ви їх викликаєте потрібно перевіряти що вони повертають. Часто допомагає створення невеличкої бібліотеки припущень і перевірок для зручнішого відслідковування помилок.
  • Надавайте перевагу винятковим ситуаціям(виняткам) над поверненням коду помилки
    • Взагалі кажучи, після виникнення виняткової ситуації потрібно вивести зрозуміле повідомлення про помилку, а не повертати код помилки, яка не дасть користувачу жодної інформації. Тому використовуючи винятки зводиться до мінімуму кількість скарг і підвищується надійність і безпека програмного забезпечення.

Зовнішні посилання

[ред. | ред. код]