Русский
Русский
English
Статистика
Реклама

Devsecops

DevOps с человеческим лицом

04.08.2020 14:20:22 | Автор: admin
Мы часто рассказываем, что полезного услышали на конференциях, реже о спикерах будущих мероприятий. И почти никогда не показываем людей, которые стоят за кулисами каждого хорошего выступления и дирижируют ансамблем тем. Пора это менять, потому что то, кто и с каким подходом готовит программу, может рассказать о конференции гораздо больше, чем список тезисов.

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


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

В этой беседе участвовали четверо: Тимур Батыршин, Дмитрий Зайцев, Валерия Пилия и Мона Архипова. И так удачно совпало, они все смотрят на DevOps немного под разными углами и приносят разные акценты в программу конференции.

Тимур Батыршин (erthad) в IT более 15 лет, за это время участвовал в строительстве дистрибутивов Linux, виртуализации серверов, когда это еще не было модным, автоматизировал разворачивание серверов на облаках и строил архитектуру облачных приложений.

Тимур Батыршин: Можно сказать, что путь в программный комитет DevOpsConf начался в 2012-2013 году с сообщества Hangops Ru (Hangops можно расшифровать как Ops на Hangouts, именно в Hangouts проводились регулярные беседы об индустрии). Постепенно сообщество выросло за пределы чата, и основной костяк энтузиастов стал организовывать DevOpsDays Moscow. Дима и Лера тоже участвовали, а больше всех двигал конференцию Саша Титов (osminog).

Дмитрий Зайцев (bhavenger) развивал DevOps- и SRE-практики, когда это ещё не было модным. Совмещал их с ITIL и Cobit, пока те ещё были в моде. Имеет опыт работы в gamedev, adtech, bigdata, fintech, marketing. Является одним из организаторов DevOpsDays Moscow, DevOps Moscow, Hangops Ru. Сейчас Head of SRE во flocktory.com.

Дмитрий Зайцев: Году в 2015 я как-то оказался в чате Hangops, вскоре стал участвовать в записи и организации видеопосиделок. В 2017 Саша Титов обратился ко мне с идеей сделать конференцию. Тогда она называлась RootConf и была больше для системных администраторов. Я как раз был среди тех, кто пытался её изменить. В 2018 это удалось, мы перезапустили конференцию уже под названием DevOpsConf, сместив акцент с тулинга для сисадминов на DevOps: что это такое, что мы хотим от этого подхода, чего хочет рынок от специалистов в DevOps.

Валерия Пилия работает во flocktory.com Infrastructure Engineer, занимается поддержкой инфраструктуры на AWS с k8s. Участвовала в адаптации русскоязычного издания книги DevOps Handbook и является одним из организаторов митапов DevOps Moscow и конференции DevOpsDays Moscow 2019. В программный комитет DevOpsConf пришла два года назад, как и другие участники беседы с подачи Александра Титова.

Валерия Пилия: Я присоединилась к программному комитету конференции, потому что мне было интересно узнать, как это устроено изнутри. У меня даже догадок не было, как, например, распределяются задачи между программным комитетом и организаторами события. Мною двигало любопытство и мне дали возможность его удовлетворить. И это та возможность, от которой не отказываются.

Мона Архипова (Mona_Sax) COO sudo.su (МИРЦ), до этого занимала руководящие и экспертные должности в области безопасности и IT. В ежедневной работе активно использует DevOps-практики и тоже начинала, когда это еще не было мейнстримом. Мона присоединилась к программному комитету после отличного доклада Как (вы)жить без отдела безопасности о том, что безопасность это ответственность каждого человека. Теперь несет безопасность в массы не только на конференциях по безопасности, но и на DevOps Live.

Мона Архипова: Когда Саша Титов пригласил меня в ПК, я решила, не можешь предотвратить возглавь. К сожалению, большинство IT-конференций не уделяет внимание безопасности и получается вжух-вжух и в продакшн, а потом бывает очень больно от того, что что-то утекло или выставлено без пароля. Хотелось бы насадить в IT понимание того, что безопасность нужна, но важно искать баланс между безопасность и скоростью DevOps.

Безопасность и DevOps


Как вы уже поняли, Мона продвигает DevSecOps и в этом году безопасности на конференции станет больше. Но это не будет конференция по безопасности внутри DevOps Live все выступления рассчитаны на любой уровень экспертизы, то есть подойдут тем, кто только начинает задумываться о безопасности. Но и тем, кто уже работает в этом направлении, покажем некоторые неочевидные моменты и полезные инструменты.

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

DevOps системным инженерам: инструменты и не только


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

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

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

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

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

Валерия Пилия: Было бы наивно ставить перед собой исключительно просветительскую цель. Она, конечно, заложена в конференциях by design, но невозможно научить кого-либо против воли. Зато можно заинтересовать, заставить удивиться и усомниться. Помочь узнать что-то новое, что, может быть не сразу, но пригодится и получится что-то очень классное.

Мона Архипова: Я в свою очередь привношу старые добрые вещи. По одной простой причине: хотя область и молодая, уже успело смениться поколение тех, кто занимается инфраструктурой, DevOps и т.д. Нынешние джуниоры уже не слышали про этот наш ITIL и другие страшные аббревиатуры. Поэтому, мне кажется, важно не только рассказывать о новом, но и напоминать, что старые практики никуда не делись, они все еще актуальны. Новые инструменты безусловно облегчают жизнь, но фундамент и база тоже должны быть.

Дмитрий Зайцев: Ждать от новых людей в индустрии, что они будут изучать ITIL, я бы не стал. Кажется, основное, что нужно было взять из ITIL, естественным образом перешло в современные практики. С другой стороны, странно, если вы работаете в IT и ничего не читали об Extreme Programming, хотя этой методологии уже больше двадцати лет.

Тимур Батыршин: А мне кажется, что тут есть две стороны. Когда тебе нужно просто сделать что-то практическое, то наверное не нужно тратить время на изучение истоков. Но если хочешь разобраться в устройстве IT в целом и понять, как сделать, чтобы всё работало, то имеет смысл представлять себе эволюцию.

Мона Архипова: Рано или поздно любой технический специалист сталкивается с тем, что решения его проблемы нет в гугле. И вот тут и нужна фундаментальная база. Да и вообще, стильные модные молодежные системы управления логами изобрели, если не ошибаюсь, в 1969 году.

DevOps в продуктовой разработке


Идея конференции DevOps Live посмотреть на DevOps с разных сторон, в частности с точки зрения продуктовой разработки.

В рамках DevOps-подхода технические решения должны быть связаны с задачами бизнеса. Но инженеры часто не очень хорошо себе представляют, в чём эти задачи состоят. Например, считают, что раз в DORA Report написано, что крутые организации релизятся по несколько раз в день, то для бизнеса важнее всего time-to-market.

Тимур Батыршин: Я как любой инженер раньше считал, что главное хорошо писать код, чётко настраивать его работу, и всё будет отлично. А потом оказалось, что нужно не только настаивать сервисы и писать код, но еще и двигать дела в компании. А в любой компании больше, скажем, двадцати человек возникают дополнительные задачи состыковать рабочие группы и специалистов, которые хотят разного. Об этом нам и говорили отцы DevOps: как сделать так, чтобы все вместе работали над одними задачами.

Для DevOps Live Тимур Батыршин и Андрей Шорин поговорили с Product Owner'ам. Оказалось, что time-to-market как его понимают инженеры частые релизы им не очень-то и важен. Важнее предсказуемость, которая достигается, когда продуктовые команды, команды разработки, эксплуатации и менеджмент работают над общими целями и понимают друг друга. Поэтому на конференции уделим внимание тому, как договариваться, устанавливать доверие, работать с токсичностью и т.д.

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

Валерия Пилия: Когда человек начинает чуть больше понимать себя как профессионала и задумывается, что он хочет от работы в контексте все своей жизни, оказывается, что это не целыми днями ставить Kubernetes. Потому что: А зачем?. Тут и до экзистенциального кризиса недалеко.

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

DevOps на Chief-уровне


Внедрение DevOps-практик в компании однозначно не обходится без активного включения CTO, то есть среди аудитории DevOps Live есть технические руководители. ПК решили определить, какие боли у CTO и как можно помочь их решить, потому что оказалось, что на современных IT-конференциях такого контента мало. Чаще всего рассматриваются более частные вопросы отдельных команд или технических задач.

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

Человек с приставкой Chief помимо технических решений занимается еще и такими увлекательными вещами как: подсчет стоимости владения и затрат на инфраструктуру, планирование технологического стека, capacity management и прочее, что выпадает из поля зрения большинства.

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

Взаимопонимание во главе угла


Как видите, все темы взаимосвязаны и являются частью пазла. Не нужно думать: Ага, продуктовая разработка меня не интересует, до СТО мне еще далеко остается только часть с инженерными практиками. Это не так.

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

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

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

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

Всё это онлайн


Тема, о которой мы не могли не поговорить, онлайн, куда пока что переместились все мероприятия. Мы в Онтико уже провели три тестовых и три больших онлайн-конференции (как они прошли можно посмотреть в статье о KnowledgeConf), не всё так страшно. Но мы понимаем, какие могут быть сомнения, более того, они есть даже внутри ПК.

Две главные проблемы онлайна на наш взгляд:

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

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

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

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

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

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

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

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

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

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

DevOps Live получается:

  • Вот такой ширины с точки зрения форматов: будут доклады, мастер-классы, воркшопы, дискуссии, групповые и индивидуальные консультации и это еще не все карты раскрыты.
  • Вот такой высоты с точки зрения аудитории: ждём на конференции инженеров разных IT-специальностей, безопасников, CТО, менеджеров продуктов и владельцев бизнеса.
  • А еще и глубины, потому что хардкорные вещи тоже обязательно будут.

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

Мы знаем, как трудно выделить время, чтобы, сидя дома, начать что-то смотреть, вовлекаться в это, думать, дискутировать и общаться с другими людьми. Ради таких людей и хочется делать реально классный контент, искать кого-то, договариваться, придумывать новые форматы. Приходите, мы вас ждём!

DevOps Live пройдет в два этапа 29-30 сентября и 6-7 октября. До 15 августа еще можно подать доклад или забронировать билет перед повышением цены.

Следите за обновлениями в телеграме или рассылке будем сообщать о принятых спикерах и открывать подробности форматов.

Всё, что осталось за кадром в этой беседе, можно будет уточнить 5 августа в 18:00 на онлайн-встрече программного комитета с докладчиками и сочувствующими. Подключайтесь и задавайте свои вопросы, уточняйте темы для заявок или давайте программному комитету задание закрыть свой запрос. Встреча открытая, но нужно зарегистрироваться, чтобы мы прислали ссылку.
Подробнее..

Статический анализ кода коллекции библиотек PMDK от Intel и ошибки, которые не ошибки

19.08.2020 12:13:25 | Автор: admin
PVS-Studio, PMDK

Нам предложили проверить с помощью анализатора PVS-Studio коллекцию открытых библиотек PMDK, предназначенную для разработки и отладки приложений с поддержкой энергонезависимой памяти. Собственно, почему бы и нет. Тем более это небольшой проект на языке C и C++ с общим размером кодовой базы около 170 KLOC, если не считать комментарии. А значит, обзор результатов анализа не займёт много сил и времени. Let's go.

Для анализа исходного кода будет использован инструмент PVS-Studio версии 7.08. Читатели нашего блога, естественно, давно знакомы с нашим инструментом, и я не буду на нём останавливаться. Для тех, кто впервые зашёл к нам, предлагаю обратиться к статье "Как быстро посмотреть интересные предупреждения, которые выдает анализатор PVS-Studio для C и C++ кода?" и попробовать бесплатную триальную версию анализатора.

В этот раз я загляну внутрь проекта PMDK и расскажу о замеченных ошибках и недочётах. По моему внутреннему ощущению, их было немного, что говорит о хорошем качестве кода этого проекта. Из интересного можно отметить, что найдено несколько фрагментов неправильного кода, который тем не менее работает правильно :). Что я имею в виду станет понятнее из дальнейшего повествования.

Итак, PMDK это коллекция библиотек и инструментальных средств с открытым исходным кодом, предназначенных для упрощения разработки, отладки и управления приложениями с поддержкой энергонезависимой памяти. Подробнее здесь: PMDK Introduction. Исходники здесь: pmdk.

Давайте посмотрим, какие ошибки и недочёты я смогу в нём найти. Сразу скажу, что я был далеко не всегда внимателен при анализе отчёта и мог многое пропустить. Поэтому призываю авторов проекта не руководствоваться при исправлении дефектов исключительно этой статьёй, а перепроверить код самостоятельно. А для написания статьи мне будет достаточно и того, что я выписал, просматривая список предупреждений :).

Неправильный код, который работает


Размер выделяемой памяти


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

int main(int argc, char *argv[]){  ....  struct pool *pop = malloc(sizeof(pop));  ....}

Предупреждение PVS-Studio: V568 It's odd that 'sizeof()' operator evaluates the size of a pointer to a class, but not the size of the 'pop' class object. util_ctl.c 717

Классическая опечатка, из-за которой выделяется неверное количество памяти. Оператор sizeof вернёт не размер структуры, а размер указателя на эту структуру. Правильным вариантом будет:

struct pool *pop = malloc(sizeof(pool));

или

struct pool *pop = malloc(sizeof(*pop));

Однако, этот неправильно написанный код прекрасно работает. Дело в том, что структура pool содержит в себе ровно один указатель:

struct pool {  struct ctl *ctl;};

Получается, что структура занимает ровно столько, сколько и указатель. Всё хорошо.

Длина строки


Перейдём к следующему случаю, где вновь допущена ошибка с использованием оператора sizeof.

typedef void *(*pmem2_memcpy_fn)(void *pmemdest, const void *src, size_t len,    unsigned flags);static const char *initial_state = "No code.";static inttest_rwx_prot_map_priv_do_execute(const struct test_case *tc,  int argc, char *argv[]){  ....  char *addr_map = pmem2_map_get_address(map);  map->memcpy_fn(addr_map, initial_state, sizeof(initial_state), 0);  ....}

Предупреждение PVS-Studio: V579 [CWE-687] The memcpy_fn function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the third argument. pmem2_map_prot.c 513

Для копирования строки используется указатель на специальную функцию копирования. Обратите внимание на вызов этой функции, а вернее на её третий аргумент.

Программист предполагает, что оператор sizeof вычислит размер строкового литерала. Но, на самом деле, вновь вычисляется размер указателя.

Везение в том, что строка состоит из 8 символов, и её размер совпадает с размером указателя, если происходит сборка 64-битного приложения. В результате все 8 символов строки "No code." будут успешно скопированы.

На самом деле, ситуация ещё сложнее и интереснее. Интерпретация этой ошибки зависит от того, хотели ли скопировать терминальный ноль или нет. Рассмотрим два сценария.

Сценарий 1. Нужно было скопировать терминальный ноль. Тогда я неправ, и это вовсе не безобидная ошибка, которая себя не проявляет. Скопировано не 9 байт, а только 8. Терминального нуля нет, и последствия предсказать невозможно. В этом случае код можно исправить, изменив определение константной строки initial_state следующим образом:

static const char initial_state [] = "No code.";

Теперь значение sizeof(initial_state) равно 9.

Сценарий 2. Терминальный ноль вовсе и не требуется. Например, ниже можно увидеть вот такую строчку кода:

UT_ASSERTeq(memcmp(addr_map, initial_state, strlen(initial_state)), 0);

Как видите, функция strlen вернёт значение 8 и терминальный ноль не участвует в сравнении. Тогда это действительно везение и всё хорошо.

Побитовый сдвиг


Следующий пример связан с операцией побитового сдвига.

static intclo_parse_single_uint(struct benchmark_clo *clo, const char *arg, void *ptr){  ....  uint64_t tmax = ~0 >> (64 - 8 * clo->type_uint.size);  ....}

Предупреждение PVS-Studio: V610 [CWE-758] Unspecified behavior. Check the shift operator '>>'. The left operand '~0' is negative. clo.cpp 205

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

Приоритет операций


И рассмотрим последний случай, связанный с приоритетом операций.

#define BTT_CREATE_DEF_SIZE  (20 * 1UL << 20) /* 20 MB */

Предупреждение PVS-Studio: V634 [CWE-783] The priority of the '*' operation is higher than that of the '<<' operation. It's possible that parentheses should be used in the expression. bttcreate.c 204

Чтобы получить константу, равную значению 20 MB, программист решил выполнить следующие действия:

  • Сдвинул 1 на 20 разрядов, чтобы получить значение 1048576, т.е. 1 MB.
  • Умножил 1 MB на 20.

Другими словами, программист думает, что вычисления происходят так: (20 * (1UL << 20))

Но на самом деле приоритет оператора умножения выше, чем приоритет оператора сдвига и выражение вычисляется так: ((20 * 1UL) << 20).

Согласитесь, вряд ли программист хотел, чтобы выражение вычислилось в такой последовательности. Нет смысла в умножении 20 на 1. Так что перед нами тот случай, когда код работает не так, как задумывал программист.

Но эта ошибка никак не проявит себя. Неважно, как написать:

  • (20 * 1UL << 20)
  • (20 * (1UL << 20))
  • ((20 * 1UL) << 20)

Результат всё равно всегда одинаковый! Всегда получается нужное значение 20971520 и программа работает совершенно корректно.

Другие ошибки


Не там поставленная скобка


#define STATUS_INFO_LENGTH_MISMATCH 0xc0000004static voidenum_handles(int op){  ....  NTSTATUS status;  while ((status = NtQuerySystemInformation(      SystemExtendedHandleInformation,      hndl_info, hi_size, &req_size)        == STATUS_INFO_LENGTH_MISMATCH)) {    hi_size = req_size + 4096;    hndl_info = (PSYSTEM_HANDLE_INFORMATION_EX)REALLOC(hndl_info,        hi_size);  }  UT_ASSERT(status >= 0);  ....}

Предупреждение PVS-Studio: V593 [CWE-783] Consider reviewing the expression of the 'A = B == C' kind. The expression is calculated as following: 'A = (B == C)'. ut.c 641

Внимательно посмотрите вот сюда:

while ((status = NtQuerySystemInformation(....) == STATUS_INFO_LENGTH_MISMATCH))

Программист хотел сохранить в переменной status значение, которое возвращает функцию NtQuerySystemInformation, а затем сравнить его с константой.

Программист скорее всего знал, что приоритет оператора сравнения (==) выше, чем у оператора присваивания (=), и поэтому следует использовать скобки. Но опечатался и поставил их не там, где надо. В результате скобки никак не помогают. Корректный код:

while ((status = NtQuerySystemInformation(....)) == STATUS_INFO_LENGTH_MISMATCH)

Из-за этой ошибки, макрос UT_ASSERT никогда не сработает. Ведь в переменную status всегда заносится результат сравнения, то есть ложь (0) или истина (1). Значит условие ([0..1] >= 0) всегда истинно.

Потенциальная утечка памяти


static enum pocli_retpocli_args_obj_root(struct pocli_ctx *ctx, char *in, PMEMoid **oidp){  char *input = strdup(in);  if (!input)    return POCLI_ERR_MALLOC;  if (!oidp)    return POCLI_ERR_PARS;  ....}

Предупреждение PVS-Studio: V773 [CWE-401] The function was exited without releasing the 'input' pointer. A memory leak is possible. pmemobjcli.c 238

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

static enum pocli_retpocli_args_obj_root(struct pocli_ctx *ctx, char *in, PMEMoid **oidp){  if (!oidp)    return POCLI_ERR_PARS;  char *input = strdup(in);  if (!input)    return POCLI_ERR_MALLOC;  ....}

Или можно явно освобождать память:

static enum pocli_retpocli_args_obj_root(struct pocli_ctx *ctx, char *in, PMEMoid **oidp){  char *input = strdup(in);  if (!input)    return POCLI_ERR_MALLOC;  if (!oidp)  {    free(input);    return POCLI_ERR_PARS;  }  ....}

Потенциальное переполнение


typedef long long os_off_t;voiddo_memcpy(...., int dest_off, ....., size_t mapped_len, .....){  ....  LSEEK(fd, (os_off_t)(dest_off + (int)(mapped_len / 2)), SEEK_SET);  ....}

Предупреждение PVS-Studio: V1028 [CWE-190] Possible overflow. Consider casting operands, not the result. memcpy_common.c 62

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

Думаю, правильнее будет написать так:

LSEEK(fd, dest_off + (os_off_t)(mapped_len) / 2, SEEK_SET);

Здесь беззнаковое значение типа size_t превращается в знаковое (чтоб не было какого-нибудь предупреждения от компилятора). И заодно точно не возникнет переполнение при сложении.

Неправильная защита от переполнения


static DWORDget_rel_wait(const struct timespec *abstime){  struct __timeb64 t;  _ftime64_s(&t);  time_t now_ms = t.time * 1000 + t.millitm;  time_t ms = (time_t)(abstime->tv_sec * 1000 +    abstime->tv_nsec / 1000000);  DWORD rel_wait = (DWORD)(ms - now_ms);  return rel_wait < 0 ? 0 : rel_wait;}

Предупреждение PVS-Studio: V547 [CWE-570] Expression 'rel_wait < 0' is always false. Unsigned type value is never < 0. os_thread_windows.c 359

Мне не очень понятно, от какой именно ситуации должна защищать проверка, но она в любом случае не работает. Переменная rel_wait имеет беззнаковый тип DWORD. А значит, сравнение rel_wait < 0 не имеет смысла, так как результатом всегда является истина.

Отсутствие проверки, что память успешно выделена


Проверка того, что память выделена, осуществляется с помощью макросов assert, которые ничего не делают, если компилируется Release версия приложения. Так что можно сказать, что нет никакой обработки ситуации, когда функции malloc возвращают NULL. Пример:

static voidremove_extra_node(TOID(struct tree_map_node) *node){  ....  unsigned char *new_key = (unsigned char *)malloc(new_key_size);  assert(new_key != NULL);  memcpy(new_key, D_RO(tmp)->key, D_RO(tmp)->key_size);  ....}

Предупреждение PVS-Studio: V575 [CWE-628] The potential null pointer is passed into 'memcpy' function. Inspect the first argument. Check lines: 340, 338. rtree_map.c 340

В других местах нет даже assert:

static voidcalc_pi_mt(void){  ....  HANDLE *workers = (HANDLE *) malloc(sizeof(HANDLE) * pending);  for (i = 0; i < pending; ++i) {    workers[i] = CreateThread(NULL, 0, calc_pi,      &tasks[i], 0, NULL);    if (workers[i] == NULL)      break;  }  ....}

Предупреждение PVS-Studio: V522 [CWE-690] There might be dereferencing of a potential null pointer 'workers'. Check lines: 126, 124. pi.c 126

Таких фрагментов кода я насчитал минимум 37 штук. Так что я не вижу смысла перечислять все их в статье.

На первый взгляд, отсутствие проверок можно посчитать просто неаккуратностью и сказать, что это код с запахом. Я не согласен с такой позицией. Программисты недооценивают опасность отсутствия таких проверок. Нулевой указатель вовсе не обязательно сразу проявит себя падением программы при попытке его разыменования. Последствия могут быть более причудливыми и опасными, особенно в многопоточных программах. Чтобы подробнее понять, в чём дело и почему проверки нужны, настоятельно рекомендую всем познакомиться со статьёй "Почему важно проверять, что вернула функция malloc".

Код с запахом


Двойной вызов CloseHandle


static voidprepare_map(struct pmem2_map **map_ptr,  struct pmem2_config *cfg, struct pmem2_source *src){  ....  HANDLE mh = CreateFileMapping(....);  ....  UT_ASSERTne(CloseHandle(mh), 0);  ....}

Предупреждение PVS-Studio: V586 [CWE-675] The 'CloseHandle' function is called twice for deallocation of the same resource. pmem2_map.c 76

Смотря на этот код и предупреждение PVS-Studio понятно, что ничего непонятно. Где тут возможен повторный вызов CloseHandle? Чтобы найти ответ, давайте посмотрим на реализацию макроса UT_ASSERTne.

#define UT_ASSERTne(lhs, rhs)\  do {\    /* See comment in UT_ASSERT. */\    if (__builtin_constant_p(lhs) && __builtin_constant_p(rhs))\      UT_ASSERT_COMPILE_ERROR_ON((lhs) != (rhs));\    UT_ASSERTne_rt(lhs, rhs);\  } while (0)

Сильно понятнее не стало. Что такое UT_ASSERT_COMPILE_ERROR_ON? Что такое UT_ASSERTne_rt?

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

do {  if (0 && 0) (void)((CloseHandle(mh)) != (0));  ((void)(((CloseHandle(mh)) != (0)) ||    (ut_fatal(".....", 76, __FUNCTION__, "......: %s (0x%llx) != %s (0x%llx)",              "CloseHandle(mh)", (unsigned long long)(CloseHandle(mh)), "0",              (unsigned long long)(0)), 0))); } while (0);

Удалим всегда ложное условие (0 && 0) и, вообще, всё не относящееся к делу. Получается:

((void)(((CloseHandle(mh)) != (0)) ||  (ut_fatal(...., "assertion failure: %s (0x%llx) != %s (0x%llx)",            ....., (unsigned long long)(CloseHandle(mh)), .... ), 0)));

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

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

Несоответствие интерфейса реализации (снятие константности)


static intstatus_push(PMEMpoolcheck *ppc, struct check_status *st, uint32_t question){  ....  } else {    status_msg_info_and_question(st->msg);            // <=    st->question = question;    ppc->result = CHECK_RESULT_ASK_QUESTIONS;    st->answer = PMEMPOOL_CHECK_ANSWER_EMPTY;    PMDK_TAILQ_INSERT_TAIL(&ppc->data->questions, st, next);  }  ....}

Анализатор выдаёт сообщение: V530 [CWE-252] The return value of function 'status_msg_info_and_question' is required to be utilized. check_util.c 293

Причина в том, что функция status_msg_info_and_question, с точки зрения анализатора, не меняет состояние внешних по отношению к ней объектов, в том числе переданную константную строчку. Т.е. функция просто что-то считает и возвращает результат. А раз так, то странно не использовать результат, который возвращает эта функция. И, хотя анализатор в этот раз не прав, он указывает на код с запахом. Давайте посмотрим, как устроена вызываемая функция status_msg_info_and_question.

static inline intstatus_msg_info_and_question(const char *msg){  char *sep = strchr(msg, MSG_SEPARATOR);  if (sep) {    *sep = ' ';    return 0;  }  return -1;}

При вызове функции strchr происходит неявное снятие константности. Дело в том, что в C она объявлена так:

char * strchr ( const char *, int );

Не лучшее решение. Но язык C такой, какой есть :).

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

Тем не менее, хоть анализатор и запутался, он указывает на код с запахом. То, что сбивает с толку анализатор, может сбивать с толку и человека, который сопровождает код. Лучше было бы объявить функцию более честно, убрав const:

static inline intstatus_msg_info_and_question(char *msg){  char *sep = strchr(msg, MSG_SEPARATOR);  if (sep) {    *sep = ' ';    return 0;  }  return -1;}

Так и намерения сразу понятнее, и анализатор будет молчать.

Переусложненный код


static struct memory_blockheap_coalesce(struct palloc_heap *heap,  const struct memory_block *blocks[], int n){  struct memory_block ret = MEMORY_BLOCK_NONE;  const struct memory_block *b = NULL;  ret.size_idx = 0;  for (int i = 0; i < n; ++i) {    if (blocks[i] == NULL)      continue;    b = b ? b : blocks[i];    ret.size_idx += blocks[i] ? blocks[i]->size_idx : 0;  }  ....}

Предупреждение PVS-Studio: V547 [CWE-571] Expression 'blocks[i]' is always true. heap.c 1054

Если blocks[i] == NULL, то сработает оператор continue и цикл начнёт следующую итерацию. Поэтому повторная проверка элемента blocks[i] не имеет смысла и тернарный оператор является лишним. Код можно упростить:

....for (int i = 0; i < n; ++i) {  if (blocks[i] == NULL)    continue;  b = b ? b : blocks[i];  ret.size_idx += blocks[i]->size_idx;}....

Подозрительное использование нулевого указателя


void win_mmap_fini(void){  ....  if (mt->BaseAddress != NULL)    UnmapViewOfFile(mt->BaseAddress);  size_t release_size =    (char *)mt->EndAddress - (char *)mt->BaseAddress;  void *release_addr = (char *)mt->BaseAddress + mt->FileLen;  mmap_unreserve(release_addr, release_size - mt->FileLen);  ....}

Предупреждение PVS-Studio: V1004 [CWE-119] The '(char *) mt->BaseAddress' pointer was used unsafely after it was verified against nullptr. Check lines: 226, 235. win_mmap.c 235

Указатель mt->BaseAddress может быть нулевым, о чём свидетельствует проверка:

if (mt->BaseAddress != NULL)

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

size_t release_size =  (char *)mt->EndAddress - (char *)mt->BaseAddress;

Будет получено некое большое целочисленное значение, равное фактически значению указателя mt->EndAddress. Возможно, это и не ошибка, но выглядит всё это очень подозрительно, и мне кажется, код следует перепроверить. Запах заключается в том, что код непонятен и ему явно не хватает поясняющих комментариев.

Короткие имена глобальных переменных


Я считаю, что код пахнет, если содержит глобальные переменные с короткими именами. Легко опечататься и случайно использовать в какой-то функции не локальную, а глобальную переменную. Пример:

static struct critnib *c;

Предупреждения PVS-Studio на такие переменные:

  • V707 Giving short names to global variables is considered to be bad practice. It is suggested to rename 'ri' variable. map.c 131
  • V707 Giving short names to global variables is considered to be bad practice. It is suggested to rename 'c' variable. obj_critnib_mt.c 56
  • V707 Giving short names to global variables is considered to be bad practice. It is suggested to rename 'Id' variable. obj_list.h 68
  • V707 Giving short names to global variables is considered to be bad practice. It is suggested to rename 'Id' variable. obj_list.c 34

Странное


PVS-Studio: Странный код в функции

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

voiddo_memmove(char *dst, char *src, const char *file_name,    size_t dest_off, size_t src_off, size_t bytes,    memmove_fn fn, unsigned flags, persist_fn persist){  ....  /* do the same using regular memmove and verify that buffers match */  memmove(dstshadow + dest_off, dstshadow + dest_off, bytes / 2);  verify_contents(file_name, 0, dstshadow, dst, bytes);  verify_contents(file_name, 1, srcshadow, src, bytes);  ....}

Предупреждение PVS-Studio: V549 [CWE-688] The first argument of 'memmove' function is equal to the second argument. memmove_common.c 71

Обратите внимание, что первый и второй аргумент функции совпадают. Таким образом, функция по факту ничего не делает. Какие мне на ум приходят варианты:

  • Хотелось "потрогать" блок памяти. Но произойдёт ли это в реальности? Не удалит ли оптимизирующий компилятор код, который копирует блок памяти сам в себя?
  • Это какой-то юнит-тест на работу функции memmove.
  • Код содержит опечатку.

А вот не менее странный фрагмент в этой же функции:

voiddo_memmove(char *dst, char *src, const char *file_name,    size_t dest_off, size_t src_off, size_t bytes,    memmove_fn fn, unsigned flags, persist_fn persist){  ....  /* do the same using regular memmove and verify that buffers match */  memmove(dstshadow + dest_off, srcshadow + src_off, 0);  verify_contents(file_name, 2, dstshadow, dst, bytes);  verify_contents(file_name, 3, srcshadow, src, bytes);  ....}

Предупреждение PVS-Studio: V575 [CWE-628] The 'memmove' function processes '0' elements. Inspect the third argument. memmove_common.c 82

Функция перемещает 0 байт. Что это? Юнит-тест? Опечатка?

Для меня этот код непонятен и странен.

Зачем использовать анализаторы кода?


Может показаться, что раз найдено мало ошибок, то и внедрение анализатора в процесс разработки кода малообоснованно. Но смысл использования инструментов статического анализа не в разовых проверках, а в регулярном выявлении ошибок ещё на этапе написания кода. В противном случае, эти ошибки выявляются более дорогими и медленными способами (отладка, тестирование, отзывы пользователей и так далее). Более подробно эта мысль изложена в статье "Ошибки, которые не находит статический анализ кода, потому что он не используется", с которой я рекомендую познакомиться. А затем приходите к нам на сайт скачать и попробовать PVS-Studio для проверки своих проектов.

Спасибо за внимание!


Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov. Static code analysis of the PMDK library collection by Intel and errors that are not actual errors.
Подробнее..

Из песочницы DevSecOps принципы работы и сравнение SCA. Часть первая

26.08.2020 14:06:49 | Автор: admin
Значимость анализа сторонних компонентов ПО (англ. Software Composition Analysis SCA) в процессе разработки растет по мере выхода ежегодных отчетов об уязвимостях open source библиотек, которые публикуются компаниями Synopsys, Sonatype, Snyk, White Source. Согласно отчету The State of Open Source Security Vulnerabilities 2020 число выявленных уязвимостей в open source в 2019 выросло почти в 1.5 раза в сравнении с предыдущим годом, в то время как компоненты с открытым кодом используются от 60% до 80% проектов. Если обратиться к независимому мнению, то процессы SCA являются отдельной практикой OWASP SAMM и BSIMM в качестве показателя зрелости, а в первой половине 2020 года OWASP выпустила новый стандарт OWASP Software Component Verification Standard (SCVS), предоставляющий лучшие практики по проверке сторонних компонент в цепочке поставок ПО.



Один из самых показательных кейсов произошел с компанией Equifax в мае 2017 года. Неизвестные злоумышленники завладели информацией о 143 млн. американцев, включая полные имена, адреса, номера социального страхования и водительских удостоверений. В 209 000 случаях в документах также фигурировала информация о банковских картах пострадавших. Данная утечка произошла в следствие эксплуатации критической уязвимости в Apache Struts 2 (CVE-2017-5638), в то время как исправление было выпущено еще в марте 2017 года. У компании было два месяца на установку обновления, однако этим никто не озаботился.

В данной статье будет обсуждаться вопрос выбора инструмента для проведения SCA с точки зрения качества результатов анализа. Также будет приведено функциональное сравнение инструментов. Процесс встраивания в CI/CD и возможности по интеграции оставим на последующие публикации. Широкий список инструментов был представлен OWASP на своем сайте, но в рамках текущего обзора мы коснемся только самого популярного open source инструмента Dependency Check, чуть менее известной open source платформы Dependency Track и Enterprise-решения Sonatype Nexus IQ. Также разберемся, как работают эти решения и сравним полученные результаты на предмет ложных срабатываний.



Принцип работы


Dependency Check это утилита (CLI, maven, jenkins модуль, ant), которая анализирует файлы проекта, собирает фрагменты информации о зависимостях (package name, groupid, specification title, version), строит строку CPE (Common Platform Enumeration), Package URL (PURL) и выявляет для CPE/PURL уязвимости из баз данных (NVD, Sonatype OSS Index, NPM Audit API), после чего строит единоразовый отчет в формате HTML, JSON, XML

Рассмотрим, как выглядит CPE:

cpe:2.3:part:vendor:product:version:update:edition:language:sw_edition:target_sw:target_hw:other

  • Part: Указание о том, что компонент относится к приложению (a), операционной системе (o), железу (h) (Обязательный пункт)
  • Vendor: Название производителя продукта (Обязательный пункт)
  • Product: Название продукта (Обязательный пункт)
  • Version: Версия компоненты (Устаревший пункт)
  • Update: Обновление пакета
  • Edition: Наследуемая версия (Устаревший пункт)
  • Language: Язык, определяемый в RFC-5646
  • SW Edition: Версия ПО
  • Target SW: Программная среда, в которой работает продукт
  • Target HW: Аппаратная среда, в которой работает продукт
  • Other: Информация о поставщике или продукте

Пример CPE выглядит следующим образом:

cpe:2.3:a:pivotal_software:spring_framework:3.0.0:*:*:*:*:*:*:*

Строка означает, что CPE версии 2.3 описывает компонент приложения от производителя pivotal_software с названием spring_framework версии 3.0.0. Если мы откроем уязвимость CVE-2014-0225 в NVD, то можем увидеть упоминание этой CPE. Первая проблема, на которую сразу стоит обратить внимание CVE в NVD, согласно CPE, сообщает о наличии проблемы во фреймворке, а не в конкретной компоненте. То есть, если разработчики плотно завязаны на фреймворк, а выявленная уязвимость не касается тех модулей, которые используют разработчики, специалисту по безопасности так или иначе придется разбирать данную CVE и задумываться об обновлении.

URL-адрес также используется инструментами SCA. Формат URL-адреса пакета следующий:

scheme:type/namespace/name@version?qualifiers#subpath

  • Sсheme: Всегда будет 'pkg', указывающий, что это URL-адрес пакета (Обязательный пункт)
  • Type: Тип пакета или протокол пакета, например maven, npm, nuget, gem, pypi и т.д. (Обязательный пункт)
  • Namespace: Некоторый префикс имени, такой как идентификатор группы Maven, владелец образа Docker, пользователь или организация GitHub.Необязательный и зависит от типа.
  • Name: Имя пакета (Обязательный пункт)
  • Version: Версия пакета
  • Qualifiers: Дополнительные квалификационные данные для пакета, такие как ОС, архитектура, дистрибутив и т. Д. Необязательный и зависящий от типа пункт.
  • Subpath: Дополнительный путь в пакете относительно корня пакета

Например:

pkg:golang/google.golang.org/genproto#googleapis/api/annotationspkg:maven/org.apache.commons/io@1.3.4pkg:pypi/django-package@1.11.1.dev1

Dependency Track on-premise веб-платформа, которая принимает готовые Bill of Materials (BOM) сформированные CycloneDX и SPDX, то есть готовые спецификации об имеющихся зависимостях. Это XML-файл с описанием зависимостей name, hashes, package url, publisher, license. Далее Dependency Track разбирает BOM, смотрит имеющиеся к выявленным зависимостям CVE из базы данных уязвимостей (NVD, Sonatype OSS Index ), после чего строит графики, вычисляет метрики, регулярно обновляя данные о статусе уязвимости компонент.

Пример того, как может выглядеть BOM в формате XML:

<?xml version="1.0" encoding="UTF-8"?><bom xmlns="http://personeltest.ru/away/cyclonedx.org/schema/bom/1.2" serialNumber="urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79" version="1">  <components>    <component type="library">      <publisher>Apache</publisher>      <group>org.apache.tomcat</group>      <name>tomcat-catalina</name>      <version>9.0.14</version>      <hashes>        <hash alg="MD5">3942447fac867ae5cdb3229b658f4d48</hash>        <hash alg="SHA-1">e6b1000b94e835ffd37f4c6dcbdad43f4b48a02a</hash>        <hash alg="SHA-256">f498a8ff2dd007e29c2074f5e4b01a9a01775c3ff3aeaf6906ea503bc5791b7b</hash>        <hash alg="SHA-512">e8f33e424f3f4ed6db76a482fde1a5298970e442c531729119e37991884bdffab4f9426b7ee11fccd074eeda0634d71697d6f88a460dce0ac8d627a29f7d1282</hash>      </hashes>      <licenses>        <license>          <id>Apache-2.0</id>        </license>      </licenses>      <purl>pkg:maven/org.apache.tomcat/tomcat-catalina@9.0.14</purl>    </component>      <!-- More components here -->  </components></bom>

BOM может использоваться не только в качестве входных параметров для Dependency Track, но и для инвентаризации компонентов ПО в цепочке поставок, например, для предоставления заказчику ПО. В 2014 году на рассмотрение в США был даже предложен закон Cyber Supply Chain Management and Transparency Act of 2014, который гласил, что при закупке ПО любое гос. учреждение должно запрашивать BOM для предотвращения использования уязвимых компонент, однако в силу акт так и не вступил.

Возвращаясь к SCA, у Dependency Track есть готовые интеграции с Notification Platforms вроде Slack, системами управления уязвимостями вроде Kenna Security. Стоит также сказать, что Dependency Track ко всему прочему выявляет устаревшие версии пакетов и предоставляет информацию о лицензиях (за счет поддержки SPDX).

Если говорить именно о качестве SCA, то здесь есть принципиальная разница.

Dependency Track не принимает проект в качестве входных данных, а принимает именно BOM. Это означает, что если мы захотим проверить проект, то сначала нам нужно сгенерировать bom.xml, например, с помощью CycloneDX. Таким образом, Dependency Track напрямую зависит от CycloneDX. В то же время, это дает возможность кастомизации. Так команда OZON написала модуль CycloneDX для сборки BOM-файлов для проектов на Golang с целью дальнейшего сканирования через Dependency Track.

Nexus IQ коммерческое решение SCA от компании Sonatype, которое является частью экосистемы Sonatype, куда также входит Nexus Repository Manager. Nexus IQ может принимать в качестве входных данных как war архивы (для java проектов) через веб-интерфейс или API, так и BOM, если ваша организация не успела перестроиться с CycloneDX на новое решение. В отличие от open source решений, IQ обращается не только к CP/PURL к выявленной компоненте и соответствующей уязвимости в базе данных, но и учитывает собственные исследования, например, название уязвимой функции или класса. Механизмы IQ будут рассмотрены позднее при разборе результатов.

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

Язык Nexus IQ Dependency Check Dependency Track
Java + + +
C/C++ + + -
C# + + -
.Net + + +
Erlang - - +
JavaScript (NodeJS) + + +
PHP + + +
Python + + +
Ruby + + +
Perl - - -
Scala + + +
Objective C + + -
Swift + + -
R + - -
Go + + +

Функциональные возможности

Функциональные возможности Nexus IQ Dependency Check Dependency Track
Возможность обеспечивать проверку компонент, используемых в исходном коде на лицензионную чистоту + - +
Возможность сканирования и анализа на наличие уязвимостей и лицензионной чистоты для образов Docker + Интеграция с Clair - -
Возможность настройки политики безопасности для использования библиотек с открытым исходным кодом + - -
Возможность сканирования репозиториев с открытым исходным кодом на наличие уязвимых компонентов + RubyGems, Maven, NPM, Nuget, Pypi, Conan, Bower, Conda, Go, p2, R, Yum, Helm, Docker, CocoaPods, Git LFS - + Hex, RubyGems, Maven, NPM, Nuget, Pypi
Наличие специализированной исследовательской группы + - -
Работа в закрытом контуре + + +
Использование сторонних баз данных + Закрытая БД Sonatype + Sonatype OSS, NPM Public Advisors + Sonatype OSS, NPM Public Advisors, RetireJS, VulnDB, поддержка собственной БД уязвимостей
Возможность фильтровать компоненты с открытым исходным кодом при попытке загрузки в контур разработки согласно сконфигурированным политикам + - -
Рекомендации по исправлению уязвимостей, наличие ссылок на исправление + +- (зависит от описания в публичных базах) +- (зависит от описания в публичных базах)
Ранжирование обнаруженных уязвимостей по степени критичности + + +
Ролевая модель доступа + - +
Поддержка интерфейса командной строки CLI + + +- (только для CycloneDX)
Выборка / сортировка уязвимостей по определяемым критериям + - +
Dashboard по состоянию приложений + - +
Генерация отчётов в PDF-формате + - -
Генерация отчётов в формате JSON\CSV + + -
Поддержка русского языка - - -

Интеграционные возможности
Интеграция Nexus IQ Dependency Check Dependency Track
Интеграция с LDAP/Active Directory + - +
Интеграция с системой непрерывной интеграции (continous integration) Bamboo + - -
Интеграция с системой непрерывной интеграции (continous integration) TeamCity + - -
Интеграция с системой непрерывной интеграции (continous integration) GitLab + +- (в виде плагина для GitLab) +
Интеграция с системой непрерывной интеграции (continous integration) Jenkins + + +
Наличие плагинов к IDE + IntelliJ, Eclipse, Visual Studio - -
Поддержка кастомизированной интеграции через web-services (API) инструмента + - +


Dependency Check


Первый запуск


Запустим Dependency Check к умышленно уязвимому приложению DVJA.

Для этого воспользуемся Dependency Check Maven Plugin:

mvn org.owasp:dependency-check-maven:check

В результате в директории target появится dependency-check-report.html.



Откроем файл. После сводной информации об общем количестве уязвимостей можем увидеть информацию об уязвимостях с высоким уровнем Severity и Confidence с указанием на пакет, CPE, число CVE.

Следом идет более подробная информация, в частности то, на основе чего было принято решение (evidence), то есть некий BOM.



Далее идет CPE, PURL и описание CVE. Рекомендации об исправлении кстати не прилагаются в силу отсутствия их в базе NVD.



Для систематичного просмотра результатов сканирования можно настроить Nginx с минимальными настройками, либо отправлять полученные дефекты в систему управления дефектами, которые поддерживают конекторы к Dependency Check. Например, Defect Dojo.

Dependency Track


Установка


Dependency Track, в свою очередь, является веб-платформой с отображающими графиками, поэтому острый вопрос о хранении дефектов в стороннем решении здесь не стоит.
Для установки есть следующие поддерживаемые сценарии: Docker, WAR, Executable WAR.

Первый запуск


Переходим по URL запущенного сервиса. Входим через admin/admin, меняем логин и пароль, после чего попадаем на Dashboard. Следующее, что мы сделаем создадим проект для тестового приложения на Java в Home/Projects Create Project . В качестве примера возьмем DVJA.



Так как Dependency Track может принимать в качестве входных данных только BOM, этот BOM необходимо получить. Воспользуемся CycloneDX Maven Plugin:

mvn org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBom

Получаем bom.xml и загружаем файл в созданном проекте DVJA Dependeencies Upload BOM.

Зайдем в Administration Analyzers. Понимаем, что у нас включен только Internal Analyzer, включающий NVD. Подключим также Sonatype OSS Index.



Таким образом, получим следующую картину для нашего проекта:



Также в списке можно найти одну уязвимость, применимую к Sonatype OSS:



Основное разочарование было в том, что Dependency Track больше не принимает xml-отчеты Dependency Check. Последние поддерживаемые версии интеграции с Dependency Check были 1.0.0 4.0.2, в то время как я тестировал 5.3.2.

Вот видеовот), когда это было еще возможно.

Nexus IQ


Первый запуск


Установка Nexus IQ происходит из архивов по документации, но мы для этих целей собрали себе образ Docker.

После входа в консоль необходимо создать Организацию и Приложение.







Как можно видеть, настройка в случае с IQ происходит несколько сложнее, ибо нам также необходимо создать политики, применимые для разных стейджей (dev, build, stage, release). Это необходимо, чтобы блокировать уязвимые компоненты по мере продвижения по пайплайну ближе к проду, либо блокировать как только они попадают в Nexus Repo при скачивании разработчиками.

Чтобы ощутить разницу open source и enterprise, выполним такое же сканирование через Nexus IQ аналогично через Maven plugin, предварительно создав тестовое приложение в интерфейсе NexusIQ dvja-test-and-compare:

mvn com.sonatype.clm:clm-maven-plugin:evaluate -Dclm.applicationId=dvja-test-and-compare -Dclm.serverUrl=<NEXUSIQIP> -Dclm.username=<USERNAME> -Dclm.password=<PASSWORD>

Переходим по URL к сгенерированному отчету в веб-интерфейсе IQ:



Здесь можно увидеть все нарушения политики с указанием разного уровня значимости (от Info до Security Critical). Буква D рядом с компонентой означает, что компонента DirectDependency, а буква T рядом с компонентом означает, что компонента TransitiveDependency, то есть является транзитивной.

Кстати, отчет State of Open Source Security Report 2020 от Snyk сообщает, что более 70% уязвимостей open source обнаруженных в Node.js, Java и Ruby находятся в транзитивных зависимостях.

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



Если перейти в раздел уязвимостей и раскрыть CVE, то можно прочитать описание к этой уязвимости, рекомендации по устранению, а также причину, по которой данная компонента попала под нарушение, то есть наличие класса DiskFileitem.class.





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

Итого Nexus IQ:

  • Dependencies Scanned:62
  • Vulnerable Dependencies:16
  • Vulnerabilities Found:42 (8 sonatype db)

Итого Dependency Check:

  • Dependencies Scanned:47
  • Vulnerable Dependencies:13
  • Vulnerabilities Found:91 (14 sonatype oss)

Итого Dependency Track:

  • Dependencies Scanned: 59
  • Vulnerable Dependencies:10
  • Vulnerabilities Found:51 (1 sonatype oss)

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

Дисклеймер


Данное ревью не является неоспоримой истиной. Перед автором не стояло цели выделить отдельный инструмент на фоне других. Смысл ревью был показать механизмы работы инструментов SCA и способы проверки их результатов.

Сравнение результатов


Условия:

Ложным срабатыванием по отношению к уязвимостям сторонних компонентов является:

  • Несоответствие CVE к выявленной компоненте
  • Например, если уязвимость выявлена во фреймворке struts2, а инструмент указывает на компоненту фреймворка struts-tiles, к которой эта уязвимость не относится, то это false positive
  • Несоответствие CVE к выявленной версии компоненты
  • Например, уязвимость привязана к версии python > 3.5 и инструмент отмечает уязвимой версию 2.7 это false positive, так как на самом деле уязвимость относится только к ветке продукта 3.x
  • Дублирование CVE
  • Например, если SCA указал на CVE, позволяющую реализовать RCE, после чего SCA указывает для этой же компоненты CVE, применимую к продуктам Cisco, подверженным этой RCE. В таком случае будет false positive.
  • Например, CVE была найдена в компоненте spring-web, после чего SCA указывает на эту же CVE в других компонентах фреймворка Spring Framework, в то время как CVE к другим компонентам отношения не имеет. В таком случае будет false positive.

Объектом исследования выбран Open Source проект DVJA. В исследовании участвовали только java компоненты (без js).

Сводные результаты


Перейдем сразу в результатом ручного ревью выявленных уязвимостей. С полным отчетом для каждой CVE можно познакомиться в Приложении.

Сводные результаты по всем уязвимостям:
Параметр Nexus IQ Dependency Check Dependency Track
Всего выявлено уязвимостей 42 91 51
Неверно выявлено уязвимостей (false positive) 2(4.76%) 62(68,13%) 29(56.86%)
Не обнаружено релевантных уязвимостей (false negative) 10 20 27

Сводные результаты по компонентам:
Параметр Nexus IQ Dependency Check Dependency Track
Всего выявлено компонентов 62 47 59
Всего уязвимых компонентов 16 13 10
Неверно выявлены уязвимые компоненты (false positive) 1 5 0
Неверно выявлены уязвимые компоненты (false positive) 0 6 6

Построим визуальные графики, чтобы оценить соотношение false positive и false negative к общему числу уязвимостей. По горизонтали отмечены компоненты, а по вертикали выявленные в них уязвимости.







Для сравнения, аналогичное исследование было проведено командой Sonatype по тестированию проекта из 1531 компоненты с помощью OWASP Dependency Check. Как мы можем увидеть, соотношение шума к корректным срабатываниям соезмиримо с нашими результатами.


Источник: www.sonatype.com/why-precision-matters-ebook

Рассмотрим некоторые CVE из результатов нашего сканирования, чтобы понять причину таких результатов.

Подробнее


1


Разберем сначала некоторые интересные моменты Sonatype Nexus IQ.

Nexus IQ указывает на проблему с десереализацией с возможностью выполнить RCE в Spring Framework несколько раз. CVE-2016-1000027 в spring-web:3.0.5 в первый раз, и CVE-2011-2894 в spring-context:3.0.5 и spring-core:3.0.5. Поначалу кажется, что происходит дублирование уязвимости по нескольким CVE. Ибо, если посмотреть CVE-2016-1000027 и CVE-2011-2894 в базе NVD, то кажется, что все очевидно

Компонент Уязвимость
spring-web:3.0.5 CVE-2016-1000027
spring-context:3.0.5 CVE-2011-2894
spring-core:3.0.5 CVE-2011-2894

Описание CVE-2011-2894 из NVD:


Описание CVE-2016-1000027 из NVD:


CVE-2011-2894 сама по себе довольно известная. В отчете White Source за 2011 год эта CVE была признана одной из самых часто встречающихся. Описания для CVE-2016-100027 в принципе немного в NVD, да и применима она, вроде бы, только для Spring Framework 4.1.4. Взглянем на reference и тут становится все более или менее понятно. Из статьи Tenable мы понимаем, что помимо уязвимости в RemoteInvocationSerializingExporter в CVE-2011-2894, уязвимость наблюдается в HttpInvokerServiceExporter. Об этом нам и говорит Nexus IQ:



Тем не менее ничего подобного нет в NVD, из-за чего Dependency Check и Dependency Track получают по false negative.

Также из описания CVE-2011-2894 можно понять, что уязвимость действительно присутствует и в spring-context:3.0.5 и в spring-core:3.0.5. Подтверждение этому можно найти в статье от того, кто эту уязвимость нашел.

2


Компонент Уязвимость Результат
struts2-core:2.3.30 CVE-2016-4003 FALSE

Если мы изучим уязвимость CVE-2016-4003, то поймем, что ее исправили еще в версии 2.3.28, тем не менее Nexus IQ нам о ней сообщает. В описании к уязвимости есть примечание:



То есть уязвимость существует только в связке с устаревшей версией JRE, о чем нас решили предупредить. Тем не менее, считаем это False Positive, хоть и не самым страшным.

3


Компонент Уязвимость Результат
xwork-core:2.3.30 CVE-2017-9804 TRUE
xwork-core:2.3.30 CVE-2017-7672 FALSE

Если мы посмотрим описание к CVE-2017-9804 и CVE-2017-7672, то поймем, что проблема в URLValidator class, причем CVE-2017-9804 вытекает из CVE-2017-7672. Наличие второй уязвимости не несет никакой полезной нагрузки кроме того, что ее severity вырос до High, поэтому можно считать это лишним шумом.

В целом других false positive для Nexus IQ найдено не было.

4


Есть несколько моментов, которые выделяют IQ на фоне других решений.
Компонент Уязвимость Результат
spring-web:3.0.5 CVE-2020-5398 TRUE


CVE в NVD сообщает, что она применима только для версий 5.2.x до 5.2.3, 5.1.x до 5.1.13, и версий 5.0.x до 5.0.16, тем не менее, если мы посмотрим описание CVE в Nexus IQ, то увидим следующее:
Advisory Deviation Notice: The Sonatype security research team discovered that this vulnerability was introduced in version 3.0.2.RELEASE and not 5.0.x as stated in the advisory.

После этого следует PoC к этой уязвимости, который сообщает, что она присутствует в версии 3.0.5.

False negative отправляется к Dependency Check и Dependency Track.

5


Посмотрим на false positive для Dependency Check и Dependency Track.

Dependency Check отдельно выделяется тем, что отражает те CVE, которые относятся ко всему фреймворку в NVD, в те компоненты, к которым эти CVE не применимы. Это касается CVE-2012-0394, CVE-2013-2115, CVE-2014-0114, CVE-2015-0899, CVE-2015-2992, CVE-2016-1181, CVE-2016-1182, которые Dependency Check прикрутил к struts-taglib:1.3.8 и struts-tiles-1.3.8. Эти компоненты не имеют ничего общего с тем, что описано в CVE обработка запросов, валидация страниц и так далее. Это обусловлено тем, что общее между этими CVE и компонентами только фреймворк, из-за чего Dependency Check и посчитал это уязвимостью.

Такая же ситуация с spring-tx:3.0.5, и похожая ситуация с struts-core:1.3.8. Для struts-core Dependency Check и Dependency Track нашли очень много уязвимостей, которые на самом деле применимы к struts2-core, который по сути является отдельным фреймворком. В данном случае Nexus IQ правильно понял картину и в тех CVE, которые выдал, указал, что struts-core пришел end of life и необходимо перейти к struts2-core.

6


В некоторых ситуациях трактовать явную ошибку Dependency Check и Dependency Track несправедливо. В частности CVE-2013-4152, CVE-2013-6429, CVE-2013-6430, CVE-2013-7315, CVE-2014-0054, CVE-2014-0225, CVE-2014-0225, которые Dependency Check и Dependency Track отнесли к spring-core:3.0.5 на самом деле относятся к spring-web:3.0.5. При этом часть из этих CVE были найдены и Nexus IQ, тем не менее IQ их корректно определил к другой компоненте. От того, что эти уязвимости не были найдены в spring-core, нельзя утверждать, что их нет во фреймворке в принципе и open source инструменты справедливо указали на эти уязвимости (просто немного промахнулись).

Выводы


Как мы можем видеть, определение достоверности выявленных уязвимостей ручным ревью не дает однозначных результатов, из-за чего возникают спорные моменты. Результаты таковы, что решение Nexus IQ обладает наименьшим показателем ложных срабатываний и наибольшей точностью.

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

Немаловажное влияние на результаты играют и те уязвимости, которые не попали в NVD, но тем не менее присутствуют в базе Sonatype с пометкой SONATYPE. Согласно отчету The State of Open Source Security Vulnerabilities 2020 о 45% обнаруженных уязвимостей с открытым исходным кодом не сообщается в NVD. Согласно базе данных WhiteSource, только 29% всех уязвимостей с открытым исходным кодом, зарегистрированных за пределами NVD, в конечном итоге публикуются в ней, поэтому так важно искать уязвимости также в других источниках.

Как итог Dependency Check выдает большое количество шума, упуская часть уязвимых компонент. Dependency Track выдает меньший шум и выявляет большое число компонент, что визуально не режет глаза в веб-интерфейсе.

Тем не менее, практика показывает, что именно open source должен становится первым шагов на пути к зрелому DevSecOps. Первое, над чем стоит задуматься для встраивания SCA в разработку, это процессы, а именно размышления совместно с руководством и смежными департаментами над тем, как должны выглядеть идеальные процессы у себя в организации. Возможно, окажется так, что для вашей организации на первых порах Dependency Check или Dependency Track закроют все стоящие перед бизнесом потребности, а Enterprise-решения будут являться логическим продолжением в силу роста сложности разрабатываемых приложений.

Приложение А. Результаты применительно к компонентам
Условные обозначения:

  • High уязвимости высокого и критичного уровня в компоненте
  • Medium Уязвимости среднего уровня критичности в компоненте
  • TRUE Верно определенная уязвимость (True positive issue)
  • FALSE Ложное срабатывание (False positive issue)

Компонент Nexus IQ Dependency Check Dependency Track Результат
dom4j: 1.6.1 High High High TRUE
log4j-core: 2.3 High High High TRUE
log4j: 1.2.14 High High - TRUE
commons-collections:3.1 High High High TRUE
commons-fileupload:1.3.2 High High High TRUE
commons-beanutils:1.7.0 High High High TRUE
commons-codec:1:10 Medium - - TRUE
mysql-connector-java:5.1.42 High High High TRUE
spring-expression:3.0.5 High компонент не найден
TRUE
spring-web:3.0.5 High компонент не найден High TRUE
spring-context:3.0.5 Medium компонент не найден - TRUE
spring-core:3.0.5 Medium High High TRUE
struts2-config-browser-plugin:2.3.30 Medium - - TRUE
spring-tx:3.0.5 - High - FALSE
struts-core:1.3.8 High High High TRUE
xwork-core: 2.3.30 High - - TRUE
struts2-core: 2.3.30 High High High TRUE
struts-taglib:1.3.8 - High - FALSE
struts-tiles-1.3.8 - High - FALSE



Приложение Б. Результаты применительно к уязвимостям
Условные обозначения:

  • High уязвимости высокого и критичного уровня в компоненте
  • Medium Уязвимости среднего уровня критичности в компоненте
  • TRUE Верно определенная уязвимость (True positive issue)
  • FALSE Ложное срабатывание (False positive issue)

Компонент Nexus IQ Dependency Check Dependency Track Severity Результат Комментарий
dom4j: 1.6.1 CVE-2018-1000632 CVE-2018-1000632 CVE-2018-1000632 High TRUE
CVE-2020-10683 CVE-2020-10683 CVE-2020-10683 High TRUE
log4j-core: 2.3 CVE-2017-5645 CVE-2017-5645 CVE-2017-5645 High TRUE
CVE-2020-9488 CVE-2020-9488 CVE-2020-9488 Low TRUE
log4j: 1.2.14 CVE-2019-17571 CVE-2019-17571 - High TRUE
- CVE-2020-9488 - Low TRUE
SONATYPE-2010-0053 - - High TRUE
commons-collections:3.1 - CVE-2015-6420 CVE-2015-6420 High FALSE Дублирует RCE(OSSINDEX)
- CVE-2017-15708 CVE-2017-15708 High FALSE Дублирует RCE(OSSINDEX)
SONATYPE-2015-0002 RCE (OSSINDEX) RCE(OSSINDEX) High TRUE
commons-fileupload:1.3.2 CVE-2016-1000031 CVE-2016-1000031 CVE-2016-1000031 High TRUE
SONATYPE-2014-0173 - - Medium TRUE
commons-beanutils:1.7.0 CVE-2014-0114 CVE-2014-0114 CVE-2014-0114 High TRUE
- CVE-2019-10086 CVE-2019-10086 High FALSE Уязвимость применима только для версий 1.9.2+
commons-codec:1:10 SONATYPE-2012-0050 - - Medium TRUE
mysql-connector-java:5.1.42 CVE-2018-3258 CVE-2018-3258 CVE-2018-3258 High TRUE
CVE-2019-2692 CVE-2019-2692 - Medium TRUE
- CVE-2020-2875 - Medium FALSE Та же самая уязвимость как и CVE-2019-2692, но c припиской attacks may significantly impact additional products
- CVE-2017-15945 - High FALSE Не относится к mysql-connector-java
- CVE-2020-2933 - Low FALSE Дубликат к CVE-2020-2934
CVE-2020-2934 CVE-2020-2934 - Medium TRUE
spring-expression:3.0.5 CVE-2018-1270 компонент не найден - High TRUE
CVE-2018-1257 - - Medium TRUE
spring-web:3.0.5 CVE-2016-1000027 компонент не найден - High TRUE
CVE-2014-0225 - CVE-2014-0225 High TRUE
CVE-2011-2730 - - High TRUE
- - CVE-2013-4152 Medium TRUE
CVE-2018-1272 - - High TRUE
CVE-2020-5398 - - High TRUE Показательный пример в пользу IQ: The Sonatype security research team discovered that this vulnerability was introduced in version 3.0.2.RELEASE and not 5.0.x as stated in the advisory.
CVE-2013-6429 - - Medium TRUE
CVE-2014-0054 - CVE-2014-0054 Medium TRUE
CVE-2013-6430 - - Medium TRUE
spring-context:3.0.5 CVE-2011-2894 компонент не найден - Medium TRUE
spring-core:3.0.5 - CVE-2011-2730 CVE-2011-2730 High TRUE
CVE-2011-2894 CVE-2011-2894 CVE-2011-2894 Medium TRUE
- - CVE-2013-4152 Medium FALSE Дубликат этой же уязвимости в spring-web
- CVE-2013-4152 - Medium FALSE Уязвимость относится к компоненте spring-web
- CVE-2013-6429 CVE-2013-6429 Medium FALSE Уязвимость относится к компоненте spring-web
- CVE-2013-6430 - Medium FALSE Уязвимость относится к компоненте spring-web
- CVE-2013-7315 CVE-2013-7315 Medium FALSE SPLIT из CVE-2013-4152. + Уязвимость относится к компоненте spring-web
- CVE-2014-0054 CVE-2014-0054 Medium FALSE Уязвимость относится к компоненте spring-web
- CVE-2014-0225 - High FALSE Уязвимость относится к компоненте spring-web
- - CVE-2014-0225 High FALSE Дубликат этой же уязвимости в spring-web
- CVE-2014-1904 CVE-2014-1904 Medium FALSE Уязвимость относится к компоненте spring-web-mvc
- CVE-2014-3625 CVE-2014-3625 Medium FALSE Уязвимость относится к компоненте spring-web-mvc
- CVE-2016-9878 CVE-2016-9878 High FALSE Уязвимость относится к компоненте spring-web-mvc
- CVE-2018-1270 CVE-2018-1270 High FALSE Для spring-expression / spring-messages
- CVE-2018-1271 CVE-2018-1271 Medium FALSE Уязвимость относится к компоненте spring-web-mvc
- CVE-2018-1272 CVE-2018-1272 High TRUE
CVE-2014-3578 CVE-2014-3578 (OSSINDEX) CVE-2014-3578 Medium TRUE
SONATYPE-2015-0327 - - Low TRUE
struts2-config-browser-plugin:2.3.30 SONATYPE-2016-0104 - - Medium TRUE
spring-tx:3.0.5 - CVE-2011-2730 - High FALSE Уязвимость не относится к spring-tx
- CVE-2011-2894 - High FALSE Уязвимость не относится к spring-tx
- CVE-2013-4152 - Medium FALSE Уязвимость не относится к spring-tx
- CVE-2013-6429 - Medium FALSE Уязвимость не относится к spring-tx
- CVE-2013-6430 - Medium FALSE Уязвимость не относится к spring-tx
- CVE-2013-7315 - Medium FALSE Уязвимость не относится к spring-tx
- CVE-2014-0054 - Medium FALSE Уязвимость не относится к spring-tx
- CVE-2014-0225 - High FALSE Уязвимость не относится к spring-tx
- CVE-2014-1904 - Medium FALSE Уязвимость не относится к spring-tx
- CVE-2014-3625 - Medium FALSE Уязвимость не относится к spring-tx
- CVE-2016-9878 - High FALSE Уязвимость не относится к spring-tx
- CVE-2018-1270 - High FALSE Уязвимость не относится к spring-tx
- CVE-2018-1271 - Medium FALSE Уязвимость не относится к spring-tx
- CVE-2018-1272 - Medium FALSE Уязвимость не относится к spring-tx
struts-core:1.3.8 - CVE-2011-5057 (OSSINDEX)
Medium FASLE Уязвимость к Struts 2
- CVE-2012-0391 (OSSINDEX) CVE-2012-0391 High FALSE Уязвимость к Struts 2
- CVE-2014-0094 (OSSINDEX) CVE-2014-0094 Medium FALSE Уязвимость к Struts 2
- CVE-2014-0113 (OSSINDEX) CVE-2014-0113 High FALSE Уязвимость к Struts 2
CVE-2016-1182 3VE-2016-1182 - High TRUE
- - CVE-2011-5057 Medium FALSE Уязвимость к Struts 2
- CVE-2012-0392 (OSSINDEX) CVE-2012-0392 High FALSE Уязвимость к Struts 2
- CVE-2012-0393 (OSSINDEX) CVE-2012-0393 Medium FALSE Уязвимость к Struts 2
CVE-2015-0899 CVE-2015-0899 - High TRUE
- CVE-2012-0394 CVE-2012-0394 Medium FALSE Уязвимость к Struts 2
- CVE-2012-0838 (OSSINDEX) CVE-2012-0838 High FALSE Уязвимость к Struts 2
- CVE-2013-1965 (OSSINDEX) CVE-2013-1965 High FALSE Уязвимость к Struts 2
- CVE-2013-1966 (OSSINDEX) CVE-2013-1966 High FASLE Уязвимость к Struts 2
- CVE-2013-2115 CVE-2013-2115 High FASLE Уязвимость к Struts 2
- CVE-2013-2134 (OSSINDEX) CVE-2013-2134 High FASLE Уязвимость к Struts 2
- CVE-2013-2135 (OSSINDEX) CVE-2013-2135 High FASLE Уязвимость к Struts 2
CVE-2014-0114 CVE-2014-0114 - High TRUE
- CVE-2015-2992 CVE-2015-2992 Medium FALSE Уязвимость к Struts 2
- CVE-2016-0785 (OSSINDEX) CVE-2016-0785 High FALSE Уязвимость к Struts 2
CVE-2016-1181 CVE-2016-1181 - High TRUE
- CVE-2016-4003 (OSSINDEX) CVE-2016-4003 High FALSE Уязвимость к Struts 2
xwork-core:2.3.30 CVE-2017-9804 - - High TRUE
SONATYPE-2017-0173 - - High TRUE
CVE-2017-7672 - - High FALSE Дубль к CVE-2017-9804
SONATYPE-2016-0127 - - High TRUE
struts2-core:2.3.30 - CVE-2016-6795 CVE-2016-6795 High TRUE
- CVE-2017-9787 CVE-2017-9787 High TRUE
- CVE-2017-9791 CVE-2017-9791 High TRUE
- CVE-2017-9793 - High FALSE Дубликат к CVE-2018-1327
- CVE-2017-9804 - High TRUE
- CVE-2017-9805 CVE-2017-9805 High TRUE
CVE-2016-4003 - - Medium FALSE Применимо к Apache Struts 2.x до 2.3.28, а это версия 2.3.30. Тем не менее, исходя из описания, CVE действует при любых версиях Struts 2, если используется JRE 1.7 и меньше. Видимо нас тут решили перестраховать, но больше похоже на FALSE
- CVE-2018-1327 CVE-2018-1327 High TRUE
CVE-2017-5638 CVE-2017-5638 CVE-2017-5638 High TRUE Та самая уязвимость, которой воспользовались злоумышленники в Equifax в 2017 году
CVE-2017-12611 CVE-2017-12611 - High TRUE
CVE-2018-11776 CVE-2018-11776 CVE-2018-11776 High TRUE
struts-taglib:1.3.8 - CVE-2012-0394 - Medium FALSE Для struts2-core
- CVE-2013-2115 - High FALSE Для struts2-core
- CVE-2014-0114 - High FALSE Для commons-beanutils
- CVE-2015-0899 - High FALSE Не относится к taglib
- CVE-2015-2992 - Medium FALSE Относится к struts2-core
- CVE-2016-1181 - High FALSE Не относится к taglib
- CVE-2016-1182 - High FALSE Не относится к taglib
struts-tiles-1.3.8 - CVE-2012-0394 - Medium FALSE Для struts2-core
- CVE-2013-2115 - High FALSE Для struts2-core
- CVE-2014-0114 - High FALSE Под commons-beanutils
- CVE-2015-0899 - High FALSE Не относится к tiles
- CVE-2015-2992 - Medium FALSE Для struts2-core
- CVE-2016-1181 - High FALSE Не относится к taglib
- CVE-2016-1182 - High FALSE Не относится к taglib

Подробнее..

Перевод Как использовать простую утилиту для поиска уязвимостей в программном коде

01.09.2020 10:05:42 | Автор: admin
Graudit поддерживает множество языков программирования и позволяет интегрировать тестирование безопасности кодовой базы непосредственно в процесс разработки.


Источник: Unsplash (Markus Spiske)

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

Очевидно, что в современных реалиях разработки программного обеспечения важно обеспечить безопасность процессов. В своё время был даже введён специальный термин DevSecOps. Под этим термином понимают ряд процедур, направленных на выявление и устранение уязвимостей в приложении. Существуют специализированные open source решения для проверки уязвимостей в соответствии со стандартами OWASP, которые описывают различные типы и поведение уязвимостей в исходном коде.


Существуют разные подходы к решению проблем безопасности, например, статическое тестирование безопасности приложений (SAST), динамическое тестирование безопасности приложений (DAST), интерактивное тестирование безопасности приложений (IAST), анализ компонентов программного обеспечения (Software Composition Analysis) и так далее.

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

Я остановлюсь на статическом анализе кода и воспользуюсь простым open source инструментом, чтобы продемонстрировать всё на практике.

Почему я выбрал open source инструмент для статического анализа безопасности кода


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

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

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

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

Использование Graudit для анализа безопасности кода


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

Здесь мы будем использовать Graudit, простую утилиту для командной строки, которая позволит нам найти уязвимости в нашей кодовой базе. Он поддерживает разные языки, но всё-таки их набор ограничен. Graudit разработан на основе служебной утилиты grep, которая в своё время вышла под лицензией GNU.

Существуют похожие инструменты для статического анализа кода Rough Auditing Tool for Security (RATS), Securitycompass Web Application Analysis Tool (SWAAT), flawfinder и так далее. Но Graudit очень гибок и имеет минимальные технические требования. Тем не менее, у вас могут появиться задачи, которые Graudit решить не в состоянии. Тогда можно поискать другие варианты вот в этом списке.

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

$ git clone https://github.com/wireghoul/graudit

Теперь давайте создадим для Graudit символическую ссылку, чтобы использовать его в формате команды

$ cd ~/bin && mkdir graudit$ ln --symbolic ~/graudit/graudit ~/bin/graudit

Добавим алиас в .bashrc (или к другому конфигурационному файлу, который вы используете):

#------ .bashrc ------alias graudit="~/bin/graudit"

Перезагружаемся:

$ source ~/.bashrc # OR$ exex $SHELL

Проверим, успешно ли прошла установка:

$ graudit -h


Если вы увидите что-то похожее, то, значит, всё хорошо.



Я буду тестировать один из уже существующих моих проектов. Перед запуском инструмента ему нужно передать базу данных, соответствующую языку, на котором написан мой проект. Базы данных находятся в папке ~/gradit/signatures:

$ graudit -d ~/gradit/signatures/js.db

Итак, я протестировал два js-файла из моего проекта, и Graudit вывел в консоль информацию об уязвимостях в моём коде:





Можете попробовать таким же образом протестировать ваши проекты. Список баз данных для разных языков программирования можно посмотреть здесь.

Преимущества и недостатки Graudit


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

Это удобный инструмент, но пока он не всегда может точно указать, в чём именно заключается проблема, связанная с подозрительным участком кода. Разработчики продолжают дорабатывать Graudit.

Но в любом случае полезно обращать внимание на потенциальные проблемы безопасности в коде, используя подобные инструменты.

Начало...


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



На правах рекламы


Надёжный VPS и правильный выбор тарифного плана позволят меньше отвлекаться от разработки на неприятные проблемы всё будет работать без сбоев и с очень высоким uptime!

Подробнее..

DevSecOps организация фаззинга исходного кода

10.09.2020 06:07:43 | Автор: admin


Узнав результаты голосования, проведённого в одной из наших прошлых статей, мы решили более подробно обсудить вопрос организации фаззинга. Кроме того, в рамках онлайн-встречи по информационной безопасности "Digital Security ON AIR" мы представили доклад, основанный на нашем опыте в DevSecOps, где также рассказали об этой интересной теме.


Записи всех докладов можно посмотреть на нашем Youtube-канале. Если же вы предпочитаете текстовый формат, добро пожаловать под кат!


  1. Фаззинг
  2. Этапы интеграции фаззинга в проект
  3. Фаззинг в облаке
  4. Заключение



Фаззинг


Введение


Относительно недавно вышла замечательная книга Building Secure and Reliable Systems от инженеров компании Google. Её главным лейтмотивом является мысль о том, что безопасность и надёжность систем и ПО тесно взаимосвязаны. Публикация подобного материала одним из крупнейших лидеров IT-рынка отлично демонстрирует, что безопасность становится неотъемлемой частью как процесса разработки, так и всего жизненного цикла системы или ПО.


Эффективно ли тестирование?


Чтобы сделать безопасную и стабильную систему, её нужно тестировать. К сожалению, писать идеальный код, не требующий доработки, не может никто. Поэтому в написанном коде априори есть ошибки и уязвимости. Как и в других сферах жизни, понимание этого приходит лишь с опытом. Аналогично в IT в целом и в разработке в частности: каждая компания или команда может находиться на своем уровне профессиональной зрелости. Какие-то подходы на определённом этапе будут просто недоступны для них. Так, если не внедрена непрерывная интеграция, то и о нормальном тестировании речи быть не может, не говоря уже о тестировании безопасности во время разработки. Так что, если вы не внедрили непрерывную интеграцию (CI), мы надеемся, что после прочтения нашей статьи вы начнёте делать шаги в нужном направлении. Далее мы предполагаем, что у вас есть настроенный и функционирующий CI, на базе которого и будут предлагаться улучшения.


CI/CD нужны не только для автоматизированных тестов, но и в глобальном плане для того, чтобы соответствовать требованиям времени. Каскадная модель разработки, согласно которой этапы проектирования, разработки и тестирования происходят строго последовательно, постепенно уходит в прошлое или остается лишь в определенных нишах. Сейчас очень важна скорость разработки. Важно, чтобы каждая часть ПО могла развиваться независимо от других частей. Таким требованиям соответствует метод непрерывной разработки. А непрерывной разработке нужно непрерывное тестирование.


Но перед тем, как продолжить, нам хотелось бы напомнить, что чем раньше найдена проблема, тем проще и менее затратно её исправить как во временном, так и в денежном эквиваленте. С точки же зрения обнаружения уязвимостей, это ещё и временные затраты на доставку патчей до ваших клиентов, а также их своевременная установка. Ведь закрытие неизвестной уязвимости нельзя заранее запланировать. А отвлекать разработчиков по малейшему поводу от их основной деятельности разработки продукта это тоже терять время и деньги. И ваши клиенты не будут рады часто прилетающим экстренным патчам. Равно как и разработчики :)



https://resources.securitycompass.com/blog/how-to-sell-training-costs-internally-2


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



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


Что такое фаззинг?


Для начала небольшой исторический экскурс. Сам термин "фаззинг" ("fuzzing") появился впервые около 30 лет назад в работе An Empirical Study of the Reliability of UNIX Utilities. С его появлением связана одна интересная легенда. В 1988 удар молнии исказил передаваемые по линии данные, что привело к падению утилиты автора исследования. После этого уже профессор Брет Миллер вместе со своими студентами сделал полноценное исследование в данном направлении и на одном из семинаров представил программу fuzzer. Данная программа предназначалась как раз для тестирования ПО. Сам Брет Миллер так описывал свой подход: Наш подход не является заменой формальной верификации или тестирования. Скорее, это легковесный механизм для поиска ошибок в системе и повышения её надёжности. Официальное же определение фаззинга звучит так:


Фаззинг это техника автоматизированного тестирования, при которой на вход программе подаются специально подготовленные данные, которые могут привести её к аварийному состоянию или неопределённому поведению.

Сегодня мы сконцетрируемся на использовании фаззинга для кода на таких языках программирования, как С и С++, потому что:


  • в ПО на таких ЯП чаще находят бинарные уязвимости;
  • эффективные средства поиска уязвимостей, такие как libFuzzer и AFL, ориентированы, в первую очередь, на языки C и C++;
  • огромное количество программ и библиотек написано на C и C++.

Тем не менее все указанные наработки как минимум можно использовать без особых проблем для ПО на Rust или Go.


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



The Art, Science, and Engineering of Fuzzing:A Survey


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


  • по знанию о входных данных:
    • Black-box мы ничего не знаем про формат данных. Нужно реверсить
    • Gray-box что-то о формате мы выяснили, но полная информация о нём нам всё ещё неизвестна
    • White-box у нас есть спецификация. Мы располагаем всеми сведениями о формате данных
  • по цели, которая будет подвергнута фаззингу:
    • source-based у нас есть исходники проекта
    • binary-based у нас нет исходников проекта
  • по наличию обратной реакции от тестируемого приложения:
    • feedback driven есть обратная реакция
    • not feedback driven нет обратной реакции
  • по операциям, которые будут совершаться над входными данными:
    • генерационные
    • мутационные
    • комбинированные

Стоит отметить, что часто binary-based фаззеры называют black-box, а source-based gray- или white-box. В данной статье нас в первую очередь интересует source-based фаззинг с обратной связью, поскольку мы строим DevSecOps для разработки собственного продукта или анализа opensource-проектов, а это значит, что исходный код для него у нас есть.


Фаззинг всё ещё популярен?


Кто-то может задаться вопросом: "Если фаззингу уже более 30 лет (согласно некоторым источникам и с учетом прообраза этого метода тестирования, и все 70), то почему только в последнее время он стал так популярен?".


В начале тысячелетия Microsoft в рамках описания создания Secure Development Lifecycle
выпустили несколько статей, посвящённых тематике фаззинга, но тогда речь в основном шла о black-box-фаззинге. Однако в 2013 году фаззинг получил вторую жизнь, благодаря созданию feedback-driven fuzzing, который позволил достичь новых высот в этой сфере. Важную роль также сыграло появление простых и понятных инструментов, о которых мы расскажем далее.


Feedback-driven fuzzing это вид фаззинга, при котором фаззер изменяет входные данные так, чтобы их обработка затрагивала как можно больше участков кода программы. Работа таких фаззеров возможна, благодаря их способности реагировать на отклик (feedback) программы. Обычно таким откликом является покрытие кода. Метрики покрытия кода отслеживают суммарное количество выполненных строк кода, базовых блоков, количество сделанных условных переходов. Задача фаззера генерировать данные, которые приводят к увеличению покрытия кода.


Одним из наиболее популярных feedback-driven фаззеров является American Fuzzy Lop от Michael Zalewski. AFL был создан в 2013 году и стал главным двигателем прогресса в современном фаззинге.


Про AFL было уже несколько статей на Хабре, но повторимся, как он работает:


  • в исполняемый файл добавляется инструментация для сбора информации о покрытии кода. Информация будет сохраняться в shared bitmap;
  • AFL запускает программу и доходит до функции, которую нужно профаззить. Здесь он сохраняет своё состояние через системный вызов fork;
  • далее происходит цикл "мутация данных запуск откат". При этом данные мутируются с учётом покрытия кода, получаемого от программы.

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


AFL оказался чрезвычайно эффективным при фаззинге различных программ. Это и сделало его знаменитым. Он был добавлен в Google Summer of Code (GSoC), а вокруг него сформировалось большое сообщество. Многочисленные энтузиасты предлагают собственные модификации AFL, которые по разным причинам не были приняты в основную ветку (например, существуют вариации для других ЯП). Об этом можно узнать подробнее в ещё одной нашей статье, посвященной различным вариациям AFL фаззера. Ещё хотелось бы заметить, что создатель AFL отошёл от дел и до недавнего времени проект почти не обновлялся, из-за чего многие стали использовать его активно поддерживаемый форк AFL++.


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


В итоге так и случилось. В 2015 году появился libFuzzer один из подпроектов LLVM. LibFuzzer позволяет реализовать feedback-driven in-memory фаззинг и имеет все преимущества, характерные для средств фаззинга исходного кода:


  • позволяет создавать отдельные фаззеры для каждой функции из рабочего проекта;
  • использует санитайзеры;
  • может быть интегрирован в проект через toolchain.

Статический анализ


Прежде чем обратиться непосредственно к Continuous Fuzzing, скажем пару слов об анализаторах кода и фаззерах. Для тех, кто думает, нужно ли это всё, если те или иные анализаторы кода уже используются в процессе разработки.


Code analysis Fuzzing
Встраивание в CI Да* Да
Тип Статика Динамика
Покрытие кода ~100% Зависит от тестовых данных и алгоритмов мутации
Ложные срабатывания Много Нет
Пропуск ошибок Зависит от базы знаний анализатора Зависит от тестовых данных
Типы ошибок Широкий спектр Определённый спектр ошибок
Переиспользование Нет* Уходит в тест
Ручной анализ Много Нет

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


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


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


На эту тему есть отличная статья How to Prevent the next Heartbleed, в которой автор сравнил эти подходы для улучшения качества openssl на примере нашумевшей уязвимости heartbleed. И именно подход, объединяющий анализ кода и фаззинг, помог выявить проблему с heartbleed. А обычное покрытие кода тестами, пусть и 100% по всем веткам разработки, оказалось не столь эффективным.


Непрерывный фаззинг в непрерывной разработке



Что же такое Continuous Fuzzing? Continuous fuzzing это подход к организации процесса. Иными словами, его эффективность будет зависеть от правильности организации процесса разработки в вашей компании. Все инструменты для организации такого процесса уже давно созданы осталось лишь правильно встроить их в свою систему.


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


Крупные компании уже не первый год настраивают у себя процесс фаззинга и развивают используемые инструменты. Этот подход уже множество раз доказал свою эффективность. Одна только статистика от Google ошеломляет числом найденных за время ее работы уязвимостей. По данным на 2019-й год, за все время существования ClusterFuzz было обнаружено около 16000 ошибок в Chrome и 11000 ошибок в более чем 160 популярных проектах с открытым исходным кодом.


Для организации фаззинга большие компании используют разный инструментарий. Выбор инструментов зависит от того, какие цели преследуются фаззингом, а также насколько безболезненно возможно внедрить этот инструментарий в уже существующий процесс разработки. В основе ClusterFuzz от Google лежат LibFuzzer, AFL и HonggFuzz (создан на базе LibFuzzera). Компания Microsoft недавно сделала публичным свой проект Springfield, превратив его в полноценный SaaS. Mozilla, помимо уже упомянутых фаззеров, разработала свой фреймворк для быстрого построения фаззеров (Grizzly) и дашборд для агрегации результатов фаззинга. Github не отстает. Так, он интегрировал ossfuzz в свою систему.


Вы, конечно, можете сказать, что это всё довольно большие компании, которые могут выделить значительные "лишние" мощности на "генерацию случайных данных". Но что мы видим? Несмотря на работу ClusterFuzza, та же компания Google активно поддерживает сторонних разработчиков и исследователей безопасности, позволяя запускать написанные ими фаззеры на своих мощностях. А если в процессе были обнаружены баги, за каждый из них выплачивается денежное вознаграждение. Всё-таки предварительный анализ и творческий подход исследователя безопасности позволяют найти новые векторы и уязвимости там, где "бездушная машина" заходит в тупик.


Поэтому, чтобы начать строить процесс Continuous Fuzzing у себя, необязательно быть крупной компанией, как тот же Google. Для этого не нужно крупных кластеров или мейнфреймов стартовые требования для внедрения Continuous Fuzzing минимальны. Речь о них пойдёт ниже, так как мы в своих проектах чаще используем значительно меньшие мощности и при этом успешно находим уязвимости.


Этапы интеграции фаззинга в проект


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


Выбор инструментов для организации фаззинга


Начнём со списка инструментов, которые необходимы для организации фаззинга, и разделим их условно на 3 большие категории:


  • Составление поверхности атаки (Attack Surface)
    • ПО, в котором удобнее проводить аудит кода (Пример: Sourcetrail)
    • ПО для построения MindMap-схем (Пример: Xmind)
  • Фаззинг
  • Санитайзеры (Sanitizers)

Для составления поверхности атаки необходимо ПО для аудита кода. Например, это может быть sourcetrail, understand, или подойдёт какая-нибудь популярная IDE. Лучше, чтобы ПО для инспекции кода имело возможность автоматического "рисования" блок-схем. Если у разработчиков уже есть готовые блок-схемы для программ из их проекта, то это значительно облегчит процесс. Если же нет, то искренне соболезнуем. Далее из составленной блок-схемы необходимо извлечь те куски, где тем или иным способом могут попасть на вход данные из недоверенных источников. Так и определяется поверхность атаки. На практике весьма удобно оформлять поверхность атаки в виде майндмап. Поверхность атаки служит не только для составления метрик "безопасности" (code coverage), а также, можно сказать, выступает чеклистом для написания фаззеров.


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


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


Сегодня работу с санитайзерами поддерживают компиляторы GCC и Clang. Настоятельно рекомендуем обратить на них внимание и попробовать, если еще не пробовали.


Мы не зря начали наш список инструментов с тех, что помогают составить Attack Surface. В презентации для FuzzCon, посвящённой 25-летию фаззинга, была представлена следующая схема:



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


Определение Attack Surface


Что же стоит за словосочетанием "attack surface" ("поверхность атаки")? Возможно, вы уже знаете официальное значение, так как оно так прочно обосновалось в глоссарии, что даже удостоилось отдельной страницы в словаре хакерских терминов журнала Wired. Однако повторимся: если говорить по простому, это "возможность получения некорректных данных из недоверенных источников".


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


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


  • файлы различные парсеры (XML, JSON), мультимедиакодеки (аудио, видео, графика);
  • сеть сетевые протоколы (HTTP, SMTP), криптография (SSL/TLS), браузеры (Firefox, Chrome) и архиваторы файлов (ZIP, TAR).

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


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



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


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

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


Анализ целей для фаззинга


Цель для фаззинга (fuzz target) это функция, которая принимает на вход данные и обрабатывает их с использованием тестируемого API. Иными словами, это то, что нам необходимо фаззить.


Данный этап заключается в тщательном анализе каждой цели для фаззинга из attack surface. Вот что необходимо узнать:


  1. Аргументы функции, через которые передаются данные для обработки. Нужен сам буфер для данных и его длина, если её возможно определить.
  2. Тип передаваемых данных. Например, документ html, картинка png, zip-архив. От этого зависит то, как будут генерироваться и мутироваться поступающие на вход данные.
  3. Список ресурсов (память, объекты, глобальные переменные), которые должны быть инициализированы перед вызовом целевой функции.
  4. Если мы фаззим внутренние функции компонентов, а не API, то понадобится составить список ограничений, которые накладываются на данные кодом, выполненным ранее. Бывает так, что проверка данных происходит в несколько этапов это нам тоже следует учитывать.


Анализ opensource-проекта с использованием sourcetrail


Этот этап является самым кропотливым, ведь целей для фаззинга может быть очень много: сотни или даже тысячи! Как раз тут у нас появился термин "реверс исходников", поскольку времени и сил на анализ может быть потрачено примерно столько же, сколько уходит на реверс приличного бинарного файла.


Подбор входных данных


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


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


При создании набора seeds нужно учитывать, что:


  • Его элементы должны влиять на покрытие кода программы. Чем выше покрытие, тем меньше неисследованных мест остаётся в программе.
  • Размер его элементов не должен быть большим, иначе это повлияет на скорость фаззинга. Ведь чем больше длина входных данных, тем дольше функция будет их обрабатывать и меньше запусков программы сможет сделать фаззер за единицу времени.
  • Кейсы в сидах должны быть функционально различными. Сильно похожие данные будут тормозить процесс фаззинга, поскольку фаззер часто будет попадать в места, которые уже были им исследованы. Перед запуском фаззера лучше провести минимизацию данных и убрать всё лишнее.

В процессе фаззинга сиды трансформируются в корпус. Корпус (corpus) это набор тест-кейсов, которые привели к росту покрытия кода во время фаззинга целевой программы или функции. Иначе говоря, это те самые интересные инпуты, которые потенциально могут привести к крешу программы. В корпусе сначала содержатся сиды, потом их мутации, затем мутации мутаций и т.д. Здесь мы видим, что фаззинг (feedback-driven) это циклический процесс, где на каждой новой итерации у нас всё больше шансов сгенерировать набор входных данных, который позволит найти уязвимости в программе.



Сиды очень сильно влияют на эффективность фаззинга. Этап подбора входных данных также нельзя игнорировать. Однако современные фаззеры порой бывают настолько хороши, что могут буквально из ничего сгенерировать хороший корпус. Пример такого корпуса, сгенерированного фаззером AFL, показан на рисунке выше. Более подробно этот процесс автор AFL Michal Zalewski описал в своём блоге. Советуем почитать.


Написание фаззеров


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


Допустим, у нас есть функция, которую надо фаззить. Для этого мы скачиваем набор библиотек llvm, среди которых есть компилятор clang и санитайзеры. Далее пишем код для фаззера:


#include "MyAPI.h"extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {  MyAPI_ProcessInput(Data, Size);  return 0;}

Данный код скомпилируется в отдельный исполняемый файл, где в цикле на вход функции MyAPI_ProcessInput будут подаваться случайные данные Data размером Size. Если фаззер обнаружит ошибку, то на консоль будет выведено сообщение о ней. Настанет время устранить ошибку, а после запустить фаззинг дальше.


Сообщение об ошибке от libfuzzer
<!--INFO: Seed: 2240819152INFO: Loaded 1 modules   (6 inline 8-bit counters): 6 [0x565e90, 0x565e96), INFO: Loaded 1 PC tables (6 PCs): 6 [0x541908,0x541968), INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytesINFO: A corpus is not provided, starting from an empty corpus#2  INITED cov: 3 ft: 4 corp: 1/1b lim: 4 exec/s: 0 rss: 35Mb===================================================================29562==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fff2fba1ba0 at pc 0x0000004dda61 bp 0x7fff2fba1b30 sp 0x7fff2fba12d0WRITE of size 65 at 0x7fff2fba1ba0 thread T0    #0 0x4dda60 in strcpy (/home/user/Documents/tmp/main+0x4dda60)    #1 0x52540f in MyAPI_ProcessInput(char const*) (/home/user/Documents/tmp/main+0x52540f)    #2 0x52561e in LLVMFuzzerTestOneInput (/home/user/Documents/tmp/main+0x52561e)    #3 0x42fe0a in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/home/user/Documents/tmp/main+0x42fe0a)    #4 0x42f3a5 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) (/home/user/Documents/tmp/main+0x42f3a5)    #5 0x4310ee in fuzzer::Fuzzer::MutateAndTestOne() (/home/user/Documents/tmp/main+0x4310ee)    #6 0x431dc5 in fuzzer::Fuzzer::Loop(std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, fuzzer::fuzzer_allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&) (/home/user/Documents/tmp/main+0x431dc5)    #7 0x427df0 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/home/user/Documents/tmp/main+0x427df0)    #8 0x44b402 in main (/home/user/Documents/tmp/main+0x44b402)    #9 0x7f7ee294409a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2409a)    #10 0x421909 in _start (/home/user/Documents/tmp/main+0x421909)Address 0x7fff2fba1ba0 is located in stack of thread T0 at offset 96 in frame    #0 0x5252ef in MyAPI_ProcessInput(char const*) (/home/user/Documents/tmp/main+0x5252ef)  This frame has 1 object(s):    [32, 96) 'buf' <== Memory access at offset 96 overflows this variableHINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork      (longjmp and C++ exceptions *are* supported)SUMMARY: AddressSanitizer: stack-buffer-overflow (/home/user/Documents/tmp/main+0x4dda60) in strcpyShadow bytes around the buggy address:  0x100065f6c320: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  0x100065f6c330: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  0x100065f6c340: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  0x100065f6c350: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  0x100065f6c360: 00 00 00 00 00 00 00 00 f1 f1 f1 f1 00 00 00 00=>0x100065f6c370: 00 00 00 00[f3]f3 f3 f3 00 00 00 00 00 00 00 00  0x100065f6c380: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  0x100065f6c390: f1 f1 f1 f1 00 00 00 00 f2 f2 f2 f2 f8 f3 f3 f3  0x100065f6c3a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  0x100065f6c3b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  0x100065f6c3c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00Shadow byte legend (one shadow byte represents 8 application bytes):  Addressable:           00  Partially addressable: 01 02 03 04 05 06 07   Heap left redzone:       fa  Freed heap region:       fd  Stack left redzone:      f1  Stack mid redzone:       f2  Stack right redzone:     f3  Stack after return:      f5  Stack use after scope:   f8  Global redzone:          f9  Global init order:       f6  Poisoned by user:        f7  Container overflow:      fc  Array cookie:            ac  Intra object redzone:    bb  ASan internal:           fe  Left alloca redzone:     ca  Right alloca redzone:    cb  Shadow gap:              cc==29562==ABORTINGMS: 1 InsertRepeatedBytes-; base unit: adc83b19e793491b1c6ea0fd8b46cd9f32e592fc0xa,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,\x0a\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffartifact_prefix='./'; Test unit written to ./crash-76387a0aaeb6a1d2b6b6f095ab49c927c00243e5-->

Сборка и её особенности на разных платформах


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


Платформа


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


Платформа* LibFuzzer AFL ASAN/UBSAN/TSAN
Windows -
Linux + + +
OSX + + +

Однако, если продукт работает лишь на ОС Windows, не стоит отчаиваться. Во-первых, инструментарий для фаззинга активно развивается. Во-вторых, код крупных проектов, как правило, не является платформозависимым. Можно собрать необходимую часть проекта на unixсистеме и начать его фаззить. Ошибки никуда не денутся.


Компилятор


Думая о том, каким компилятором собирать фаззеры, мы всегда вспоминаем, что libfuzzer это подпроект LLVM, а в AFL предусмотрен llvm_mode, про который в его readme написано:


Настоящая инструментация на этапе компиляции, а не просто грубые изменения на уровне ассемблера.

Возможность добавления инструментации во время компиляции и поддержка LLVM самыми популярными фаззерами делает выбор компилятора очевидным для сборки лучше всего использовать clang. Конечно, нам попадались проекты, которые не были ориентированы на сборку этим компилятором, но нас это никогда не останавливало.


Система сборки


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


# Step 1add_custom_target(${FUZZ_TARGET_NAME})# Step 2function(add_fuzzer name path)    add_executable(${name} ${SRC} ${path})    target_compile_options(...)    set_target_properties(...)    add_dependencies(${FUZZ_TARGET_NAME} ${name})# Step 3set(fuzzers my_ideal_fuzzer1 my_ideal_fuzzer2 ...)foreach(fuzzer ${fuzzers})    add_fuzzer(${fuzzer} ${FUZZERS_DIR})endforeach()

Кратко интеграцию фаззеров в сборку проекта на cmake можно описать в трех шагах на примере нашего недавнего проекта:


  • Создаём цель ${FUZZ_TARGET_NAME}, к которой в зависимости будем добавлять фаззеры.
  • Пишем простую функцию, которая принимает на вход название фаззера и путь к его исходному коду. Реализация этой функции сводится к нескольким вызовам других cmake-функций. Сначала необходимо указать список исходников для сборки компонента который мы будем фаззить. Он хранится в переменной ${SRC}. Потом идут два вызова для установки опций компилятора и компоновщика. Последним вызовом добавляем новый исполняемый файл в качестве зависимости к цели, созданной ранее.
  • Остаётся дописать простой цикл который поочередно добавит все фаззеры к созданной ранее цели.

Другой пример система сборки autotools. Она является достаточно старой и не такой удобной, как cmake. Однако это не помешает внедрению фаззинга в проект. Например, здесь показан пример интеграции фаззинга в WebRTC сервер janus-gateway. Сам скрипт занимает где-то 100 строчек на bash. Конечно, это не так много, однако, как только проект начнёт меняться, эти скрипты тоже необходимо будет поддерживать в актуальном состоянии. На это может потребоваться дополнительное время.


Фаззинг в облаке


Наконец, мы добрались до финальной главы непосредственно про организацию continuous fuzzing. Сначала мы посмотрим, как с этой задачей справились в Google, а затем что получилось у нас.


Фаззинг-ферма от Google


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



Схема сontinious fuzzing от google


Пробежимся по элементам схемы:


  • Developer команда разработчиков проекта
  • Upstream project репозиторий с проектом
  • Oss-fuzz вспомогательный репозиторий для интеграции continuous fuzzing. Хранит build-конфиги фаззеров
  • Builder Сборщик фаззеров. Его задача скачивать исходники фаззеров из репозитория с проектом, подхватывать build-конфиги из oss-fuzz и производить сборку фаззеров. После сборки builder закачивает в хранилку (GCS bucket) файлы, необходимые для запуска фаззера
  • Clusterfuzz масштабируемая система для управления процессом фаззинга. Отвечает за планирование запусков фаззеров, обработку полученных от них данных, сбор статистики и многое другое. Пополняет свою коллекцию фаззеров из хранилки.
  • Issue tracker система отслеживания задач: youtrack, jira и подобные им программы. Сюда приходят отчёты о найденных уязвимостях

А теперь рассмотрим сам процесс интеграции. Начнём с того, что у команды разработчиков должен иметься репозиторий с кодом проекта. Здесь же должны храниться исходные коды фаззеров. Для интеграции необходимо подготовить build-конфиги для сборки и запуска фаззеров в инфраструктуре Google. Готовые build-конфиги нужно добавить в репозиторий oss-fuzz через пулл-реквест. После принятия пулл-реквеста можно радоваться: фаззеры будут автоматически собраны и запущены в Clusterfuzz. С каждым новым коммитом в oss-fuzz будут подхватываться фаззеры из репозитория с проектом. Если вдруг нашлась уязвимость, то уведомление придёт к разработчикам в issue tracker. Разработчики своевременно исправляют баги и все остаются довольными.


Почему решение от Google подходит не всем?


Отметим, что ферма для фаззинга от Google это круто. Впрочем, и в этой бочке мёда есть ложка дёгтя. Несмотря на то, что Clusterfuzz имеет открытый код, он жёстко привязан к облачным сервисам Google.


Это означает, что:


  • Для закрытых проектов такая система не подходит.
  • Изменить что-то под себя вряд ли получится.

Поэтому мы сделали свою фаззинг-ферму. Собственное решение для continuous fuzzing позволяет нам:


  • Снять ограничения на используемую инфраструктуру. Ферму можно запускать как в публичном облаке, так и у себя локально.
  • Полностью контролировать работу фермы: мониторинг, конфигурирование, добавление собственных фич и т.д.
  • Осуществлять фаззинг проприетарного программного обеспечения.

Схема нашей фермы похожа на схему от Google, однако она проще. Основная логика сосредоточена в компоненте FuzzFarm. Он выполняет те же задачи, что и Clusterfuzz. Ферма использует только наши внутренние ресурсы и может работать без доступа к интернету.



Схема работы нашей фаззинг-фермы


Интерфейс пользователя мы сделали простым и минималистичным. При этом он покрывает все необходимые задачи в continuous fuzzing.



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


Что получаем в итоге?


В итоге мы имеем следующие преимущества использования continuous fuzzing:


  • Для разработчика это более быстрое обнаружение ошибок и готовые данные для юнит-тестов. В баг-трекер приходит уведомление, разработчик устраняет ошибку и быстро делает тест с использованием полученного от фермы кейса.
  • Ещё один шаг к оптимизации заключается в автоматическом создании юнит-тестов из полученных результатов фаззинга. Тестировщик будет рад :)
  • Наконец, можно добавить автоматический анализ крэшей. Для безопасника будет намного лучше, если система за него будет оценивать критичность найденных уязвимостей и предоставлять ему красивый отчёт. Более того, запуск фаззеров и обработка их результатов производятся автоматически. У безопасника появится время попить кофе.

Заключение


Тема фаззинга на сегодняшний день уже вышла далеко за пределы просто одного из видов тестирования. Это целое направление, объединившее единомышленников в коммьюнити. Уже проходят целые конференции и митапы, посвященные только фаззингу. Это большое и разноплановое направление со множеством перспектив развития, к примеру, Structure-Aware фаззинг, о котором мы постараемся рассказать позже. Поскольку, как вы могли заметить, развитие как отдельных инструментов, так и в целом направления не стоит на месте, существует возможность улучшать показатели по всем параметрам: скорости, качества, трудозатратам и т.п.


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


В конце хотелось бы поблагодарить за помощь в создании доклада всю нашу команду отдела Исследований и особенно других членов фаззинг-команды:


  • Дмитрий Евдокимов d1g1
  • Никита Кныжов presler

Соавтор статьи: Павел Князев poulix


Материалы


Подробнее..

От Threat Modeling до безопасности AWS 50 open-source инструментов для выстраивания безопасности DevOps

14.09.2020 10:18:41 | Автор: admin

Привет, Хабр! Я консультант по информационной безопасности в Swordfish Security по части выстраивания безопасного DevOps для наших заказчиков. Я слежу за тем, как развивается тенденция развития компаний в сторону DevSecOps в мире, пытаюсь транслировать самые интересные практики в русскоговорящее сообщество и помогаю выстраивать этот процесс с нашей командой у заказчиков. За последние 2 года тема DevSecOps стала привлекать все больше внимания. Новые инструменты не успевают стать частью быстро растущего набора практик, из-за чего у меня появилось желание поставить некоторую контрольную точку в виде списка инструментов. Отправной точкой стал выход статьи коллег из Mail.ru, где отдельно был выделен раздел по безопасности Kubernetes. Я решил расширить этот список, охватив другие этапы жизненного цикла SDLC и приведя пару новых инструментов.

Под практикой подразумевается набор мер, который может быть встроен в один из этапов SDLC/DevOps (Threat modeling, SAST, DAST, SCA, Docker image scanning, Kubernetes scanning, AWS Audit и так далее).

Оглавление

Одно из видений практик DevSecOps. Источник: https://holisticsecurity.io/2020/02/10/security-along-the-container-based-sdlcОдно из видений практик DevSecOps. Источник: https://holisticsecurity.io/2020/02/10/security-along-the-container-based-sdlc

Dev

Threat Modeling

Моделирование угроз в контексте Secure Development Lifecycle представляет из себя процесс анализа архитектуры ПО на предмет наличия в ней потенциальных уязвимостей и небезопасных технологий. Чтобы сократить расходы на добавление дополнительного функционала с точки зрения безопасности, решением может являться внедрение процесса проверок ИБ еще на этапе проектирования архитектуры. На этом же этапе формируются требования со стороны специалистов по безопасности приложений, которые в дальнейшем пойдут в backlog. Подобный подход прибегается, на самом деле, во всех практиках DevSecOps и получил устойчивое выражение Shift security to the left.

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

OWASP Threat Dragon

Ссылка на OWASP Threat Dragon

Довольно простой инструмент для самостоятельного моделирования угроз. Пользователь отрисовывает архитектуру ПО, указывая на ней угрозы, которые могут следовать по методологии STRIDE. Никакой автоматизации, но в классическом варианте, когда процессы отлажены, может быть полезно. Это инструмент в частности используется для моделирования угроз в GitHub.

Пример диаграммы Threat DragonПример диаграммы Threat Dragon

Pytm

Ссылка на Pytm

Pytm - фреймворк на Python для создания диаграмм потоков данных и списка угроз системы.

Пользователь Pytm задает модель взаимодействия компонентов в архитектуре в виде кода, после чего инструмент самостоятельно формирует схемы взаимодействия компонентов. Может быть полезно для совместной работы и использовании в системе отслеживания версий.

Результат Pytm в виде графика Результат Pytm в виде графика

Materialize threats tool

Ссылка на Materialize threats tool

Materialize-threats - фреймворк на Python, который позволяет конвертировать схемы архитектуры ПО из сервиса draw.io в графы, сохранить их в базу и в дальнейшем работать с утверждениями относительно этих графов при помощи SQL запросов. Помимо этого инструмент умеет формировать тесты на Gherkin.

Это любопытный open-source инструмент, который хорошо описывает то, куда двигаются современные Enterpise-решения по моделированию угроз вроде Irius Risks.

Сценарий работы с ним следующий:

  1. Создание диаграммы взаимодействия компонентов в .drawio, согласно вашему data flow

  2. Выделение доверенных зон на диаграмме, согласно Rapid Threat Model Prototyping methodology(в readme есть пояснение)

  3. Сохранение файла .drawio

  4. Запуск materialize.py с импортом файла .drawio

  5. Получение сценария реализации угроз в формате Gherkin.

Пример архитектуры в draw.io в качестве входных данных для Materialize threats tool Пример архитектуры в draw.io в качестве входных данных для Materialize threats tool Результат работы Materialize threats tool Результат работы Materialize threats tool

Не будем долго останавливаться на других инструментах ибо принцип работы везде похожий.

Вот еще несколько open-source инструментов по части моделирования угроз:

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

Статический анализ приложений на уязвимости (SAST):

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

Но есть и несколько довольно любопытных инструментов, которые могут быть применены универсально.

Salus

Ссылка на Salus

Образ контейнера, в который помещено сразу несколько статических анализаторов, вроде Bandit, Gosec, Brakeman, а также анализаторы open-source компонентов (Ruby,Node.js, Python,Go). Запускается это следующим образом:

# Navigate to the root directory of the project you want to run Salus on$ cd /path/to/repo# Run the following line while in the root directory (No edits necessary)$ docker run --rm -t -v $(pwd):/home/repo coinbase/salus

На выходе получаем JSON/YAML отчет. На GitHub также можно найти описание встраивания в CircleCI.

ShiftLeft Scan

Ссылка на ShiftLeft Scan

Инструмент работает по аналогии с Salus, но с поддержкой большего количества статических анализаторов. В репо можно посмотреть те статические анализаторы, которые были помещены в docker образ (gosec, find-sec-bugs, psalm, bandit, ). В образ Docker поместили даже анализаторы terraform, bash, kubernetes манифестов.

Пример запуска статического анализа Python проекта:

$ docker run --rm -e "WORKSPACE=${PWD}" -v "$PWD:/app" shiftleft/sast-scan scan --src /app --type python

Более того, к инструменту есть интеграция с IDE.

Пример интеграции с VS Code для ShiftLeftПример интеграции с VS Code для ShiftLeft

GitLab SAST

Ссылка на описание SAST в GitLab

Gitlab является довольно популярной DevOps платформой, но что еще в ней есть, так это бесплатный набор разных open-source SAST, которые можно подключить из коробки в пайплайн. В Gitlab также есть возможность встроить SCA, поиск секретов, fuzzing и другие практики DevSecOps, но централизованное управление всеми средствами будет доступно только в Gold-версии.

Пример встраивания SAST в пайплайн GitLab.Пример встраивания SAST в пайплайн GitLab.

LGTM

Ссылка на LGTM

LGTM - облачная платформа для сканирования кода от компании Semmle, которая в конце того года стала частью GitHub. Semmle также являются автором CodeQL, применение которого было анонсировано GitHub на своей онлайн-конференции Satellite.

Пример отчета LGTMПример отчета LGTM

Semgrep

Ссылка на облачную версию Semgrep

Уникальный проект, про который хотелось бы написать отдельную статью. По сути это не столько статический анализатор кода на наличие уязвимостей, сколько фреймворк, под капотом которого находится AST и Data flow, который можно использовать по разным назначениям. Например, можно сформировать следующий запрос:

$ semgrep --config=&lt;path/to/config> path/to/src

Конфигурационный файл для semgrep пусть будет следующим:

rules:- id: user-eval  patterns:  - pattern-inside: |      def $F(...):        ...  - pattern-either:    - pattern: eval(..., request.$W.get(...), ...)    - pattern: |        $V = request.$W.get(...)        ...        eval(..., $V, ...)    - pattern: eval(..., request.$W(...), ...)    - pattern: |        $V = request.$W(...)        ...        eval(..., $V, ...)    - pattern: eval(..., request.$W[...], ...)    - pattern: |        $V = request.$W[...]        ...        eval(..., $V, ...)

В результате Semgrep будет находить инъекции вроде тех, что отображены ниже на скриншоте (желтым отмечены те строки, которые semgrep посчитал уязвимыми):

Результат выполнения поиска Semgrep из облачной версии инструментаРезультат выполнения поиска Semgrep из облачной версии инструмента

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

Проверка open-source компонент - SCA

Наряду с тем кодом, разработка которого выполняется внутри команды, необходимо также проверить open-source, который подключается к проекту. Отсутствие процесса анализа сторонних компонент на наличие уязвимостей может существенно повлиять на безопасность продукта (как например, это случилось с компанией Equifax). Принципы работы, сравнение популярных инструментов SCA я приводил в отдельной статье, а здесь мы поговорим о том, какие существуют инструменты.

Dependency Check

Ссылка на Dependency Check

Dependency Check - одно из самых популярных open-source решений от OWASP для проверки сторонних компонентов . Существует большое количество готовых интеграций, способов встраивания в пайплайн, но у инструмента есть куда развиваться в сторону качества. Рекомендуется к внедрению для компаний, где процессы Secure SDLC находятся на начальных этапах и у специалистов со стороны ИБ есть время для разбора ложных срабатываний. У Dependency Check нет единой платформы, в которой можно отслеживать результаты в ретроспективе, поэтому, если процессы менеджмента уязвимостей в организации не выстроены, стоит обратить внимание на Dependency Track.

# Dependency Check Maven Plugin example$ mvn org.owasp:dependency-check-maven:check
Скриншот из HTML-отчета Dependency CheckСкриншот из HTML-отчета Dependency Check

Dependency Track

Ссылка на сайте Dependency Track

Dependency Track - второе по популярности решение от OWASP, которое представляет из себя веб-платформу, принимающую на вход Software bill of materials (SBOM) от другого инструмента CycloneDx. Dependency Track исследует BOM, после чего обращается в общедоступные базы данных уязвимостей, например, NVD. Инструмент имеет также возможность интегрироваться со Slack, Microsoft Teams, сканировать репозитории артефактов и проверять сторонние компоненты на лицензионную чистоту.

# CycloneDx Maven Plugin example to make SBOM$ mvn org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBom
Скриншот из веб-интерфейса Dependency Track.Скриншот из веб-интерфейса Dependency Track.

Snyk Open-source

Ссылка на официальном сайте Snyk Open-source

Компания Snyk кроме того, что развивает свою коммерческую платформу для сканирования open-source компонентов в проектах, также предоставляет бесплатную версию в виде SaaS-решения. Проекты можно подгружать как через репозиторий (GitHub, Bitbucket), так и через CLI.

Скриншот из SaaS-платформы Snyk open-source для Python-проектаСкриншот из SaaS-платформы Snyk open-source для Python-проекта

Как может выглядеть сканирование snyk для npm:

$ npm install -g snyk$ snyk auth$ snyk monitor

Sonatype Open-source

Помимо NVD (основного источника информации об уязвимостях для большинства решений) существует база Sonatype OSS, поддерживаемая компанией Sonatype, у которой также есть коммерческое решение Nexus IQ. Мы, в свою очередь, взяли на вооружение Nexus IQ как основной инструмент SCA для наших заказчиков. Sonatype OSS - база данных уязвимостей, которая может быть подключена инструментами Dependency Check и Dependency Track. Кроме того, Sonatype поддерживает следующие open-source инструменты SCA, которые могут быть использованы для сканирования зависимостей и берут данные из Sonatype OSS:

Скриншот из отчета Nexus Vulnerability ScannerСкриншот из отчета Nexus Vulnerability Scanner

Другие материалы по SCA:

Поиск секретов

Проект может содержать не только уязвимости, которые были допущены в собственном коде и open-source компонентах, но секреты вроде паролей, токенов, закрытых ключей. Разумеется, если в коде содержится открытый пароль от критически важной базы данных, допускать его в репозиторий нежелательно. С этой целью существует ряд инструментов, которые могут помочь отыскать открытые секреты. Здесь мы не будем долго останавливаться, ибо принцип работы везде одинаковый.

git-secrets

Ссылка на официальном сайте git-secrets

$ git secrets --scan /path/to/file

Gitrob

Ссылка на gitrob

$ export GITROB_ACCESS_TOKEN=&lt;TOKEN>$ gitrob &lt;target>

Gitleaks

Ссылка на gitleaks

$ gitleaks --repo-path=&lt;path to repo>$ gitleaks --repo=&lt;url of github>

Также есть в виде Github-action.

TruffleHog

Ссылка на TruffleHog

$ trufflehog [-h] [--json] [--regex] [--rules RULES]                  [--entropy DO_ENTROPY] [--since_commit SINCE_COMMIT]                  [--max_depth MAX_DEPTH]                  git_url

TruffleHog - самый известный инструмент в сообществе, осуществляющий поиск секретов как по регулярным выражениям, так и прибегая к оценки энтропии методом Шеннона.

GitGuardian

Ссылка на GitGuardian

SaaS платформа для поиска секретов. Есть также коммерческая on-prem версия. В России не продается.

Скриншот из GitGuardianСкриншот из GitGuardian

Примечание. Для хранения секретов и сокрытия их в исходном коде и конфигурационных файлах необходимо использовать решения класса Password Vault (HashiCorp Vault, conjur, )

Динамический анализ приложений на уязвимости (DAST):

Arachni

Ссылка на Arachni

Одно из моих любимых open-source решений, в первую очередь за свою точность. Есть возможность развертывания в виде Docker-контейнера, CLI и веб-интерфейса. Жаль, что решение перестало поддерживаться разработчиками. Результаты выводятся в CWE-формате.

Скриншот из веб-интерфейса ArachniСкриншот из веб-интерфейса Arachni

Способ сканирования через Docker:

$ docker run -d \    -p 222:22 \    -p 7331:7331 \    -p 9292:9292 \    --name arachni \    arachni/arachni:latest

После развертывания контейнера, запуск сканирования и выгрузка отчетов происходит через REST API по порту 7331 в виде json.

OWASP ZAP

Ссылка на OWASP ZAP

Одно из самых популярных open-source решений, которое может быть встроено в CI/CD. Имеет свой GUI, может быть развернуто в виде CLI или docker-контейнера. Также есть режим работы в виде прокси.

# OWASP ZAP as a daemondocker run -p 8090:8090 -i owasp/zap2docker-stable zap.sh -daemon -port 8090 -host 0.0.0.0# OWASP ZAP runs for 1  minute and then waits for the passive scanning to complete before reporting the results.docker run -t owasp/zap2docker-weekly zap-baseline.py -t https://www.example.com
Скриншот из GUI OWASP ZAPСкриншот из GUI OWASP ZAP

Есть даже кастомные сценарии развертывания в виде Kubernetes-оператора.

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

w3af

Ссылка на w3af

Несмотря на то, что инструмент давно не обновляется и не обозревается аналитиками (например, наиболее свежий обзор в журнале Хакер датируется 2012 годом (http://personeltest.ru/aways/xakep.ru/2012/11/09/w3af-pentest/)), тем не менее есть официальный docker-контейнер и инструкции по встраиванию сканера в CI/CD

Вот например, автор встроил разные open-source решения по безопасности в пайплайн Jenkins на AWS, включая w3af.

Пример сканирования через docker:

$ git clone https://github.com/andresriancho/w3af.git$ cd w3af/extras/docker/scripts/$ sudo ./w3af_console_docker
Скриншот w3af. Источник: https://xakep.ru/2012/11/09/w3af-pentest/Скриншот w3af. Источник: https://xakep.ru/2012/11/09/w3af-pentest/

Примечание. Динамический анализ бывает не только для веб-приложений. Более полный список можно увидеть здесь.

Стоит отметить про отдельный класс решений для динамического сканирования мобильных приложений, open-source инструментов и enterprise, которых весьма мало. Мы в частности занимаемся разработкой собственного инструмента DAST для мобильных приложений.

Тестирование по принципам Behaviour Driven Development

Behavioral Driven Development (BDD) (или разработка через поведение)- подход в разработке ПО, который произошел от Test Driven Development (TDD) (разработка через тестирование). Несмотря на том, что эти подходы, как итог, стали применяться для разных целей и использовать разные инструменты, их применение можно найти и в сфере безопасности. Основная концепция BDD в описании пользовательских сценариев тестирования с помощью человеко-читаемоего языка Gherkin.

Посмотрим, как это выглядит сразу на примере инструментов.

Gauntlt

Ссылка на Gauntlt

Guantlt - фреймворк, использующий концепцию Behavioral Driven Development. Он автоматизировать сканирование с помощью различных инструментов и позволяет описать Arachni, nmap, sslyze, sqlmap и другие инструменты на языке Gherkin.

# nmap-simple.attackFeature: simple nmap attack to check for open ports  Background:    Given "nmap" is installed    And the following profile:      | name     | value       |      | hostname | example.com |  Scenario: Check standard web ports    When I launch an "nmap" attack with:      """      nmap -F <hostname>      """    Then the output should match /80.tcp\s+open/    Then the output should not match:      """      25\/tcp\s+open      """

Таким образом, Guantlt может стать мостиком между командами разработки, безопасности и менеджмента.

Примечание. Аналогом Guantlt является BDD-Security, который в поддерживаемых инструментах имеет также OWASP ZAP, Tenable Nessus Scanner.

Сканирование образов Docker:

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

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

Пройдемся по некоторым из инструментов.

Clair

Ссылка на Clair

Инструмент для проверки слоев образа на общедоступные CVE уязвимости. У инструмента отсутствует из коробки UI для работы, поэтому необходимо подключать сторонние инструменты вроде Klar.

$ docker run -d -e POSTGRES_PASSWORD="" -p 5432:5432 postgres:9.6$ docker run --net=host -d -p 6060-6061:6060-6061 -v $PWD/clair_config:/config quay.io/coreos/clair:latest -config=/config/config.yaml

Klar

Ссылка на Klar

Утилита для взаимодействия с API Clair.

$ mkdir klar &amp;&amp; cd klar &amp;&amp; wget https://github.com/optiopay/klar/releases/download/v2.4.0/klar-2.4.0-linux-amd64 -O klar &amp;&amp; chmod +x klar$ CLAIR_ADDR=http: //localhost:6060 CLAIR_THRESHOLD=10 ./klar &lt;docker image>

Trivy

Ссылка на Trivy

Trivy находит уязвимости сборок ОС (поддерживаются Alpine, RedHat (EL), CentOS, Debian GNU, Ubuntu) и проблемы в зависимостях (Gemfile.lock, Pipfile.lock, composer.lock, package-lock.json, yarn.lock, Cargo.lock) В отличие от Clair умеет сканировать как в репозитории, так и локально, или на основании переданного .tar файла с Docker образом.

# Download bin$ wget https: //github.com/knqyf263/trivy/releases/download/v0.1.3/trivy_0.1.3_Linux-64bit.deb$ dpkg -i ./trivy_0. 1 .3_Linux-64bit.deb# Scan image$ trivy bkimminich/juice-shop# Scan image in tar$ trivy -i ./ my_saved_docker_image.tar
Результат сканирования TrivyРезультат сканирования Trivy

Anchore

Ссылка на Anchore

Популярный инструмент для сканирования образов Docker. Есть возможность работы через REST API или CLI.

$ anchore-cli --u admin --p foobar image add httpd:latest$ anchore-cli --u admin --p foobar image vuln httpd:latest all
Результат сканирования Anchore. Источник: https://swordfishsecurity.ru/blog/obzor-utilit-bezopasnosti-dockerРезультат сканирования Anchore. Источник: https://swordfishsecurity.ru/blog/obzor-utilit-bezopasnosti-docker

AquaMicroscanner

Ссылка на AquaMicroscanner

Инструмент от Aqua Security, который развивается параллельно вместе с Trivy.

$ docker run --rm -it aquasec/microscanner --register &lt;email address>
ADD https://get.aquasec.com/microscanner /RUN chmod +x /microscannerRUN /microscanner &lt;TOKEN> [--continue-on-failure]

Примечание. Сравнение инструментов по сканированию образов на общедоступные CVE можно почитать здесь:

Dagda

Ссылка на Dagda

Dagda выделяется тем, что имеет под капотом Dependency Check, Retire.js и ClamAV для поиска вредоносных программ.

$ export DAGDA_HOST='127.0.0.1'$ export DAGDA_PORT=5000$ python3 dagda.py vuln --init$ python3 dagda.py check --docker_image jboss/wildfly

Docker bench

Ссылка на Docker Bench

Docker bench - инструмент для compliance-проверок как образов, так и контейнеров и хостов.
Основной набор проверок строится на базе документа CIS Benchmarks для Docker.

$ docker run -it --net host --pid host --userns host --cap-add audit_control \      -e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \      -v /etc:/etc:ro \      -v /usr/bin/containerd:/usr/bin/containerd:ro \      -v /usr/bin/runc:/usr/bin/runc:ro \      -v /usr/lib/systemd:/usr/lib/systemd:ro \      -v /var/lib:/var/lib:ro \      -v /var/run/docker.sock:/var/run/docker.sock:ro \      --label docker_bench_security \      docker/docker-bench-security
Результат сканирования Docker benchРезультат сканирования Docker bench

Dockle

Ссылка на Dockle

Инструмент для выполнения compliance-проверок, в том числе, выходящих за рамки CIS.

$ docker run --rm goodwithtech/dockle:v${DOCKLE_LATEST} [YOUR_IMAGE_NAME]
Результат работы DockleРезультат работы Dockle

Ops:

Kubernetes Security

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

Дополнительный материал для расширения подборки:

My Arsenal of Cloud Native (Security) Tools by MARCO LANCINI

Kube-bench

Ссылка на Kube-bench

Еще один (и не последний) инструмент от компании Aqua Security. Инструмент выполняет проверки CIS Kubernetes Benchmark для развернутых рабочих нагрузок Kubernetes (в том числе GKE, EKS and AKS)

Для разных сценариев желателен разный вариант запуска команд:

# Run inside a container$ docker run --rm --pid=host                      \   -v $(which kubectl):/usr/bin/kubectl         \   -t aquasec/kube-bench:latest <master|node># Run in a cluster - on master node$ kubectl run                                          \      --rm                                             \      -it                                              \      kube-bench-master                                \      --image=aquasec/kube-bench:latest                \      --restart=Never                                  \      --overrides="{ \"apiVersion\": \"v1\",           \          \"spec\": { \"hostPID\": true,               \          \"nodeSelector\":                            \          { \"kubernetes.io/role\": \"master\" },      \          \"tolerations\": [ {                         \          \"key\": \"node-role.kubernetes.io/master\", \          \"operator\": \"Exists\",                    \          \"effect\": \"NoSchedule\" }]}}"             \      -- master                                        \      --version 1.8# Run in a cluster - on worker nodes$ kubectl run                                \      --rm                                   \      -it                                    \      kube-bench-node                        \      --image=aquasec/kube-bench:latest      \      --restart=Never                        \      --overrides="{ \"apiVersion\": \"v1\", \          \"spec\": { \"hostPID\": true } }" \      -- node                                \      --version 1.8

Kubernetes Auto Analyzer

Ссылка на Kubernetes Auto Analyzer

Инструмент работает по тому же принципу, что и Kube-bench, но в отличие от него перестал поддерживаться. Сами авторы инструмента предлагают продолжить пользоваться Kube-bench от Aqua Security.

# Put the config file in a directory and mount it to the /data folder$ docker run --rm                               \      -v /data:/data raesene/kube_auto_analyzer \      -c /data/admin.conf -r testdock# Provide a KUBECONFIG file to identify and authenticate the session$ kubeautoanalyzer -c <kubeconfig_file_name> -r <report_name> --html
Пример отчета Kuberntes-Auto-AnalyzerПример отчета Kuberntes-Auto-Analyzer

Kube-hunter

Ссылка на Kube-hunter

Инструмент для имитации тестирования на проникновения Kubernetes. Может быть запущен на сервере кластера, внутри контейнера или на удаленном компьютере с указанием IP-адреса / доменного имени кластера Kubernetes.

# Run from docker$ docker run -it --rm --network host aquasec/kube-hunter# Run from kubectl$ kubectl run --rm -it                        \     kube-hunter                            \     --image=aquasec/kube-hunter            \     --restart=Never                        \     --overrides="{ \"apiVersion\": \"v1\", \     \"spec\": { \"hostNetwork\": true } }"

KubiScan

Ссылка на KubiScan

Инструмент для проверки выданных разрешений RBAC-модели Kubernetes.

Кстати, проверке выданных разрешений посвящена отдельная неплохая статья на сайте CyberARK.

# Run from MASTER node$ docker run -it --rm -e CONF_PATH=~/.kube/config -v /:/tmp cyberark/kubiscan [CMD]# Search for pods with privileged accounts$ kubiscan -rp# Show all risky subjects (users, service accounts, groups)$ kubiscan -rs# Show all the rules a service account has$ kubiscan -aars "SANAME" -ns "default" -k "ServiceAccount"# List service account RoleBindings$ kubiscan -aarbs "SANAME" -ns "default" -k "ServiceAccount"

Krane

Ссылка на Krane

Инструмент, выполняющий статический анализ RBAC за счет индексации объектов RBAC в RedisGraph. Управление рисками RBAC происходит через настройку политик. Krane может работать как CLI, docker-контейнер или автономная служба для непрерывного анализа, а также быть встроенным в CI/CD.

Пример графа Krane.Пример графа Krane.

Statboard

Ссылка на Starboard

Инструмент, позволяющий нативно интегрировать инструменты безопасности в среду Kubernetes благодаря CustomResourceDefinitions (CRDs) для работы с такими инструментами как trivy, kube-bench, kube-hunter. Starboard предоставляет также kubectl-совместимый инструмент командной строки и плагин Octant, который делает отчеты о безопасности доступными через знакомые инструменты Kubernetes.

$ starboard find vulnerabilities deployment/nginx --namespace dev$ starboard get vulnerabilities deployment/nginx \  --namespace dev \  --output yaml
Результат работы Starboard через OctantРезультат работы Starboard через Octant

Kubeaudit

Ссылка на Kubeaudit

Еще один инструмент, выполняющий проверки Kubernetes.

# Run from kubectl (as plugin)$ kubectl audit all

Kubesec

Ссылка на Kubesec

Последний легковесный инструмент для проверки Kubernetes в этой подборке.

$ krew install kubesec-scan$ kubectl kubesec-scan pod <podname>

Deepfence Runtime Threat Mapper

Ссылка на Deepfence Runtime Threat Mapper

Бесплатная community-версия комплексного решения по защите облачных нагрузок. Платформа отображает рабочие нагрузки на графе, ищет аномалии в поведении с помощью агентов сканирования, интегрируется с CI/CD для сканирования образов, а также выполняет поиск уязвимостей образов в заданном Registry. Также есть интеграция с SIEM, Slack, Jira, Amazon S3 (неполный список интеграций).

Скриншот из Deepfence Runtime Threat MapperСкриншот из Deepfence Runtime Threat Mapper

Sysdig Falco

Ссылка на Sysdig Falco

Бесплатная версия решения для защиты в режиме run-time от Sysdig, хорошо себя зарекомендовавшая в сообществе.

Vulnerability Management

Можно выполнять огромное количество различных сканирований инструментами SAST, DAST, SCA, анализаторами образов Docker и конфигурации Kubernetes, но без правильно построенного процесса управления выявленными уязвимостями и распределения ответственных процесс устранения выявленных дефектов может очень сильно затянуться. Решения класса Vulnerability Management призваны помочь в этом вопросе. Как правило, это единая точка входа всех выявленных уязвимостей посредством взаимодействия через API или при помощи веб-интерфейса с целью дальнейшей визуализации и экспорта структурированной информации в дефект-трекинг. Мы в своих практиках используем коммерческое решение собственной разработки AppSec.Hub, которое помимо управления уязвимостями, умеет также создавать и экспортировать готовые DevSecOps-пайплайны в CI/CD системы. Но в этой статье мы коснемся только open-source решений.

DefectDojo

Ссылка на DefectDojo

Решение для управление уязвимостями от OWASP. Есть много интеграций (22+) как с open-source сканерами (ZAP, Trivy, nmap, Dependency Check), так и с enterprise (Veracode, Checkmarx, Twistlock). Как правило, имеет некоторые сложности при интеграции с API.

Вот здесь можно увидеть пример скрипта для интеграции.

Скриншот DefectDojo.Скриншот DefectDojo.

Secure code Box

Ссылка на Secure code box

Open-source фреймворк, объединяющий несколько бесплатных инструментов сканирования (ZAP, NMAP, Nikto, Arachni), собранных вместе в docker-compose в связке с Kibana и Elasticsearch. В отличие от того же DefectDojo, здесь все инструменты развертываются вместе с решением, и отчеты о результатах сканирования подтягиваются самостоятельно (не нужно писать скрипты для автоматической отправки issue в сборщик). Также можно запускать сканирование всех заявленных инструментов из UI. На текущий момент инструменты направлены исключительно на тестирование веб-приложений.

Несмотря на кажущуюся простоту развертывания и работы, разработчики заявляют, что это не one-button-click-solution и требуется глубокое понимание для настройки сканеров.

Скрншот графиков Kibana из Secure Code Box.Скрншот графиков Kibana из Secure Code Box.

Archery

Ссылка на Archery

Еще одна open-source система управления уязвимостями. Есть поддержка Acuntetix, Nessus, Burp, Netsparker, WebInspect. В отличие от DefectDojo, о котором я упоминал ранее, решение позволяет запускать из консоли сканирование ZAP, Burp и OpenVAS. Из интересного то, что есть обработчик false positive. Ну и конечно же интеграция с CI/CD.

Скриншот из ArcheryСкриншот из Archery

Еще материал по vulnerability management:

Public Cloud Security

Говоря про безопасный DevOps нельзя не сказать про безопасность облачных провайдеров (AWS, GCP, Azure, Oracle) в силу активного перехода из on-prem в облака.

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

Сервисы безопасности от AWS. Источник :https://cloudseclist.com/issues/issue-42/Сервисы безопасности от AWS. Источник :https://cloudseclist.com/issues/issue-42/

AWS-inventor

Ссылка на AWS-inventor

Инструмент для инвентаризации активов AWS с помощью заданных параметров учетной записи AWS.

$ python aws_inventory.py# Select the generated JSON file when prompted$ firefox gui/dist/index.html
Результат инвентаризации AWS-inventorРезультат инвентаризации AWS-inventor

Aws-public-ips

Ссылка на AWS-puclic-ips

Инструмент для получении информации об общедоступных ресурсах AWS в виде ip-адресов.

# Uses default ~/.aws/credentials$ aws_public_ips -f json -s apigateway,cloudfront,ec2, \    elasticsearch,elb,elbv2,lightsail,rds,redshift# With a custom profile$ AWS_PROFILE=production aws_public_ips ...

CloudSploit

Ссылка на CloudSploit

Инструмент для compliance-проверок публичных облаков AWS, GCP, Azure, OCI. В частности, можно провести проверку на CIS и PCI DSS.

# Edit the&nbsp;index.js&nbsp;file with your AWS key and secret# Run a standard scan$ node index.js# Run a compliance scan$ node index.js --compliance=hipaa

AWS Security Benchmark

Ссылка на AWS Security Benchmark

Отдельный инструмент для проверки AWS на соответствие CIS Amazon Web Services Foundations Benchmark 1.1.

$ python aws-cis-foundation-benchmark-checklist.py

S3 Scan

Ссылка на S3 Scan

Инструмент, формирующий отчет обо всех S3-корзинах и установленных для них ролях.

$ python s3scan.py [-f &lt;format>] [-p &lt;profile>]

Примечание. Наверное, самая большая подборка инструментов по AWS Security:

My-arsenal-of-aws-security-tools

G-Scout

Ссылка на G-Scout

Инструмент, формирующий отчет о проблемах с безопасностью GCP.

# Permissions required on the projects: Viewer, Security Reviewer, Stackdriver Account Viewer$ python gscout.py --project-id <projectID>

ScoutSuite

Ссылка для ScoutSuite

Популярный инструмент для проведения аудита безопасности публичных облаков GCP, AWS, Oracle, Azure.

# GCP example# Using an user account$ python Scout.py --provider gcp --user-account --project-id <projectID>#Using a service account$ python Scout.py --provider gcp                                     \                  --service-account --key-file service_account.json  \                  --project-id <projectID>
Пример отчета ScoutSuiteПример отчета ScoutSuite

И это еще не все?

Подборку продолжать можно еще долго, но я постарался привести все, что может стать отправной точкой в процессе выстраивания безопасности DevOps/SDLC. Тем не менее, это далеко не все практики. Я не коснулся также этапов фаззинга, управления секретами и процесса проверки манифестов IaC. И разумеется, правильно построенный процесс разработки не может существовать без организационных мер и налаженных отношений между командами разработки и безопасности. Здесь я могу порекомендовать познакомиться с моделями оценки BSIMM и OWASP SAMM.

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

RASP - практика, при которой инструмент безопасности интегрируется напрямую с сервером, отслеживая его работу (обращения к базе данных, файловые операции, сетевые запросы и тд.). Часто к этой практике относят инструменты класса Container Run-time Security (например Sysdig Falco). Вот также RASP для отслеживания работы веб-приложений:

IAST - практика, совмещающая принципы работы SAST и DAST:

Fuzzing - практика тестирования приложения, при которой на вход программе подаются данные, которые могут привести к неопределенному поведению :

IaC Security - практика тестирования декларативного описания инфраструктуры через конфигурационные файлы на соответствие требования безопасности:

  • Cfn Nag-Сканер AWS CloudFormation шаблонов на небезопасную конфигурацию

  • Checkov-Сканер Terraform, AWS CloudFormation и Kubernetes шаблонов на небезопасную конфигурацию

  • Terrascan-Сканер Terraform шаблонов на соответствие лучшим практикам безопасности

  • Tfsec-Сканер Terraform шаблонов на неправильную конфигурацию и несоответствие лучшим практикам безопасности AWS, Azure и GCP.

  • Kubernetes YAML validating:

Compliance-as-code - практика представления требований безопасности через декларативное описание в виде кода с целью дальнейшей непрерывной оценки на соответствие:

Security Chaos Engineering - молодая практика, основанная на нескольких принципах: определение "стабильного" состояния, создание гипотезы о последующем состоянии, введение переменных, осуществление попыток "сломать" гипотезу. После серии тестов инфраструктура оценивается на предмет доступности, безопасности и производительности. Как итог цель всего этого - получить систему, способную выдерживать экстремальные условия. Security Chaos Engineering отличается от классического Chaos Engineering лишь гипотезами, касающимися безопасности. Тесты могут состоять, например, из отключения правил Security Groups, изменения файлов случайным образом, прослушивания портов, внедрения вредоносного трафика в VPC, случайного "убийства" процессов. При этом все это должно быть автоматизированно, а состояния системы должны непрерывно отслеживаться. Примеры инструментов:

Каналы и чаты по DevSecOps и безопасности приложений:

Подробнее..

Строим безопасную разработку в ритейлере. Опыт одного большого проекта

17.09.2020 10:15:44 | Автор: admin
Некоторое время назад мы закончили строить процесс безопасной разработки на базе нашего анализатора кода приложений в одной из крупнейших российских ритейловых компаний. Не скроем, этот опыт был трудным, долгим и дал мощнейший рывок для развития как самого инструмента, так и компетенций нашей команды разработки по реализации таких проектов. Хотим поделиться с вами этим опытом в серии статей о том, как это происходило на практике, на какие грабли мы наступали, как выходили из положения, что это дало заказчику и нам на выходе. В общем, расскажем о самом мясе внедрения. Сегодня речь пойдет о безопасной разработке порталов и мобильных приложений ритейлера.


Для начала в целом про проект. Мы выстроили процесс безопасной разработки в крупной торговой компании, в которой ИТ-подразделение имеет огромный штат сотрудников и разделено на множество направлений, минимально коррелирующих между собой. Условно эти направления можно разделить на 3 основные группы. Первая, очень большая группа, это кассовое ПО, которое написано преимущественно на языке Java (90% проектов). Вторая, самая обширная с точки зрения объема кода группа систем это SAP-приложения. И наконец, третий блок представлял собой сборную солянку из порталов и мобильных приложений: разного рода внешние сайты для клиентов компании, мобильные приложения к этим сайтам, а также внутренние ресурсы мобильные приложения и веб-порталы для персонала ритейлера.

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

Этот подход мы сформулировали максимально просто: сканируется наиболее актуальный для всех разработчиков код. Если говорить в терминах Gitflow, а все группы проектов, за исключением SAP, вели ветки разработки в Gitflow, сканируется основная ветка разработки по расписанию.

Но, как всегда, из любого правила бывают исключения: общий подход не везде мог быть применен as is по ряду причин. Во-первых, наш инструмент (анализатор кода) имеет несколько ограничений, обусловленных тем, что мы хотим иметь возможность при необходимости делать наиболее глубокий анализ некоторых языков программирования. Так, в случае с Java анализ по байткоду гораздо более глубокий, чем по исходному коду. Соответственно, для сканирования Java-проектов требовалась предварительная сборка байткода и лишь затем его отправка на анализ. В случае с C++, Objective C и приложениями для iOS анализатор встраивался в процесс на этапе сборки. Также мы должны были учесть различные индивидуальные требования со стороны разработчиков всех проектов. Ниже расскажем, как мы выстроили процесс для порталов и мобильных приложений.

Порталы и мобильные приложения


Вроде бы все эти приложения объединены в одну логическую группу, но на самом деле в них была жуткая каша. Одних порталов было больше 120-ти (!). Компания очень большая, со множеством бизнес, административных и технических подразделений, и периодически каждое из них решает, что ему нужен свой портал и мобильное приложение. Этот портал и приложение создаются, ими какое-то время пользуются, а потом благополучно забрасывают. В результате на начальном этапе нам пришлось провести для заказчика инвентаризацию, поскольку даже разработчики этих приложений не имели единого списка кодовых баз. Например, для управления репозиториями в этой группе разработчики использовали два GitLab, администраторами которых являлись разные люди. Кроме того, среди порталов и мобильных приложений существенная часть проектов была реализована с помощью внешней разработки. Поэтому, когда подходило время релиза, зачастую подрядчики передавали компании исходные коды новой версии чуть ли не на флешке. В итоге компания имела зоопарк различных приложений и полный беспорядок в их коде. Нам пришлось составить список всех проектов, найти всех ответственных за них техвладельцев, тимлидов, а затем согласовать с основным заказчиком департаментом ИБ, какие из них мы будем анализировать.

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

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

Интеграция по стандартной схеме


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


Настройка интеграции с GitLab

Далеко не во всех приложениях применялась CI/CD, и там, где ее не было, нам приходилось настаивать на ее применении. Потому что если вы хотите по-настоящему автоматизировать процесс проверки кода на уязвимости (а не просто в ручном режиме загружать ссылку на анализ), чтобы система сама скачивала ее в репозиторий и сама выдавала результаты нужным специалистам, то без установки раннеров вам не обойтись. Раннеры в данном случае это агенты, которые в автоматическом режиме связываются с системами контроля версий, скачивают исходный код и отправляют в Solar appScreener на анализ.

Разработчики группы порталов и мобильных приложений хотели организовать безопасную разработку как полуавтоматизированный процесс, чтобы код сканировался на уязвимости без какого-либо участия с их стороны. Чтобы офицер безопасности верифицировал результаты анализа на уязвимости и ставил разработчикам задачи в Jira, если считал уязвимости критичными, или же отправлял их разработчикам для получения разъяснений. Разработчики решали бы, нужно ли срочно исправлять уязвимость или нет. И если нужно, планировали бы, в какой релиз смогут включить исправления.

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


Настройка интеграции с Jira

В редких случаях тим-лиды сами смотрели результаты сканирования и заводили задачи в Jira вручную.


Создание задачи в Jira

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

В итоге процесс безопасной разработки после внедрения Solar appScreener стал выглядеть так: порталы ежедневно проверяются на наличие изменений в коде основной ветки разработки. Если основная, наиболее актуальная ветка не обновлялась в течение суток, то ничего не происходит. Если же она обновилась, то запускается отправка этой ветки на анализ в соответствующий проект для этого репозитория. Репозиторию в GitLab соответствовал определенный проект в анализаторе кода, и именно в нем сканировалась основная ветка. После этого офицер безопасности просматривал результаты анализа, верифицировал их и заводил задачи на исправления в Jira.


Результаты анализа и созданные в Jira задачи на исправление уязвимостей

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

Нестандартное в стандартном


В этом, на первый взгляд, не таком уж и сложном процессе имелось два серьезных ограничения. Во-первых, для анализа Android-приложений (то есть написанных на Java) нам нужна была сборка. А во-вторых, для iOS нужны были машины с MacOS, на которых устанавливался бы наш агент и имелась бы среда, которая позволяла бы собирать приложения. С Android-приложениями мы разобрались довольно просто: написали свои части в уже имеющиеся у разработчиков скрипты, которые запускались так же по расписанию. Наши части скриптов предварительно запускали сборку проекта в наиболее широкой конфигурации, которая направлялась в Solar appScreener на анализ. Для проверки же iOS-приложений мы устанавливали на Mac-машину наш MacOS-агент, который производил сборку кода и так же через GitLab CI отправлял код в анализатор на сканирование. Далее, как и в случае с другими видами ПО, офицер безопасности просматривал результаты анализа, верифицировал их и заводил задачи на исправления в Jira.

К порталам и мобильным приложениям мы также относили любые проекты, написанные на Java, их мы собирали и анализировали по аналогичной схеме.

В тех проектах, где не было CI/CD, что было обязательным для нас условием, мы просто говорили: Ребята, хотите анализировать собирайте в ручном режиме и загружайте в сканер сами. Если у вас нет Java или JVM-подобных языков Scala, Kotlin и прочих, то можете просто загружать код в репозиторий по ссылке, и все будет хорошо.

Сложности проекта


Как видно из вышесказанного, в этом стеке приложений основной проблемой было отсутствие во многих проектах CI/CD. Разработчики часто делали сборки вручную. Мы начали интеграцию нашего анализатора с порталов Sharepoint на языке C#. Сейчас C# более-менее перешел на Linux-системы, хотя и не совсем полноценные. А когда проект был в самом разгаре, этот язык еще работал на Windows, и нам приходилось ставить агент на Windows для GitLab. Это было настоящим испытанием, поскольку наши специалисты привыкли использовать Linux-команды. Необходимы были особенные решения, например, в каких-то случаях нужно было указывать полный путь до exe-файла, в каких-то нет, что-то надо было заэкранировать и т.п. И уже после реализации интеграции c Sharepoint команда проекта мобильного приложения на PHP сказала, что у них тоже нет раннера и они хотят использовать C#-овский. Пришлось повторять операции и для них.

Резюме


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

  • внедряемое нами решение достаточно взрослое, чтобы проявлять нужную гибкость для построения процессов DevSecOps в кардинально разных средах внедрения. Гибкость достигается за счёт большого набора встроенных и кастомных интеграций, без которых трудозатраты на внедрение возросли бы в разы или сделали бы его невозможным;
  • настройка нужной автоматизации и последующий разбор результатов не требуют необъятного количества трудозатрат даже при огромном скоупе работ. Согласование и построение внедряемых процессов и полная их автоматизация возможны усилиями небольшой экспертной группы из 3-4 человек;
  • внедрение средств автоматической проверки кода и практик DevSecOps позволяет выявить недостатки текущих процессов DevOps и становится поводом для их настройки, улучшения, унификации и регламентирования. В конечном счёте получается win-win ситуация для всех участников процесса от рядовых разработчиков до топ-менеджеров отделов инженерии и ИБ.

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

А был ли у вас свой опыт реализации подобных проектов? Будем рады, если вы поделитесь с нами своими кейсами внедрения практик безопасной разработки в комментариях!

Автор: Иван Старосельский, руководитель отдела эксплуатации и автоматизации информационных систем
Подробнее..

Из песочницы Способы и примеры внедрения утилит для проверки безопасности Docker

21.10.2020 18:15:40 | Автор: admin

Привет, Хабр!

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

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

Утилиты проверки безопасности


Существует большое количество различных вспомогательных приложений и скриптов, которые выполняют проверки разнообразных аспектов Docker-инфраструктуры. Часть из них уже была описана в предыдущей статье (http://personeltest.ru/aways/habr.com/ru/company/swordfish_security/blog/518758/#docker-security), а в данном материале я бы хотел остановиться на трех из них, которые покрывают основную часть требований к безопасности Docker-образов, строящихся в процессе разработки. Помимо этого я также покажу пример как можно эти три утилиты соединить в один pipeline для осуществления проверок безопасности.

Hadolint
https://github.com/hadolint/hadolint

Довольно простая консольная утилита, которая помогает в первом приближении оценить корректность и безопасность инструкций Dockerfile-ов (например использование только разрешенных реестров образов или использование sudo).

Вывод утилиты Hadolint

Dockle
https://github.com/goodwithtech/dockle

Консольная утилита, работающая с образом (или с сохраненным tar-архивом образа), которая проверяет корректность и безопасность конкретного образа как такового, анализируя его слои и конфигурацию какие пользователи созданы, какие инструкции используются, какие тома подключены, присутствие пустого пароля и т. д. Пока количество проверок не очень большое и базируется на нескольких собственных проверках и рекомендациях CIS (Center for Internet Security) Benchmark для Docker.


Trivy
https://github.com/aquasecurity/trivy

Эта утилита нацелена на нахождение уязвимостей двух типов проблемы сборок ОС (поддерживаются Alpine, RedHat (EL), CentOS, Debian GNU, Ubuntu) и проблемы в зависимостях (Gemfile.lock, Pipfile.lock, composer.lock, package-lock.json, yarn.lock, Cargo.lock). Trivy умеет сканировать как образ в репозитории, так и локальный образ, а также проводить сканирование на основании переданного .tar файла с Docker-образом.



Варианты внедрения утилит


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

Основная идея состоит в том, чтобы продемонстрировать, как можно внедрить автоматическую проверку содержимого Dockerfile и Docker-образов, которые создаются в процессе разработки.

Сама проверка состоит из следующих шагов:
  1. Проверка корректности и безопасности инструкций Dockerfile утилитой линтером Hadolint
  2. Проверка корректности и безопасности конечного и промежуточных образов утилитой Dockle
  3. Проверка наличия общеизвестных уязвимостей (CVE) в базовом образе и ряде зависимостей утилитой Trivy

Дальше в статье я приведу три варианта внедрения этих шагов:
Первый путём конфигурации CI/CD pipeline на примере GitLab (с описанием процесса поднятия тестового инстанса).
Второй с использованием shell-скрипта.
Третий с построением Docker-образа для сканирования Docker-образов.
Вы можете выбрать вариант который больше вам подходит, перенести его на свою инфраструктуру и адаптировать под свои нужды.

Все необходимые файлы и дополнительные инструкции также находятся в репозитории: https://github.com/Swordfish-Security/docker_cicd

Интеграция в GitLab CI/CD


В первом варианте мы рассмотрим, как можно внедрить проверки безопасности на примере системы репозиториев GitLab. Здесь мы пройдем по шагам и разберем как установить с нуля тестовое окружение с GitLab, составить процесс сканирования и осуществить запуск утилит для проверки тестового Dockerfile и случайного образа приложения JuiceShop.

Установка GitLab
1. Ставим Docker:
sudo apt-get update && sudo apt-get install docker.io

2. Добавляем текущего пользователя в группу docker, чтобы можно было работать с докером не через sudo:
sudo addgroup <username> docker

3. Находим свой IP:
ip addr

4. Ставим и запускаем GitLab в контейнере, заменяя IP адрес в hostname на свой:
docker run --detach \--hostname 192.168.1.112 \--publish 443:443 --publish 80:80 \--name gitlab \--restart always \--volume /srv/gitlab/config:/etc/gitlab \--volume /srv/gitlab/logs:/var/log/gitlab \--volume /srv/gitlab/data:/var/opt/gitlab \gitlab/gitlab-ce:latest

Ждём, пока GitLab выполнит все необходимые процедуры по установке (можно следить за процессом через вывод лог-файла: docker logs -f gitlab).

5. Открываем в браузере свой локальный IP и видим страницу с предложением поменять пароль для пользователя root:

Задаём новый пароль и заходим в GitLab.

6. Создаём новый проект, например cicd-test и инициализируем его стартовым файлом README.md:

7. Теперь нам необходимо установить GitLab Runner: агента, который будет по запросу запускать все необходимые операции.
Скачиваем последнюю версию (в данном случае под Linux 64-bit):
sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64

8. Делаем его исполняемым:
sudo chmod +x /usr/local/bin/gitlab-runner

9. Добавляем пользователя ОС для Runner-а и запускаем сервис:
sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bashsudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runnersudo gitlab-runner start

Должно получиться примерно так:

local@osboxes:~$ sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runnerRuntime platform arch=amd64 os=linux pid=8438 revision=0e5417a3 version=12.0.1local@osboxes:~$ sudo gitlab-runner startRuntime platform arch=amd64 os=linux pid=8518 revision=0e5417a3 version=12.0.1

10. Теперь регистрируем Runner, чтобы он мог взаимодействовать с нашим инстансом GitLab.
Для этого открываем страницу Settings-CI/CD (http://personeltest.ru/away/OUR_ IP_ADDRESS/root/cicd-test/-/settings/ci_cd) и на вкладке Runners находим URL и Registration token:

11. Регистрируем Runner, подставляя URL и Registration token:
sudo gitlab-runner register \--non-interactive \--url "http://<URL>/" \--registration-token "<Registration Token>" \--executor "docker" \--docker-privileged \--docker-image alpine:latest \--description "docker-runner" \--tag-list "docker,privileged" \--run-untagged="true" \--locked="false" \--access-level="not_protected"

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

Конфигурация pipeline

1. Добавим в репозиторий файлы mydockerfile.df (это некий тестовый Dockerfile, который мы будем проверять) и конфигурационный файл GitLab CI/CD процесса .gitlab-cicd.yml, который перечисляет инструкции для сканеров (обратите внимание на точку в названии файла).

YAML-файл конфигурации содержит инструкции по запуску трех утилит (Hadolint, Dockle и Trivy), которые проанализируют выбранный Dockerfile и образ, заданный в переменной DOCKERFILE. Все необходимые файлы можно взять из репозитория: https://github.com/Swordfish-Security/docker_cicd/

Выдержка из mydockerfile.df (это абстрактный файл с набором произвольных инструкций только для демонстрации работы утилиты). Прямая ссылка на файл: mydockerfile.df

Содержимое mydockerfile.df
FROM amd64/node:10.16.0-alpine@sha256:f59303fb3248e5d992586c76cc83e1d3700f641cbcd7c0067bc7ad5bb2e5b489 AS tsbuildCOPY package.json .COPY yarn.lock .RUN yarn installCOPY lib libCOPY tsconfig.json tsconfig.jsonCOPY tsconfig.app.json tsconfig.app.jsonRUN yarn buildFROM amd64/ubuntu:18.04@sha256:eb70667a801686f914408558660da753cde27192cd036148e58258819b927395LABEL maintainer="Rhys Arkins <rhys@arkins.net>"LABEL name="renovate"...COPY php.ini /usr/local/etc/php/php.iniRUN cp -a /tmp/piik/* /var/www/html/RUN rm -rf /tmp/piwikRUN chown -R www-data /var/www/htmlADD piwik-cli-setup /piwik-cli-setupADD reset.php /var/www/html/## ENTRYPOINT ##ADD entrypoint.sh /entrypoint.shENTRYPOINT ["/entrypoint.sh"]USER root

Конфигурационный YAML выглядит таким образом (сам файл можно взять по прямой ссылке здесь: .gitlab-ci.yml):

Содержимое .gitlab-ci.yml
variables:    DOCKER_HOST: "tcp://docker:2375/"    DOCKERFILE: "mydockerfile.df" # name of the Dockerfile to analyse       DOCKERIMAGE: "bkimminich/juice-shop" # name of the Docker image to analyse    # DOCKERIMAGE: "knqyf263/cve-2018-11235" # test Docker image with several CRITICAL CVE    SHOWSTOPPER_PRIORITY: "CRITICAL" # what level of criticality will fail Trivy job    TRIVYCACHE: "$CI_PROJECT_DIR/.cache" # where to cache Trivy database of vulnerabilities for faster reuse    ARTIFACT_FOLDER: "$CI_PROJECT_DIR" services:    - docker:dind # to be able to build docker images inside the Runner stages:    - scan    - report    - publish HadoLint:    # Basic lint analysis of Dockerfile instructions    stage: scan    image: docker:git     after_script:    - cat $ARTIFACT_FOLDER/hadolint_results.json     script:    - export VERSION=$(wget -q -O - https://api.github.com/repos/hadolint/hadolint/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')    - wget https://github.com/hadolint/hadolint/releases/download/v${VERSION}/hadolint-Linux-x86_64 && chmod +x hadolint-Linux-x86_64         # NB: hadolint will always exit with 0 exit code    - ./hadolint-Linux-x86_64 -f json $DOCKERFILE > $ARTIFACT_FOLDER/hadolint_results.json || exit 0     artifacts:        when: always # return artifacts even after job failure               paths:        - $ARTIFACT_FOLDER/hadolint_results.json Dockle:    # Analysing best practices about docker image (users permissions, instructions followed when image was built, etc.)    stage: scan       image: docker:git     after_script:    - cat $ARTIFACT_FOLDER/dockle_results.json     script:    - export VERSION=$(wget -q -O - https://api.github.com/repos/goodwithtech/dockle/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')    - wget https://github.com/goodwithtech/dockle/releases/download/v${VERSION}/dockle_${VERSION}_Linux-64bit.tar.gz && tar zxf dockle_${VERSION}_Linux-64bit.tar.gz    - ./dockle --exit-code 1 -f json --output $ARTIFACT_FOLDER/dockle_results.json $DOCKERIMAGE            artifacts:        when: always # return artifacts even after job failure               paths:        - $ARTIFACT_FOLDER/dockle_results.json Trivy:    # Analysing docker image and package dependencies against several CVE bases    stage: scan       image: docker:git     script:    # getting the latest Trivy    - apk add rpm    - export VERSION=$(wget -q -O - https://api.github.com/repos/knqyf263/trivy/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')    - wget https://github.com/knqyf263/trivy/releases/download/v${VERSION}/trivy_${VERSION}_Linux-64bit.tar.gz && tar zxf trivy_${VERSION}_Linux-64bit.tar.gz         # displaying all vulnerabilities w/o failing the build    - ./trivy -d --cache-dir $TRIVYCACHE -f json -o $ARTIFACT_FOLDER/trivy_results.json --exit-code 0 $DOCKERIMAGE            # write vulnerabilities info to stdout in human readable format (reading pure json is not fun, eh?). You can remove this if you don't need this.    - ./trivy -d --cache-dir $TRIVYCACHE --exit-code 0 $DOCKERIMAGE         # failing the build if the SHOWSTOPPER priority is found    - ./trivy -d --cache-dir $TRIVYCACHE --exit-code 1 --severity $SHOWSTOPPER_PRIORITY --quiet $DOCKERIMAGE             artifacts:        when: always # return artifacts even after job failure        paths:        - $ARTIFACT_FOLDER/trivy_results.json     cache:        paths:        - .cache Report:    # combining tools outputs into one HTML    stage: report    when: always    image: python:3.5         script:    - mkdir json    - cp $ARTIFACT_FOLDER/*.json ./json/    - pip install json2html    - wget https://raw.githubusercontent.com/shad0wrunner/docker_cicd/master/convert_json_results.py    - python ./convert_json_results.py         artifacts:        paths:        - results.html

При необходимости также можно сканировать и сохраненные образы в виде .tar-архива (однако потребуется в YAML файле изменить входные параметры для утилит)

NB: Trivy требует для своего запуска установленные rpm и git. В противном случае он будет выдавать ошибки при сканировании RedHat-based образов и получении обновлений базы уязвимостей.

2. После добавления файлов в репозиторий, в соответствии с инструкциями в нашем конфигурационном файле, GitLab автоматически начнёт процесс сборки и сканирования. На вкладке CI/CD Pipelines можно будет увидеть ход выполнения инструкций.

В результате у нас есть четыре задачи. Три из них занимаются непосредственно сканированием и последняя (Report) собирает простой отчёт из разрозненных файлов с результатами сканирования.

По умолчанию Trivy останавливает своё выполнение, если были обнаружены CRITICAL уязвимости в образе или зависимостях. В то же время Hadolint всегда возвращает Success код выполнения, так как в результате его выполнения всегда есть замечания, что приводит к остановке сборки.

В зависимости от конкретных требований можно сконфигурировать код выхода, чтобы эти утилиты при обнаружении проблем определенной критичности, останавливали также и процесс сборки. В нашем случае сборка остановится, только если Trivy обнаружит уязвимость с критичностью, которую мы указали в переменной SHOWSTOPPER в .gitlab-ci.yml.


Результат работы каждой утилиты можно посмотреть в логе каждой сканирующей задачи, непосредственно в json-файлах в разделе artifacts или в простом HTML-отчёте (о нем чуть ниже):


3. Для представления отчётов утилит в чуть более человекочитаемом виде используется небольшой скрипт на Python для конвертации трёх json-файлов в один HTML-файл с таблицей дефектов.
Этот скрипт запускается отдельной задачей Report, а его итоговым артефактом является HTML-файл с отчётом. Исходник скрипта также лежит в репозитории и его можно адаптировать под свои нужды, цвета и т. п.


Shell-скрипт


Второй вариант подходит для случаев, когда необходимо проверять Docker-образы не в рамках CI/CD системы или необходимо иметь все инструкции в виде, который можно выполнить непосредственно на хосте. Этот вариант покрывается готовым shell-скриптом, который можно запустить на чистой виртуальной (или даже реальной) машине. Скрипт выполняет те же самые инструкции, что и вышеописанный gitlab-runner.

Для успешной работы скрипта в системе должен быть установлен Docker и текущий пользователь должен быть в группе docker.

Сам скрипт можно взять здесь: docker_sec_check.sh

В начале файла переменными задается, какой образ необходимо сканировать и дефекты какой критичности будут вызывать выход из утилиты Trivy с указанным кодом ошибки.

В процессе выполнения скрипта все утилиты будут скачаны в директорию docker_tools, результаты их работы в директорию docker_tools/json, а HTML с отчётом будет находиться в файле results.html.

Пример вывода скрипта
~/docker_cicd$ ./docker_sec_check.sh[+] Setting environment variables[+] Installing required packages[+] Preparing necessary directories[+] Fetching sample Dockerfile2020-10-20 10:40:00 (45.3 MB/s) - Dockerfile saved [8071/8071][+] Pulling image to scanlatest: Pulling from bkimminich/juice-shop[+] Running Hadolint...Dockerfile:205 DL3015 Avoid additional packages by specifying `--no-install-recommends`Dockerfile:248 DL3002 Last USER should not be root...[+] Running Dockle...WARN    - DKL-DI-0006: Avoid latest tag        * Avoid 'latest' tagINFO    - CIS-DI-0005: Enable Content trust for Docker        * export DOCKER_CONTENT_TRUST=1 before docker pull/build...[+] Running Trivyjuice-shop/frontend/package-lock.json=====================================Total: 3 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 2, CRITICAL: 0)+---------------------+------------------+----------+---------+-------------------------+|       LIBRARY       | VULNERABILITY ID | SEVERITY | VERSION |             TITLE       |+---------------------+------------------+----------+---------+-------------------------+| object-path         | CVE-2020-15256   | HIGH     | 0.11.4  | Prototype pollution in  ||                     |                  |          |         | object-path             |+---------------------+------------------+          +---------+-------------------------+| tree-kill           | CVE-2019-15599   |          | 1.2.2   | Code Injection          |+---------------------+------------------+----------+---------+-------------------------+| webpack-subresource | CVE-2020-15262   | LOW      | 1.4.1   | Unprotected dynamically ||                     |                  |          |         | loaded chunks           |+---------------------+------------------+----------+---------+-------------------------+juice-shop/package-lock.json============================Total: 20 (UNKNOWN: 0, LOW: 1, MEDIUM: 6, HIGH: 8, CRITICAL: 5)...juice-shop/package-lock.json============================Total: 5 (CRITICAL: 5)...[+] Removing left-overs[+] Making the output look pretty[+] Converting JSON results[+] Writing results HTML[+] Clean exit ============================================================[+] Everything is done. Find the resulting HTML report in results.html


Docker-образ со всеми утилитами


В качестве третьей альтернативы я составил два простых Dockerfile для создания образа с утилитами безопасности. Один Dockerfile поможет собрать набор для сканирования образа из репозитория, второй (Dockerfile_tar) собрать набор для сканирования tar-файла с образом.

1. Берем соответствующий Docker файл и скрипты из репозитория https://github.com/Swordfish-Security/docker_cicd/tree/master/Dockerfile.
2. Запускаем его на сборку:
docker build -t dscan:image -f docker_security.df .

3. После окончания сборки создаем контейнер из образа. При этом передаём переменную окружения DOCKERIMAGE с названием интересующего нас образа и монтируем Dockerfile, который хотим анализировать, с нашей машины на файл /Dockerfile (обратите внимание, что требуется абсолютный путь до этого файла):
docker run --rm -v $(pwd)/results:/results -v $(pwd)/docker_security.df:/Dockerfile -e DOCKERIMAGE="bkimminich/juice-shop" dscan:image

[+] Setting environment variables[+] Running Hadolint/Dockerfile:3 DL3006 Always tag the version of an image explicitly[+] Running DockleWARN    - DKL-DI-0006: Avoid latest tag        * Avoid 'latest' tagINFO    - CIS-DI-0005: Enable Content trust for Docker        * export DOCKER_CONTENT_TRUST=1 before docker pull/buildINFO    - CIS-DI-0006: Add HEALTHCHECK instruction to the container image        * not found HEALTHCHECK statementINFO    - DKL-LI-0003: Only put necessary files        * unnecessary file : juice-shop/node_modules/sqlite3/Dockerfile        * unnecessary file : juice-shop/node_modules/sqlite3/tools/docker/architecture/linux-arm64/Dockerfile        * unnecessary file : juice-shop/node_modules/sqlite3/tools/docker/architecture/linux-arm/Dockerfile[+] Running Trivy...juice-shop/package-lock.json============================Total: 20 (UNKNOWN: 0, LOW: 1, MEDIUM: 6, HIGH: 8, CRITICAL: 5)...[+] Making the output look pretty[+] Starting the main module ============================================================[+] Converting JSON results[+] Writing results HTML[+] Clean exit ============================================================[+] Everything is done. Find the resulting HTML report in results.html

Результаты


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

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

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

Строим безопасную разработку в ритейлере. Опыт интеграции с кассовым ПО GK

26.11.2020 10:08:34 | Автор: admin
Что самое сложное в проектной работе? Пожалуй, свести к общему знаменателю ожидания от процесса и результата у заказчика и исполнителя. Когда мы начинали внедрять безопасную разработку в группе GK-приложений (кассового ПО) крупного ритейлера, то на входе имели вагон времени и задачи снижения уязвимостей в коде. А вот что и как нам пришлось решать на практике, мы вам расскажем под катом.
Кстати, это уже третий пост, в котором мы делимся своим опытом выстраивания процесса безопасной разработки для крупного ритейлера. Если пропустили, можете прочитать первые две части: о безопасной разработке порталов и мобильных приложений и о безопасной разработке в группе приложений SAP.



О проекте


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

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

GK


GK это по сути фреймворк, на основе которого можно создавать свою кастомизацию кассового ПО. Заказчик несколько лет назад выкупил у GK некоторую часть исходного кода этого софта для создания собственной версии, соответственно, это направление представляло собой очень большую группу разработки. Большая часть ПО написана на языке Java (90% проектов). Встречались и некоторые проекты, написанные на C++ с использованием довольно древних библиотек, поскольку аппаратное обеспечение было заточено под этот стек технологий. Для Windows под Windows Server 2008 и под набор библиотек для Visual Studio, для которых данная версия ОС была последней совместимой.

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

Помимо внедрения в CI/CD мы еще внедрялись и в Jira, для чего нам пришлось создать отдельную сущность для классификации задач уязвимость. Этот вид задачи позволял заводить уязвимости внутри Jira в соответствии с процессом разработки и работы с таск-трекером.

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

Решение для интеграции


Когда мы пришли в группу GK внедрять Solar appScreener, мы были удивлены огромным количеством кода в проектах этого направления и предложили стандартный вариант сканирования основной ветки разработки. Разработчики сказали, что они тоже хотят получать информацию об уязвимостях и тоже хотят пользоваться анализатором, но не так, как офицеры безопасности. Им нужна была автоматизация проверки на уязвимости: единая точка входа в виде GitLab, где можно видеть все изменения. Чтобы на сканирование автоматически запускался код не только основной ветки разработки, но и всех побочных веток. При этом нужен был автоматизированный запуск из Jira и по просьбе заказчика полуавтоматизированный из Jenkins и GitLab.

В итоге мы решили, что самым простым способом анализа ветки (в особенности для долгих feature-веток) будет запуск сканирования таким образом, чтобы задача в Jira при ее полном завершении (финальном push-е) переводилась в статус in review. После чего она попадала к специалисту, который может принять изменения и дать добро на старт сканирования кода на уязвимости. Иначе анализ кода по каждому push-у разработчика в репозитории создал бы адскую нагрузку и на людей, и на технику. Разработчики комитили код в репозитории не реже раза в пять минут, и при этом проекты порой содержали по 3,5 млн строк кода, одно сканирование которого, соответственно, длилось 6-8 часов. Такое никакими ресурсами не покроешь.

Стек технологий


Теперь немного о том, какой набор технологий использовался в проекте. GitLab в качестве системы контроля версий, Jenkins в качестве CI/CD-системы, Nexus в качестве хранилища артефактов и Jira в качестве системы отслеживания задач. При этом сами разработчики взаимодействовали только с Jira и с GitLab, тогда как с Jenkins инженеры, а с Solar appScreener безопасники. Вся информация об уязвимостях в коде присутствовала в GitLab и Jira.

Поэтому пришлось организовать процесс сканирования следующим образом: в Jira задача переводилась в in review через Jenkins, так как была необходима сборка. Имелся целый стек из Jenkins-плагинов и скриптов, которые получали веб-хук из Jira. То есть помимо вида задачи уязвимость нам пришлось создавать в Jira еще и веб-хуки, которые при изменении проекта в GK отправлялись в Jenkins и требовали дополнительных шагов по настройке и серьёзной доработке двусторонней интеграции. Процесс был таким: когда в GK обновлялась какая-либо задача, Jira проверяла, соответствует ли эта задача тем, которые должны отправляться в Jenkins, а именно является ли это задачей разработки. И только после этого веб-хук уходил в Jenkins, который парсил веб-хук и на основе тела запроса понимал, какому репозиторию в GitLab и какой группе работ (job-ов) нужно запускаться.

Разработчики GK выставили ряд требований при заведении задач в Jira, и мы старались их использовать по полной, чтобы получить максимальное количество информации. Благодаря этому мы смогли правильно матчить репозитории в GitLab и ветку, которую мы должны были стянуть из конкретного репозитория для ее запуска на анализ. В требованиях мы жестко прописали, что ветка в GitLab обязательно должна содержать в названии номер задачи в Jira. В принципе, это best practice, который используется повсеместно в самой задаче должны быть метки, маркирующие, к какой части приложения/системы относится проблема.

На основе веб-хука, в котором содержалось название проекта, метки, поля с компонентами, а также ключ самой задачи и т.п., происходило автоматическое определение необходимости запуска всех задач. Часть проектов получала ответ всех значений удовлетворительно и после этого отправлялась на сканирование. А именно: поступал запрос в GitLab, проверялось наличие в нем данной ветки разработки, и при подтверждении система запускала сканирование новой части кода этой ветки в Solar appScreener.

Учитываем пожелания



Кадр из мультфильма Вовка в тридевятом царстве

Поскольку уязвимостей в GK-проектах было десятки тысяч, разработчики не хотели видеть их все. Меж тем в коде содержались порой критичные уязвимости: вставленные в код пароли, отраженные межсайтовые скриптинги, SQL-инъекции и т.п.

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

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

Как и для остальных проектов компании, мы выстроили для GK соответствие проекта в GitLab проекту в Solar appScreener, а дополнительно провели корреляцию с веткой разработки. Чтобы разработчикам и офицерам ИБ было проще искать, мы создали файл с реестром названия группы репозиториев, конкретного проекта из этой группы и ветки в Solar appScreener.

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

Интеграция с GitLab


Сначала мы сканировали основную ветку разработки для каждого репозитория, от которой шла ветка с изменениями. Поскольку разработчики хотели видеть комментарии с изменениями сразу в GitLab, мы договорились, что если ветка переведена в статус in review, это значит, что уже есть соответствующий merge request на ее вливание. Система проверяла, что merge request был действительно открыт, и если это подтверждалось, то Solar appScreener анализировал основную ветку разработки. Затем он сканировал ветку с изменениями, строил diff, выгружал его, получал только новые уязвимости, брал из них критичные (наш инструмент позволяет настраивать уровни критичности) и преобразовывал их описание в формат текстовых сообщений со ссылкой на данное вхождение в конкретном проекте в Solar appScreener (см.скриншот ниже). Эти сообщения отправлялись в GitLab в виде дискуссии от имени технической учетной записи. В merge request вместе с измененным кодом можно было увидеть непосредственно строку срабатывания. Её сопровождал адресованный конкретному разработчику комментарий в GitLab типа вот здесь содержится уязвимость, пожалуйста, поправьте или закройте ее.



Таким образом, между этими двумя сканированиями мы выявляли изменения, которые привнес разработчик. Если количество уязвимостей увеличивалось, это обязательно отражалось в комментариях отчёта. Такая схема была очень удобна для всех участников, потому что в GitLab сразу же появлялись неразрешенные дискуссии, а это значит, что merge request влить нельзя. И ответственный за approve merge requestа видел, что разработчик допустил уязвимости, и их нужно закрыть.

Интеграция с Active Directory


Чтобы просматривать уязвимости через Solar appScreener, разработчикам нужны были права. Поэтому помимо всей интеграции с системами разработки и со всеми тремя скоупами мы еще интегрировались и с сервисами Active Directory. Для нас некоторую сложность представляло то, что у заказчика был чудовищно большой AD с миллионом вложенных групп, что потребовало множества оптимизаций. То есть помимо внедрения CI/CD нам приходилось в процессе решать много сопутствующих задач настройки, адаптации и оптимизации (см. скриншот ниже).



Что касается интеграции с Active Directory, разработчики GK не хотели настраивать параметры доступа в ряд проектов Solar appScreener непосредственно в AD, потому что они не были владельцами этого ресурса. В компании процесс выдачи прав доступа выглядел следующим образом: если разработке нужно было внести нового человека в группу, приходилось писать заявку в техподдержку. Там определяли, кому именно отправлять данный запрос, кто отвечает за выдачу подобных прав, действительно ли можно выдать этому человеку права, авторизованы они или нет и т.п.

Это трудоемкий процесс, которым разработчики не управляли, в отличие от GitLab.Поэтому они попросили организовать интеграцию с AD следующим образом. Они разрешают видеть уязвимости не только техвладельцам системы и офицерам безопасности, как это было сделано для других направлений, но и сделать матчинг прав в GitLab. То есть если человек имеет права разработчика и выше (maintainer, владелец репозитория), то он может просматривать соответствующие проекты в Solar appScreener, которые относятся к этому репозиторию. Когда в Solar appScreener создавался новый проект, система запрашивала имена всех, кто состоит в GitLab в данном репозитории с уровнем доступа выше разработчика. Список выгружался, поступало обращение в анализатор кода, который в свою очередь отправлял запрос в AD с добавлением списка отобранных людей. Все, кто состоял в данном репозитории в GitLab, получали автоматические права через свои AD-учетки на доступ к проекту в Solar appScreener. В конце всей цепочки генерился pdf-файл с отчетом по уязвимостям из анализатора кода. В отчете содержался diff, который рассылался списку пользователей проектов GK на электронную почту.

Резюме


Реализованная интеграция с кассовым ПО GK это пример весьма масштабного проекта внедрения и автоматизации в компании процесса безопасной разработки, который был выстроен не только вокруг службы информационной безопасности, но и вокруг разработчиков. Причем в этой группе проектов так же, как и в других группах, параллельно шли сканирования основной ветки разработки: офицеры безопасности тоже заводили задачи в Jira и их верифицировали. Уязвимости заносились в бэклог и позже исправлялись.

Разработчики получили для себя удобный инструмент, встроенный в привычную среду и workflow. Так как они выбрали для себя порог из топ критических уязвимостей, которые довольно редко возникают, их нагрузка несильно возросла после внедрения. В итоге довольны оказались все участники проекта: и те, кто стремился повысить безопасность кода, и те, кому приходилось исправлять уязвимости.

Если у вас есть собственный опыт реализации практик безопасной разработки, пожалуйста, поделитесь им в комментариях!

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

Автор: Иван Старосельский, руководитель отдела эксплуатации и автоматизации информационных систем
Подробнее..

Интеграция Netsparker с AD через Keycloak

16.12.2020 14:07:22 | Автор: admin

Привет, Хабр!

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

Забегая вперед, стоит отметить, что аналогичные шаги помогут настроить интеграцию с AD не только Netsparker, но и иных приложений, которые не имеют собственных средств взаимодействия с Active Directory.

Netsparker это коммерческий динамический сканер веб-приложений, который позволяет обнаруживать большое количество уязвимостей в реальном времени и при этом располагает широким набором средств для интеграции с различными инструментами разработки и сборки. Это позволяет осуществлять поиск уязвимостей еще до того, как приложение выходит в производственную среду.
Подробнее: https://www.netsparker.com/

Keycloak решение с открытым кодом, которое способно выступать промежуточным звеном между приложением и сторонним аутентификационным сервисом. Это часто необходимо в случаях, если требования к аутентификации превосходят возможности приложения или имеющаяся инфраструктура позволяет сделать бесшовную аутентификацию между всеми используемыми сервисами (SSO).
Keycloak позволяет интегрировать ваши приложения с внешними системами аутентификации пользователей, может выступать в качестве посредника для использования social login сервисов и многое другое.
Подробнее: https://www.keycloak.org/

Проблема

По умолчанию в Netsparker есть несколько вариантов внедрения Single Sign-On: с Google Cloud, PingIdentity, Okta, Azure AD, ADFS, а также базовая интеграция при помощи SAML2.0.

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

Проблема: Netsparker не работает напрямую с ADПроблема: Netsparker не работает напрямую с AD

Дальше я расскажу, как мы подключали чистый AD к Netsparker при помощи промежуточного инструмента Keycloak. Ниже будет представлен самый базовый вариант, так как в каждой организации процесс будет отличаться.

Общая схема выглядит так:

Схема интеграции Netsparker с AD через KeycloakСхема интеграции Netsparker с AD через Keycloak

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

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

Приступим.

Экспорт конфигурации Netsparker SSO

Первый шаг. Чтобы включить Single Sign-On в Netsparker необходимо сделать следующее:

  1. Войти в Netsparker под административным пользователем и открыть Settings Single Sign-On.

  2. Поставить флажок Enable для включения поддержки SSO.
    (!) Опция Enforce to authenticate ... нужна для того, чтобы запретить пользователям без прав администратора вход напрямую в Netsparker с использованием пароля.
    Остальные поля мы заполним чуть позже данными из Keycloak.

    Экран настройки Netsparker SSOЭкран настройки Netsparker SSO


    После включения функции SSO на странице входа в Netsparker появится дополнительная ссылка для доступа через внешний сервис.
    Вот так это будет выглядеть для неаутентифицированного пользователя:

    Экран входа в Netsparker для неаутентифицированного пользователяЭкран входа в Netsparker для неаутентифицированного пользователя
  3. Справа на закладке SAML скачиваем базовый XML с метаданными Netsparker.
    Этот XML содержит настройки SAML-клиента (в нашем случае им является Netsparker), которые понадобятся для конфигурирования Keycloak, например Client Identifier, SAML Service URL.

    Сохранение SAML мета-данных NetsparkerСохранение SAML мета-данных Netsparker

На данном этапе с Netsparker'ом всё. Переходим в Keycloak.

Конфигурация Keycloak

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

    Экран добавления Realm'аЭкран добавления Realm'а
  2. Теперь необходимо в этом Realm прописать нашего клиента (Netsparker), который будет пользоваться услугами Keycloak.
    Переходим на закладку Clients, нажимаем Create, импортируем XML, который сохранили из Netsparker.

    Экран добавления клиента KeycloakЭкран добавления клиента Keycloak

    Данные формы подгрузятся из файла, поэтому нажимаем Save.

  3. В открывшемся экране настроек клиента сразу сохраняем себе в блокнот данные сертификата (закладка SAML Keys).

    Данные сертификата клиента KeycloakДанные сертификата клиента Keycloak
  4. Возвращаемся на закладку Settings и вносим следующие изменения:

    Client Signature Required ставим в положение OFF, т. к. Netsparker пока не передает данные о своем алгоритме подписи, и поэтому SAML-запрос получается некорректным.
    Valid Redirect URIs модифицируем URL и заменяем часть строки адреса ?spid на *. Это поле задает шаблон для адресов, на которые Keycloak разрешено осуществлять перенаправление браузера. Это сделано для того, чтобы злоумышленник не мог подставить адрес подконтрольного ему сервиса.

    Настройки клиента KeycloakНастройки клиента Keycloak


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

  5. Нажимаем Save и возвращаемся в Netsparker.

Конфигурация Netsparker

Теперь на стороне сканера добавляем данные о провайдере аутентификационных данных (сокращенно IdP), в нашем случае это Keycloak.

  1. Донастроим то, что мы начали настраивать на первых шагах.
    Снова открываем страницу Netsparker Settings Single Sign-On и прописываем идентификатор IdP и URL для SAML-запросов.

    У Keycloak эти значения стандартные:
    IdP Identifier:
    https://<домен-где-установлен-Keycloak>/auth/realm/<наш-Realm>
    SAML Endpoint:
    https://<домен-где-установлен-Keycloak>>/auth/realms/<наш-Realm>/protocol/saml
    X.509 Certificate:
    Сертификат в base64, который мы сохранили из Keycloak.

    Настройка SSO в NetsparkerНастройка SSO в Netsparker
  2. Сохраняем конфигурацию, и на данный момент у нас уже есть базовая возможность пользоваться аутентификацией Keycloak.

    При нажатии на Sign in via your Identity Provider открывается промежуточная форма ввода email пользователя из Netsparker, из которой уже осуществляется переход на форму входа Keycloak. Пользовательские данные, вводимые на этой форме либо проверяются в базе пользователей Keycloak, либо прокидываются дальше на AD (мы посмотрим оба варианта).

    После успешного входа в Keycloak нас должно перенаправить обратно в Netsparker уже в аутентифицированном состоянии.

    Процесс входа в NetsparkerПроцесс входа в Netsparker


    Но чтобы это произошло, надо сперва сконфигурировать пользователей.

Создание пользователей вручную

Netsparker

Для начала заведем тестового пользователя в Netsparker для проверки интеграции.

  1. В Netsparker открываем Team New Team Member, заполняем поля Name, Email и проставляем какие-нибудь разрешения (те, что на снимке, выбраны просто для примера):

    Экран создания пользователей в NetsparkerЭкран создания пользователей в Netsparker
  2. Активируем пользователя, кликнув на код подтверждения на странице Invitations.

    Список "приглашенных" пользователейСписок "приглашенных" пользователей
  3. На открывшейся странице вводим пароль для созданного пользователя и нажимаем Create an account.
    Этот пароль будет использоваться, только если пользователь решит войти в Netsparker не через SSO, а через обычную форму входа:

    Экран регистрации нового пользователя NetsparkerЭкран регистрации нового пользователя Netsparker

Keycloak

На данный момент войти в Netsparker через SSO не получится, т. к. в базе Keycloak в нашем Realm еще нет ни одного пользователя и Keycloak не сможет его аутентифицировать.

Исправим это:

  1. Заходим в Keycloak Manage Users, нажимаем Add user, вводим данные пользователя и сохраняем:

    Экран добавления пользователя в KeycloakЭкран добавления пользователя в Keycloak

    Здесь важно указать Username в виде email-адреса и поставить Email Verified в положение On, чтобы можно было бесшовно перейти от Keycloak к Netsparker.

  2. После сохранения появляется страница деталей пользователя. На ней в закладке Credentials необходимо установить пароль этому пользователю. Он при необходимости может отличаться от пароля в Netsparker:

    Создание пароля для пользователя KeycloakСоздание пароля для пользователя Keycloak

    Ставим Temporary в положение Off и устанавливаем пароль.

Промежуточная проверка интеграции

Теперь можем убедиться, что наша интеграция Netsparker+Keycloak работает:

  1. Открываем страницу входа в Netsparker и выбираем Sign in via your Identity Provider.

  2. Вводим почтовый адрес нашего тестового пользователя и попадаем на страницу входа в Keycloak.

  3. Указываем аутентификационные данные созданного пользователя.

    Экран входа в KeycloakЭкран входа в Keycloak
  4. Если всё прошло успешно, Keycloak перенаправит нас на главную страницу Netsparker под нашим тестовым пользователем.

    Перенаправление из Keycloak в NetsparkerПеренаправление из Keycloak в NetsparkerОсновной экран сканера NetsparkerОсновной экран сканера Netsparker

Промежуточный итог
Итак, на данный момент у нас есть возможность входить в Netsparker, вводя данные пользователя в Keycloak. Следующий шаг и реализация начальной цели связать аутентификацию Netsparker c аутентификацией на AD. При необходимости мы также можем синхронизировать базу данных пользователей Keycloak с базой Active Directory.

Следующий этап настройки: Keycloak + ADСледующий этап настройки: Keycloak + AD

Дорога в AD

Теперь сделаем следующий шаг и настроим аутентификацию пользователей Active Directory через Keycloak по LDAP. Для этого нам потребуется функция User Federation.

  1. Открываем Keycloak User Federation и в списке провайдеров выбираем ldap.

    Выбор провайдера аутентификации в KeycloakВыбор провайдера аутентификации в Keycloak
  2. Дальше настройка может отличаться в зависимости от конкретной структуры AD, но я опишу на базовом примере, что нам потребуется.

    В окне Add user federation provider заполняем поля:
    Vendor: Active Directory.
    Connection URL: путь к вашему серверу.
    Например ldap://172.16.2.23:389.
    Users DN: путь навигации к записям пользователя.
    Например CN=Users,DC=cx,DC=local.
    Bind DN: путь к записи администратора AD.
    Например cn=cxadmin,cn=users,dc=cx,dc=local.
    Bind Credential: пароль учетной записи администратора.
    Например ваш валидный пароль :)

    (!) Если мы хотим, чтобы данные из AD сперва синхронизировались в локальную базу Keycloak, ставим переключатель Import User в положение On.

    Опция импортирования пользователейОпция импортирования пользователей

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

  3. Дальше необходима небольшая манипуляция с данными из AD. Так как Netsparker требует в качестве имени пользователя его email-адрес (помните ремарку в начале?), нам надо убедиться, что во-первых из AD придут только пользователи с почтовым адресом и, во-вторых, что имя пользователя будет содержать этот адрес, а не иной параметр.

    Поэтому следующим шагом мы прописываем фильтр пользователей AD, который выберет только сущности каталога с email. Дальше мы явно укажем, какой атрибут у нас будет служить именем пользователя:

    Custom User LDAP Filter: (&(objectCategory=person)(mail=*)(objectClass=user)).
    Username LDAP attribute: mail.

    Сохраняем форму, и в итоге наш экран настройки должен выглядеть так:

    Сводная страница настроек LDAP провайдераСводная страница настроек LDAP провайдера
  4. Теперь сделаем так, чтобы при синхронизации пользователей AD они автоматически получали email в качестве имени пользователя. Для этого открываем закладку Mappers:

    Настройка mapper'овНастройка mapper'ов

    И в mapperе Username заменяем атрибут cn на mail.

    Настройка отображения email адреса на имя пользователяНастройка отображения email адреса на имя пользователя
  5. Сохраняем настройки.

(!) Если мы настраивали синхронизацию базы Keycloak с AD, мы можем проверить эту настройку, нажав Synchronize all users. Если всё прошло успешно, то на вкладке Users у нас появятся наши пользователи из AD'a:)

Проверяем, что всё работает

Можем убедиться, что наша настройка верна, войдя под учётной записью из AD в Netsparker.

  1. Проверяем, что в Netsparker есть пользователь с таким почтовым адресом (Manage Team). Какой у него пароль неважно, так как аутентификация будет происходить на стороне AD.

    Искомый пользователь NetsparkerИскомый пользователь Netsparker
  2. Открываем страницу Netsparker SSO и вводим email-адрес пользователя AD.

    Промежуточный экран входа в Netsparker SSOПромежуточный экран входа в Netsparker SSO
  3. На странице входа Keycloak вводим данные пользователя AD с паролем.

    Экран входа в KeycloakЭкран входа в Keycloak
  4. Нам должна открыться страница Netsparker для этого пользователя.

    Основной экран Netsparker для аутентифицированного AD-пользователя AlexОсновной экран Netsparker для аутентифицированного AD-пользователя Alex

Настройка автоматического создания пользователей

Вручную заводить новых пользователей зачастую неудобно, поэтому в Netsparker есть возможность упростить заведение новых учётных записей, которая называется auto provisioning. С ее помощью устраняется необходимость вручную добавлять нового пользователя: если его нет в базе данных Netsparker, то такой пользователь будет создан автоматически при попытке входа с верными данными своей учётной записи AD.

Дальше нам будет необходимо сделать небольшие изменения в конфигурациях Keycloak и Netsparker. Так как auto provisioning требует, чтобы сессия инициировалась Identity Provider'ом, давайте его и настроим:

Keycloak

  1. Открываем настройки нашего клиента Keycloak Clients наш клиент.

  2. Нам потребуется внести изменения в три поля:
    Valid Redirect URIs (опционально): можно оставить как было, а можно ограничить перенаправление конкретным клиентом (который задаётся параметром spid). Для этого копируем из настроек SSO Netsparke'а полный URL SAML 2.0 Service URL.
    IDP Initiated SSO URL: необходимо задать URL, который мы хотим использовать для нашего конкретного клиента (Netsparker), чтобы явно указать, что должно использоваться SSO, вызываемое со стороны IdP. Например netsparker. После этого пользователи смогут входить конкретно в Netsparker, используя URL из подсказки.
    Assertion Consumer Service POST Binding URL: URL, куда Keycloak будет отправлять SAML-данные для входа в Netsparker. Указываем здесь SAML 2.0 Service URL из настроек Netsparker'a.

    Дополнительная настройка клиента KeycloakДополнительная настройка клиента Keycloak
  3. Сохраняем настройки.

Настройка mappers

Чтобы Keycloak формировал SAML-запрос в виде, необходимом Netsparker'у, необходимо добавить в него несколько обязательных полей (FirstName и LastName). В этом нам поможет mapper, который автоматически назначит полям SAML соответствующие поля из пользовательской записи.

Для этого открываем Keycloak Clients Mappers и создаем mapper, нажав на Create.

Дополнительная настройка mapper'овДополнительная настройка mapper'ов

Содержимое полей mapper задаем как на картинке ниже:

Настройка SAML-mapper'а для имени пользователяНастройка SAML-mapper'а для имени пользователя

Аналогично создаём mapper для фамилии:

Настройка SAML-mapper'а для фамилии пользователяНастройка SAML-mapper'а для фамилии пользователя

Итоговый список должен выглядеть так:

Список mapper'овСписок mapper'ов

Netsparker

Последнее, что нам нужно сделать донастроить Netsparker, чтобы он создавал пользователей и принимал запросы на это действие от Keycloak.

  1. Открываем Netsparker Settings Single Sign-On.

  2. В поле SAML 2.0 Endpoint указываем URL из Keycloak из подсказки поля IDP Initiated SSO URL.

  3. Выбираем опцию Enable Auto Provisioning и сохраняем настройки.

    Настройка автоматического создания пользователей в NetsparkerНастройка автоматического создания пользователей в Netsparker

Финальная проверка

Теперь проверим как это всё работает.

  1. Прежде всего теперь на странице Netsparker Team можем удалить пользователя AD, которого мы создавали вручную.

    Удаление пользователяУдаление пользователя
  2. В Keycloak завершаем все сессии, чтобы они нам не мешали: Keycloak Manage Sessions Logout all.

  3. Открываем страницу Keycloak для SSO (напомню, Target IDP initiated SSO URL это ссылка вида:
    https://yourdomain.com/auth/realms/netsparker/protocol/saml/clients/netsparker).

  4. Вводим данные пользователя из AD.

  5. Если всё прошло успешно, в Netsparker будет создан соответствующий пользователь с минимальными правами и для него будет установлена новая сессия.

    Основной экран Netsparker для пользователя, автоматически созданного при входе через ADОсновной экран Netsparker для пользователя, автоматически созданного при входе через AD

Что дальше?

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

Если была выбрана опция auto provisioning, необходимо от имени административного пользователя выдать необходимые разрешения созданному пользователю, чтобы тот мог работать с Netsparker.

Для простоты лучше первый раз для создания пользователя входить через Target IDP initiated SSO URL и уже впоследствии аутентифицироваться либо по этому же URL, либо осуществлять вход через привычную страницу Netsparker Login.

Еще один нюанс в данный момент выход пользователя из Netsparker не приводит к выходу пользователя из Keycloak. Поэтому, когда в следующий раз мы решим войти при помощи SSO, нас сразу перебросит в Netsparker под последним аутентифицированным пользователем. Это можно решить, например, уменьшив длительность сессии пользователя в Keycloak. Тогда его SSO-сессия будет автоматически уничтожена после указанного промежутка времени.
Настройка осуществляется в Realms Netsparker Tokens:

Настройка длительности SSO сессииНастройка длительности SSO сессии

Также пользователь может вручную закончить свою сессию на собственной странице учётной записи Keycloak по ссылке вида: https://keycloak.yourcompany.com/auth/realms/netsparker/account
После этого для входа в Netsparker потребуется снова аутентифицироваться в Keycloak и можно будет осуществить вход под другим пользователем.

Заключение

Мы рассмотрели в деталях, как можно аутентифицировать пользователей Netsparker через Active Directory с использованием промежуточного решения Keycloak. В принципе, такой же процесс может быть применён и для других приложений, которые не работают с Active Directory напрямую, но поддерживают SAML для описания деталей аутентификации.
Keycloak позволяет достаточно гибко настраивать и дополнять систему аутентификации, комплексно закрывая этот аспект безопасности для приложений, используемых в организации.

Подробнее..

Строим безопасную разработку в ритейлере. Итоги одного большого проекта

22.12.2020 10:22:28 | Автор: admin
Эта статья завершающая в цикле материалов о нашем опыте выстраивания процесса безопасной разработки для крупного ритейлера. Если пропустили, можете прочитать первые части: о безопасной разработке порталов и мобильных приложений, о безопасной разработке в группе приложений SAP и о встраивании в процесс разработки кассового ПО. Настало время собрать шишки, которые мы набили подвести итоги.

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




Lessons to Be Learned


1. Учитываем регламенты и бюрократию


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

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

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

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

2. Текучка существенный тормоз процесса


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

Однажды нас застали врасплох категоричным ответом: Я считаю, что это решение будет неверным. Мы так делать не будем. Это случилось, когда мы уже договорились о полной передаче поддержки баз данных MariaDB с руководителем отдела администрирования баз данных. Перед самой сдачей проекта, когда мы все согласовали, выяснилось, что в отделе сменился руководитель. И хотя все договоренности с прежним сотрудником были запротоколированы, согласно регламенту, нам пришлось пойти на компромисс и взять администрирование на себя в рамках техподдержки всего продукта. Оказалось, что заказчик не берет на поддержку MariaDB, хотя в проектной документации не было упоминаний об этом.

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

3. Погружение в специфику избавит от множества проблем


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

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


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

Изменения в продукте по результатам проекта


1. Интеграция с AD


До этого проекта интеграция Solar appScreener с Active Directory не была оптимизирована под высокие нагрузки. Она начинала тормозить при подключении к каталогам с тысячами пользователей, с ней было не очень удобно работать. В процессе развития проекта систему доработали так, что никаких нареканий к ней уже не возникало. Теперь мы сможем интегрироваться с любой подобной системой, и проблем с производительностью у нас не будет.

2. Jira


Мы также серьезно улучшили все нюансы, связанные с Jira. До проекта мы полагались на дефолтную реализацию Jira, но у данной ритейловой компании Jira была очень серьезно переписана: добавлены различные поля, изменена дефолтная механика создания задач, все изначальные шаблоны. Поэтому мы внедрили в Solar appScreener инструмент, который позволяет подстраиваться под любой вид интеграции, потому что имеет возможность редактировать тело создаваемой задачи, которая посылается в Jira по API из Solar appScreener. Это очень гибкий инструмент.

Например, когда мы настраивали интеграцию с SAP, то столкнулись с тем, что наша старая интеграция с Jira выгружает какие-то дефолтные поля задачи, ты пытаешься создать задачу, и это не получается. Думаешь, что происходит?. Открываешь Jira, а там все не так, Jira по API выдает абсолютно не те дефолтные поля, не того типа данных, которые на самом деле должны содержаться. Но поскольку на эту реализацию у заказчика были завязаны процессы, то проблемы надо было решать на стороне Solar appScreener.

В результате в SAP нужно было заранее определить поля estimation time to do this task. Estimation time приходит как string, заходишь и видишь в нем два поля. А если открыть XML, то обнаруживается, что в этих полях данные должны быть описаны определенным образом. То есть это уже, по сути, словарь со множеством значений, где нужно сразу прописать время, например, one day в формате именно 1d. Ты это исправляешь, пытаешься запустить интеграцию и опять что-то не работает. Смотришь и понимаешь: какое-то поле, которое выгрузилось как обязательное, действительно числится как обязательное. Но при этом в самом интерфейсе его заполнять не нужно. Пару раз вот так потыкавшись, ты в итоге реализуешь интеграцию, но если бы у нас не было возможности гибкой настройки любого вида полей, то мы бы не смогли настроить интеграцию с такой кастомной версией Jira.



3. Изменение архитектуры приложения


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

Для себя мы выяснили, что есть очень много запросов на переход с MariaDB/MySQL, функционала которой было достаточно для работы приложения, на PostreSQL. Особенно со стороны крупного бизнеса, для которого эта база данных является одной из наиболее распространенных благодаря широкому функционалу и хорошей поддержке. Так, заказчик в определенный момент отказался от поддержки MariaDB, и их подразделение баз данных перешло на работу исключительно с PostgreSQL. Поэтому нам пришлось внедрять поддержку этой базы данных.

Резюме


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

Проект выявил узкие места, и мы осознали, как с ними бороться в будущем. Наша команда поняла для себя, как должна выглядеть архитектура Solar appScreener через год, чтобы продукт улучшался, расширялись его варианты поставки. Многое для себя выяснили с точки зрения улучшения пользовательского опыта работы с продуктом. В некоторых местах не хватало поиска по пользователям. Нужно было дать возможность добавить пользователя в локальную группу из LDAP и других протоколов сразу в нескольких, связанных с панелью администрирования местах (в настройках конкретного проекта, в настройках пользователя и т.п.).

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

Это заключительная часть цикла статей о построении процесса безопасной разработки в крупном ритейлере. Будем рады ответить на вопросы и узнать о вашем опыте реализации практик безопасной разработки в комментариях!

Автор: Иван Старосельский, руководитель отдела эксплуатации и автоматизации информационных систем
Подробнее..

Лучшие практики при написании безопасного Dockerfile

14.01.2021 10:07:09 | Автор: admin

В данной статье мы рассмотрим небезопасные варианты написания собственного Dockerfile, а также лучшие практики, включая работу с секретами и встраивание инструментов статического анализа. Тем не менее для написания безопасного Dockerfile наличия документа с лучшими практиками мало. В первую очередь требуется организовать культуру написания кода. К ней, например, относятся формализация и контроль процесса использования сторонних компонентов, организация собственных Software Bill-of-Materials (SBOM), выстраивание принципов при написании собственных базовых образов, согласованное использование безопасных функций, и так далее. В данном случае отправной точкой для организации процессов может служить модель оценки зрелости BSIMM. Однако в этой статьей пойдет речь именно о технических аспектах.

Безопасное написание Dockerfile

Задавать LABEL и не использовать тег latest

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

FROM redis@sha256:3479bbcab384fa343b52743b933661335448f816LABEL version 1.0LABEL description "Test image for labels"

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

LABEL securitytxt="http://personeltest.ru/aways/www.example.com/.well-known/security.txt"

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

Не использовать автоматическое обновление компонентов

Использование apt-get upgrade,yum update может привести к тому, что внутри вашего контейнера будет произведена установка неизвестного вам ранее ПО, либо ПО уязвимой версии. Чтобы избежать этого, устанавливайте пакеты, четко указывая версию для каждого из них. Каждая версия должна проверяться на наличие в ней уязвимостей до того, как компонент окажется внутри вашего контейнера. Версия компонента может быть проверена с помощью инструментов класса Software Composition Analysis (SCA).

Пример установки компонента с точностью до версии:

RUN apt-get install cowsay=3.03+dfsg1-6

Производить скачивание пакетов безопасным образом

Использование curl и wget без мер предосторожности позволяет злоумышленнику выполнять скачивание нежелательных компонентов с неизвестных ресурсов (атака "человек-посередине", при которой злоумышленник может перехватить незащищенный трафик и подменить скачиваемый нами пакет на зловредный). Это полностью разрушает концепцию Zero trust, согласно которой необходимо проверять любое подключение или действие до предоставления доступа (или в данном случае установки компонента с неизвестного ресурса). Соответственно следующий сценарий скачивания будет являться грубейшей ошибкой, так как происходит выполнение скрипта, полученного из недоверенного источника по небезопасному каналу без должных проверок:

RUN wget http://somesite.com/some-packet/install.sh | sh

Чтобы убедиться, что скаченный компонент будет являться действительно тем, что мы ожидаем, в качестве решения может подойти использование GNU Privacy Guard (GPG). Разберемся, как это работает.

В большинстве случаев вендоры вместе с библиотекой или ПО предоставляют также хеш-сумму, которую мы можем проверить при скачивании. Данная хеш-сумма подписывается вендорами с помощью закрытого ключа в рамках GPG, а открытые ключи помещаются в репозитории. Следующий пример демонстрирует, как может выглядеть безопасное скачивание компонентов Node.js:

RUN gpg --keyserver pool.sks-keyservers.net \--recv-keys 7937DFD2AB06298B2293C3187D33FF9D0246406D \            114F43EE0176B71C7BC219DD50A3051F888C628DENV NODE_VERSION 0.10.38ENV NPM_VERSION 2.10.0RUN curl -SLO "http://nodejs.org/dist/v$NODE_VERSION/node-v \$NODE_VERSION-linux-x64.tar.gz" \&& curl -SLO "http://nodejs.org/dist/v$NODE_VERSION/\SHASUMS256.txt.asc" \&& gpg --verify SHASUMS256.txt.asc \&& grep " node-v$NODE_VERSION-linux-x64.tar.gz$" SHASUMS256.txt.asc | sha256sum -c -

Давайте разберемся, что здесь происходит:

  1. Получение открытых GPG-ключей

  2. Скачивание Node.js пакета

  3. Скачивание хеш-суммы Node.js пакета на базе алгоритма SHA256

  4. Использование GPG-клиента для проверки, что хеш-сумма подписана тем, кто владеет закрытыми ключами

  5. Проверяем, что вычисленная хеш-сумма от пакета совпадает со скаченной с помощью sha256sum

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

Иногда разработчикам бывает необходимо использовать сторонние репозитории для установки компонента с помощью deb или rpm. В данном случае мы также можем воспользоваться GPG, а проверка хеш-суммы будет проводиться менеджерами пакетов при скачивании.

Пример безопасного добавления GPG-ключей вместе с источником пакетов.

RUN apt-key adv --keyserver hkp://pgp.mit.edu:80 \--recv-keys 573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62RUN echo "deb http://nginx.org/packages/mainline/debian/\jessie nginx" >> /etc/apt/sources.list

В случае, если для пакетов цифровая подпись в явном виде не предоставляется вендором, ее необходимо предварительно создать и сравнить в рамках сборки.

Пример для SHA256:

RUN curl -sSL -o redis.tar.gz \http://download.redis.io/releases/redis-3.0.1.tar.gz \&& echo "0e21be5d7c5e6ab6adcbed257619897db59be9e1ded7ef6fd1582d0cdb5e5bb7 \*redis.tar.gz" | sha256sum -c -

Не использовать ADD

Инструкция ADD, получив в качестве параметра путь к архиву, автоматически распакует этот архив при своем выполнении. Это может привести, в свою очередь, к появлению zip-бомбы внутри контейнера. При распаковке zip-бомба может вызвать отказ в обслуживании (DoS) приложения, путем заполнения всего выделенного свободного места.

Еще одна небольшая особенность ADD команды заключается в том, что вы можете передать ей URL в качестве параметра, и она будет извлекать контент во время сборки, что также может привести к атаке "человек-посередине":

ADD https://cloudberry.engineering/absolutely-trust-me.tar.gz

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

Задавать USER в конце Dockerfile

В случае, если злоумышленнику удастся заполучить shell внутри вашего контейнера, он может оказаться root'ом, что сильно упростит дальнейшую атаку с выходом за пределы контейнера. Чтобы избежать этого, указывайте пользователя в явном виде через инструкцию USER. Однако это работает только в том случае, если вашему приложению не требуется привилегии root после сборки.

RUN groupadd -r user_grp &amp;&amp; useradd -r -g user_grp userUSER user

Использовать gosu вместо sudo в процессе инициализации

Утилита gosu будет полезна, когда необходимо предоставлять root доступ после сборки Dockerfile во время инициализации, но при этом приложение должно запускаться в непривилегированном режиме.

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

#!/bin/bashset -eif [ "$1" = 'redis-server' ];  then    chown -R redis .     exec gosu redis "$@"  fiexec "$@"

Основная цель инструмента - запуск процессов от определенного пользователя, но в отличие от sudo и su, gosu не делает fork процессов, как показано ниже:

$ docker run -it --rm ubuntu:trusty su -c 'exec ps aux'USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMANDroot         1  0.0  0.0  46636  2688 ?        Ss+  02:22   0:00 su -c exec ps aroot         6  0.0  0.0  15576  2220 ?        Rs   02:22   0:00 ps aux$ docker run -it --rm ubuntu:trusty sudo ps auxUSER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMANDroot         1  3.0  0.0  46020  3144 ?        Ss+  02:22   0:00 sudo ps auxroot         7  0.0  0.0  15576  2172 ?        R+   02:22   0:00 ps aux$ docker run -it --rm -v $PWD/gosu-amd64:/usr/local/bin/gosu:ro ubuntu:trusty gosu root ps auxUSER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMANDroot         1  0.0  0.0   7140   768 ?        Rs+  02:22   0:00 ps aux

Это сохраняет концепцию, согласно которой контейнер ассоциируется с единственным процессом. Тем не менее, из слов разработчиков gosu не является заменой sudo, так как в рамках данного fork'а происходит взаимодействие с Linux PAM через функции pam_open_session() и pam_close_session(). Использование gosu вместо sudo за пределами процесса инициализации может привести к некорректной работе приложения.

Distroless images и минимальные образы

Можно сильно сократить поверхность атаки злоумышленника отказавшись от базовых образов Linux-дистрибутивов (Ubuntu, Debian, Alpine) и перейдя на Disroless-образы. Это образы, которые содержат только само приложение и необходимые для него зависимости без использования лишних системных компонентов (например, bash). Одним из явных преимуществ, помимо сокращения поверхности атаки и возможностей злоумышленника, является снижение размера образа. Это в свою очередь уменьшает "шум" в результатах сканирования такими инструментами как Trivy, Clair и так далее.

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

Цикл статей по созданию минимального образа:

Одним из вариантов, как можно эффективно сократить размер образа является multi-stage сборка, которая ко всему прочему поможет безопасно работать с секретами. Об этом будет в следующем подразделе.

Полезным инструментом также является Docker-slim, позволяющий уменьшить размер написанного образа.

Безопасная работа с секретами

Секреты могут по ошибке помещаться в качестве параметра инструкции ENV или передаваться внутрь образа как текстовый файл. Также секреты могут скачиваться через wget. Подобные сценарии недопустимы, так как злоумышленник может с легкостью получить доступ к секретам. Это можно сделать, например, получив доступ к API Docker:

# docker inspect ubuntu -f {{json .Config.Env}}["SECRET=mypassword", ...]

Также злоумышленник может получить доступ к секретам через логи, директорию /proc или при утечки файлов исходных кодов. В данном случае лучше всего может подойти использование решений класса Vault, например, HashiCorp Vault или Conjur, однако рассмотрим и другие методы.

Соблюдать принцип многоэтапных сборок

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

#builderFROM ubuntu as intermediateWORKDIR /appCOPY secret/key /tmp/RUN scp -i /tmp/key build@acme/files .#runnerFROM ubuntuWORKDIR /appCOPY --from=intermediate /app .

Один из минусов этого сценария - сложное кэширование, что ведет к замедлению сборки.

Работа с секретами через BuildKit

С версии Docker 18.09 появляется экспериментальная интеграция со сборщиком BuildKit, которая позволит кроме увеличения производительности, эффективно работать с секретами.

Пример синтаксиса:

# syntax = docker/dockerfile:1.0-experimentalFROM alpine# shows secret from default secret locationRUN --mount=type=secret,id=mysecret cat /run/secrets/mysecre# shows secret from custom secret locationRUN --mount=type=secret,id=mysecret,dst=/foobar cat /foobar

После выполнения сборки с помощью buildkit с указанием ключа --secret секретная информация не сохранится в конечном образе.

Пример:

$ docker build --no-cache --progress=plain --secret id=mysecret,src=mysecret.txt .

Подробнее можно прочитать в официальной документации Docker.

Остерегаться рекурсивного копирования

Команда из примера ниже может привести к тому, что файл секрета случайно окажется внутри вашего образа, если находится внутри той же директории. Если вы имеете подобные файлы, не забывайте использовать .dockerignore. Среди файлов внутри образа могут оказаться .git, .aws, .env.

COPY . .

Один из примеров подобных ошибок - кейс с утечкой исходных кодов Twitter Vine. Так в 2016 году специалист по информационной безопасности обнаружил на DockerHub образ vinewww, внутри которого обнаружились исходные коды сервиса Vine, ключи API и секреты сторонних сервисов.

Анализаторы Dockerfile

Используйте анализаторы Dockerfile, которые можно встроить в ваш пайплайн сборки. Это могут быть:

Hadolint - простой линтер Dockerfile. Большая часть проверок не относится к security и основана на официальных рекомендациях Docker (ссылка). Однако для наиболее распространенных ошибок таких проверок будет достаточно.

Conftest - анализатор файлов конфигураций, в том числе Dockerfile. Для проверки Dockerfile требуется предварительно написать правила на языке Rego, который, помимо этого, используется в быстро набирающей технологии Open Policy Agent для защиты облачных сред в процессе эксплуатации. Это позволит сохранить вариативность, возможность кастомизации и не потребует изучения ранее неизвестных языков. Conftest не содержит встроенный набор правил, поэтому подразумевается, что вы будете писать их самостоятельно. В качестве отправной точки можно использовать эту статью.

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

Способы и примеры внедрения утилит для проверки безопасности Docker.

Практика

Вы можете попробовать проэксплуатировать распространенные уязвимости при написании Dockerfile с помощью образа Pentest-in-Docker. Пошаговое описание можно найти в репозитории. Одна из главных ошибок - использование debian:wheazy, старого образа Debian, на котором поддерживается не требующийся для работы приложения Bash, содержащий в себе уязвимость удаленного выполнения кода (RCE). Таким образом злоумышленник может получить доступ к сервисной учетной записи www-data, отправив запрос на подключение к reverse-shell. Вторая ошибка - использование sudo, что позволяет злоумышленнику повысить привилегии от www-data до root внутри контейнера. И наконец отсутствие USER в конце Dockerfile из-за чего злоумышленник может выполнять действия из-под root в случае, если получит доступ к API Docker.

Дополнительные ресурсы

Подробнее..

Espressif IoT Development Framework 71 выстрел в ногу

20.01.2021 16:09:28 | Автор: admin

0790_Espressif_IoT_Development_Framework_ru/image1.png
Один из наших читателей обратил наше внимание на Espressif IoT Development Framework. Он нашёл ошибку в коде проекта и поинтересовался, смог бы её найти статический анализатор PVS-Studio. Именно эту ошибку анализатор пока найти не может, зато нашёл множество других. По мотивам этой истории и найденных ошибок, мы решили написать классическую статью про проверку открытого проекта. Приятного изучения того, из-за чего IoT устройства могут "выстрелить вам в ногу".


Программно-аппаратные системы


Отец языка C++ Бьярне Страуструп как-то сказал:


"Си" позволяет очень просто выстрелить себе в ногу. На "Си++" сделать это сложнее, но, когда вы это делаете, отрывает всю ногу.

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


Такие проекты, как Espressif IoT Development Framework, служат для реализации программно-аппаратных систем, взаимодействующих с человеком и управляющие объектами в реальном мире. Всё это накладывает дополнительные требования к качеству и надёжности программного кода. Именно отсюда берут основы такие стандарты как MISRA или AUTOSAR. Впрочем, это уже другая тема.


Вернёмся к Espressif IoT Development Framework (исходный код на сайте GitHub: esp-idf). Вот его краткое описание:


ESP-IDF is Espressif's official IoT Development Framework for the ESP32 and ESP32-S series of SoCs. It provides a self-sufficient SDK for any generic application development on these platforms, using programming languages such as C and C++. ESP-IDF currently powers millions of devices in the field, and enables building a variety of network-connected products, ranging from simple light bulbs and toys to big appliances and industrial devices.

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


Предыстория


Ещё хочется рассказать, как появилась эта статья. Мне написал Юрий Попов (Hardcore IoT fullstack dev & CTO), который с интересом следит за нашими публикациями. Незадолго до этого он самостоятельно вручную нашёл ошибку в Espressif IoT Development Framework и поинтересовался, может ли выявить этот дефект PVS-Studio. Ошибка связана с опечаткой коде, а PVS-Studio всегда славился тем, что хорошо выявляет подобные ошибки.


Некорректный код находился в файле mdns.c:


mdns_txt_linked_item_t * txt = service->txt;while (txt) {  data_len += 2 + strlen(service->txt->key) + strlen(service->txt->value);  txt = txt->next;}

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


Правильный код:


data_len += 2 + strlen(txt->key) + strlen(txt->value);

К обоюдному разочарованию меня и читателя Юры, PVS-Studio не смог заметить эту ошибку. Он просто не знает про такой паттерн ошибки. Собственно, и наша команда не знала про такой паттерн. PVS-Studio, как и любой другой анализатор, умеет замечать только то, на что его запрограммировали :).


Что ж, жаль, но не страшно. Это один из источников, где можно черпать идеи по развитию PVS-Studio. Пользователи и клиенты присылают различные паттерны ошибок, которые они выявили в коде своих проектов, но про которые не знает PVS-Studio. И мы постепенно создаём новые диагностические правила. Так произойдёт и с рассмотренным выше паттерном. Мы уже выписали этот пример в TODO и реализуем новое диагностическое правило для выявления схожих ситуаций в одной из следующих версий анализатора.


По итогам всего этого, Юра сам написал небольшую заметку про эту ошибку, как он её искал и про PVS-Studio: "Баг в ESP-IDF: MDNS, Wireshark и при чём тут единороги". Плюс он уведомил авторов проекта о найденной ошибке: Spurious MDNS collision detection (IDFGH-4263).


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


Правда проверку мы повели достаточно неуклюже. К сожалению, нет примера "собрать всё". Ну или мы не разобрались. Мы начали с getting_started\hello_world. Вроде бы он использует часть фреймворка, но не полностью. Так что можно найти и другие ошибки, добившись компиляции большего количества файлов фреймворка. Другими словами, то, что в статье будет описана только 71 ошибка, это наша недоработка :).


Надо понимать, что у меня не было задачи найти как можно больше ошибок. Поэтому, когда я бегло пробежался по неполному отчёту, я сразу понял, что материала и так более чем достаточно для статьи. Поэтому я поленился углубляться дальнейшее изучение проекта.


К счастью, у Юрия Попова, который заварил всю эту кашу, гораздо больше энтузиазма, чем у меня. Он сообщил, что смог добиться более полной компиляции фреймворка и проверил гораздо больше файлов. И, очень вероятно, вслед за моей статьей, выйдет его статья, где он рассмотрит дополнительную порцию ошибок.


Примеры, откуда берутся ложные/бессмысленные срабатывания


Всех исследователей, которые захотят проверить Espressif IoT Development Framework, я хочу предупредить, что понадобится предварительная настройка анализатора. Без неё вы утоните в большом количестве ложных/бесполезных срабатываний. Но анализатор не виноват.


В коде проекта очень активно используются директивы условной компиляции (#ifdef) и макросы. Такой стиль кодирования запутывает анализатор и порождает множество однотипных бесполезных предупреждений. Чтобы было понятнее, как и почему это происходит, рассмотрим пару примеров.


Предупреждение PVS-Studio: V547 Expression 'ret != 0' is always true. esp_hidd.c 45


esp_err_t esp_hidd_dev_init(....){  esp_err_t ret = ESP_OK;  ....  switch (transport) {#if CONFIG_GATTS_ENABLE  case ESP_HID_TRANSPORT_BLE:    ret = esp_ble_hidd_dev_init(dev, config, callback);    break;#endif /* CONFIG_GATTS_ENABLE */  default:    ret = ESP_FAIL;    break;  }  if (ret != ESP_OK) {    free(dev);    return ret;  }  ....}

Выбран такой режим компиляции, при котором макрос CONFIG_GATTS_ENABLE не объявлен. Поэтому, для анализатора этот код выглядит так:


esp_err_t ret = ESP_OK;....switch (transport) {default:  ret = ESP_FAIL;  break;}if (ret != ESP_OK) {

Анализатор вроде прав, что условие всегда истинно. С другой стороны, пользы от этого предупреждения нет, так как мы понимаем, что код совершенно корректен и имеет смысл. Подобные ситуации встречаются крайне часто, что затрудняет просмотр отчёта. Это такая неприятная плата за активное использование условной компиляции :).


Рассмотрим другой пример. В коде активно используется своя разновидность assert-макросов. К сожалению, они тоже сбивают анализатор с толку. Предупреждение PVS-Studio: V547 Expression 'sntp_pcb != NULL' is always true. sntp.c 664


#define LWIP_PLATFORM_ASSERT(x) do \  {printf("Assertion \"%s\" failed at line %d in %s\n", \    x, __LINE__, __FILE__); fflush(NULL); abort();} while(0)#ifndef LWIP_NOASSERT#define LWIP_ASSERT(message, assertion) do { if (!(assertion)) { \  LWIP_PLATFORM_ASSERT(message); }} while(0)#else  /* LWIP_NOASSERT */#define LWIP_ASSERT(message, assertion)#endif /* LWIP_NOASSERT */sntp_pcb = udp_new_ip_type(IPADDR_TYPE_ANY);LWIP_ASSERT("Failed to allocate udp pcb for sntp client", sntp_pcb != NULL);if (sntp_pcb != NULL) {

Анализатор видит, что код в которой раскрывается LWIP_ASSERT остановит выполнение программы (см. вызов функции abort), если указатель sntp_pcb будет нулевой. Поэтому PVS-Studio предупреждает, что следующая проверка (sntp_pcb != NULL) не имеет смысла.


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


Впрочем, всё это не страшно. Если взяться за проект всерьез, то можно настроить анализатор так, что большинство бессмысленных сообщений исчезнет. Ещё в ряде мест можно улучшить ситуацию, изменив стиль написания кода и макросов. Но это уже выходит за рамки данной статьи. Дополнительно можно использовать механизм подавления предупреждений в конкретных местах, в макросах и т.д. Есть ещё и механизм массовой разметки. Про всё это подробнее можно почитать в статье "Как внедрить статический анализатор кода в legacy проект и не демотивировать команду".


Security


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


Для удобства классификации слабостей кода можно использовать CWE (Common Weakness Enumeration). В PVS-Studio можно включить отображение CWE ID для предупреждений. Для предупреждений этой главы я дополнительно приведу соответствующий CWE ID.


Подробнее тема поиска потенциальных уязвимостей раскрыта в статье "Статический анализатор кода PVS-Studio как защита от уязвимостей нулевого дня".


Ошибка N1; Порядок аргументов


Предупреждение PVS-Studio: V764 Possible incorrect order of arguments passed to 'crypto_generichash_blake2b__init_salt_personal' function: 'salt' and 'personal'. blake2b-ref.c 457


int blake2b_init_salt_personal(blake2b_state *S, const uint8_t outlen,                               const void *personal, const void *salt);intblake2b_salt_personal(uint8_t *out, const void *in, const void *key,                      const uint8_t outlen, const uint64_t inlen,                      uint8_t keylen, const void *salt, const void *personal){  ....  if (blake2b_init_salt_personal(S, outlen, salt, personal) < 0)    abort();  ....}

При вызове функции blake2b_init_salt_personal перепутаны местами аргументы personal и salt. Мне кажется, вряд ли это задумано специально и, скорее всего, это ошибка, возникшая по невнимательности. Я не ориентируюсь в коде проекта и в криптографии, но что-то мне подсказывает, что такая путаница может иметь нехорошие последствия.


Согласно CWE эта ошибка классифицируется как CWE-683: Function Call With Incorrect Order of Arguments.


Ошибка N2; Отбрасывание значащих бит


Предупреждение PVS-Studio: V642 Saving the 'memcmp' function result inside the 'unsigned char' type variable is inappropriate. The significant bits could be lost breaking the program's logic. mbc_tcp_master.c 387


static esp_err_t mbc_tcp_master_set_request(  char* name, mb_param_mode_t mode, mb_param_request_t* request,  mb_parameter_descriptor_t* reg_data){  ....  // Compare the name of parameter with parameter key from table  uint8_t comp_result = memcmp((const char*)name,                               (const char*)reg_ptr->param_key,                               (size_t)param_key_len);  if (comp_result == 0) {  ....}

Сохранять результат работы функции memcmp в однобайтовую переменную это очень плохо. Это дефект, который вполне может превратиться в реальную уязвимость, подобную этой: CVE-2012-2122. Подробнее, почему так писать нельзя, описано в документации к диагностике V642.


Если совсем кратко, то некоторые реализации функция memset могут возвращать в случае несовпадения блоков памяти не только значения 1 или -1. Функция, например, может вернуть значение 1024. А это число, записанное в переменную типа uint8_t превратится в 0.


Согласно CWE эта ошибка классифицируется как CWE-197: Numeric Truncation Error.


Ошибка N3 N20; Приватные данные остаются в памяти


Предупреждение PVS-Studio: V597 The compiler could delete the 'memset' function call, which is used to flush 'prk' buffer. The memset_s() function should be used to erase the private data. dpp.c 854


#ifndef os_memset#define os_memset(s, c, n) memset(s, c, n)#endifstatic int dpp_derive_k1(const u8 *Mx, size_t Mx_len, u8 *k1,       unsigned int hash_len){  u8 salt[DPP_MAX_HASH_LEN], prk[DPP_MAX_HASH_LEN];  const char *info = "first intermediate key";  int res;  /* k1 = HKDF(<>, "first intermediate key", M.x) */  /* HKDF-Extract(<>, M.x) */  os_memset(salt, 0, hash_len);  if (dpp_hmac(hash_len, salt, hash_len, Mx, Mx_len, prk) < 0)    return -1;  wpa_hexdump_key(MSG_DEBUG, "DPP: PRK = HKDF-Extract(<>, IKM=M.x)",      prk, hash_len);  /* HKDF-Expand(PRK, info, L) */  res = dpp_hkdf_expand(hash_len, prk, hash_len, info, k1, hash_len);  os_memset(prk, 0, hash_len);             // <=  if (res < 0)    return -1;  wpa_hexdump_key(MSG_DEBUG, "DPP: k1 = HKDF-Expand(PRK, info, L)",                  k1, hash_len);  return 0;}

Очень распространённая ошибка. Компилятор вправе в целях оптимизации удалить вызов функции memset, так как после заполнения буфера нулями, он больше не используется. В результате приватные данные на самом деле не затираются, а продолжат болтаться где-то в памяти. Подробности можно узнать в статье "Безопасная очистка приватных данных".


Согласно CWE эта ошибка классифицируется как CWE-14: Compiler Removal of Code to Clear Buffers.


Другие ошибки этого типа:


  • V597 The compiler could delete the 'memset' function call, which is used to flush 'prk' buffer. The memset_s() function should be used to erase the private data. dpp.c 883
  • V597 The compiler could delete the 'memset' function call, which is used to flush 'prk' buffer. The memset_s() function should be used to erase the private data. dpp.c 942
  • V597 The compiler could delete the 'memset' function call, which is used to flush 'psk' buffer. The memset_s() function should be used to erase the private data. dpp.c 3939
  • V597 The compiler could delete the 'memset' function call, which is used to flush 'prk' buffer. The memset_s() function should be used to erase the private data. dpp.c 5729
  • V597 The compiler could delete the 'memset' function call, which is used to flush 'Nx' buffer. The memset_s() function should be used to erase the private data. dpp.c 5934
  • V597 The compiler could delete the 'memset' function call, which is used to flush 'val' buffer. The memset_s() function should be used to erase the private data. sae.c 155
  • V597 The compiler could delete the 'memset' function call, which is used to flush 'keyseed' buffer. The memset_s() function should be used to erase the private data. sae.c 834
  • V597 The compiler could delete the 'memset' function call, which is used to flush 'keys' buffer. The memset_s() function should be used to erase the private data. sae.c 838
  • V597 The compiler could delete the 'memset' function call, which is used to flush 'pkey' buffer. The memset_s() function should be used to erase the private data. des-internal.c 422
  • V597 The compiler could delete the 'memset' function call, which is used to flush 'ek' buffer. The memset_s() function should be used to erase the private data. des-internal.c 423
  • V597 The compiler could delete the 'memset' function call, which is used to flush 'finalcount' buffer. The memset_s() function should be used to erase the private data. sha1-internal.c 358
  • V597 The compiler could delete the 'memset' function call, which is used to flush 'A_MD5' buffer. The memset_s() function should be used to erase the private data. sha1-tlsprf.c 95
  • V597 The compiler could delete the 'memset' function call, which is used to flush 'P_MD5' buffer. The memset_s() function should be used to erase the private data. sha1-tlsprf.c 96
  • V597 The compiler could delete the 'memset' function call, which is used to flush 'A_SHA1' buffer. The memset_s() function should be used to erase the private data. sha1-tlsprf.c 97
  • V597 The compiler could delete the 'memset' function call, which is used to flush 'P_SHA1' buffer. The memset_s() function should be used to erase the private data. sha1-tlsprf.c 98
  • V597 The compiler could delete the 'memset' function call, which is used to flush 'T' buffer. The memset_s() function should be used to erase the private data. sha256-kdf.c 85
  • V597 The compiler could delete the 'memset' function call, which is used to flush 'hash' buffer. The memset_s() function should be used to erase the private data. sha256-prf.c 105

Ошибка N21; Не удаляется буфер с приватными данными


Предупреждение PVS-Studio: V575 The null pointer is passed into 'free' function. Inspect the first argument. sae.c 1185


static int sae_parse_password_identifier(struct sae_data *sae,           const u8 *pos, const u8 *end){  wpa_hexdump(MSG_DEBUG, "SAE: Possible elements at the end of the frame",        pos, end - pos);  if (!sae_is_password_id_elem(pos, end)) {    if (sae->tmp->pw_id) {      wpa_printf(MSG_DEBUG,           "SAE: No Password Identifier included, but expected one (%s)",           sae->tmp->pw_id);      return WLAN_STATUS_UNKNOWN_PASSWORD_IDENTIFIER;    }    os_free(sae->tmp->pw_id);    sae->tmp->pw_id = NULL;    return WLAN_STATUS_SUCCESS; /* No Password Identifier */  }  ....}

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


if (!sae_is_password_id_elem(pos, end)) {  if (sae->tmp->pw_id) {    wpa_printf(MSG_DEBUG,         "SAE: No Password Identifier included, but expected one (%s)",         sae->tmp->pw_id);    os_free(sae->tmp->pw_id);    sae->tmp->pw_id = NULL;    return WLAN_STATUS_UNKNOWN_PASSWORD_IDENTIFIER;  }  return WLAN_STATUS_SUCCESS; /* No Password Identifier */}

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


Согласно CWE, эта формально ошибка классифицируется как CWE-628: Function Call with Incorrectly Specified Arguments. Так её классифицирует PVS-Studio, но, по сути и последствиям, это какая-то другая слабость кода.


Ошибка N22, N23; Использование неинициализированного буфера в качестве ключа


Предупреждение PVS-Studio: V614 Uninitialized buffer 'hex' used. Consider checking the second actual argument of the 'memcpy' function. wps_registrar.c 1657


int wps_build_cred(struct wps_data *wps, struct wpabuf *msg){  ....  } else if (wps->use_psk_key && wps->wps->psk_set) {    char hex[65];    wpa_printf(MSG_DEBUG,  "WPS: Use PSK format for Network Key");    os_memcpy(wps->cred.key, hex, 32 * 2);    wps->cred.key_len = 32 * 2;  } else if (wps->wps->network_key) {  ....}

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


В любом случае этот код нуждается во внимательной проверке.


Согласно CWE, эта ошибка классифицируется как CWE-457: Use of Uninitialized Variable.


Аналогичная ошибка: V614 Uninitialized buffer 'hex' used. Consider checking the second actual argument of the 'memcpy' function. wps_registrar.c 1678


Опечатки и Copy-Paste


Ошибка N24; Copy-Paste классический


Предупреждение PVS-Studio: V523 The 'then' statement is equivalent to the 'else' statement. timer.c 292


esp_err_t timer_isr_register(....){  ....  if ((intr_alloc_flags & ESP_INTR_FLAG_EDGE) == 0) {    intr_source = ETS_TG1_T0_LEVEL_INTR_SOURCE + timer_num;  } else {    intr_source = ETS_TG1_T0_LEVEL_INTR_SOURCE + timer_num;  }  ....}

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


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


Ошибка N25; Не там поставлена скобка


Предупреждение PVS-Studio: V593 Consider reviewing the expression of the 'A = B != C' kind. The expression is calculated as following: 'A = (B != C)'. esp_tls_mbedtls.c 446


esp_err_t set_client_config(....){ .... if ((ret = mbedtls_ssl_conf_alpn_protocols(&tls->conf, cfg->alpn_protos) != 0)) {   ESP_LOGE(TAG, "mbedtls_ssl_conf_alpn_protocols returned -0x%x", -ret);   ESP_INT_EVENT_TRACKER_CAPTURE(tls->error_handle, ERR_TYPE_MBEDTLS, -ret);   return ESP_ERR_MBEDTLS_SSL_CONF_ALPN_PROTOCOLS_FAILED; } ....}

Приоритет оператора сравнения выше, чем приоритет оператора присваивания. Поэтому условие вычисляется следующим образом:


TEMP = mbedtls_ssl_conf_alpn_protocols(....) != 0;if ((ret = TEMP))  PRINT(...., -ret);

В принципе, ошибочная ситуация поймается и обработается в коде, но не так, как задумано. Предполагалось распечатывать статус ошибки, который хранится в переменной ret. Но значение ret всегда будет равно 0 или 1. Поэтому, если что-то пойдёт не так, всегда будет распечатываться только одно значение (-1).


Ошибка возникла из-за того, что не там поставлена скобочка. Правильный код:


if ((ret = mbedtls_ssl_conf_alpn_protocols(&tls->conf, cfg->alpn_protos)) != 0)

Теперь всё будет вычисляться как нужно:


ret = mbedtls_ssl_conf_alpn_protocols(....);if (ret != 0)  PRINT(...., -ret);

Рассмотрим ещё один очень похожий случай.


Ошибка N26; MP_MEM превращается в MP_YES


V593 Consider reviewing the expression of the 'A = B != C' kind. The expression is calculated as following: 'A = (B != C)'. libtommath.h 1660


В начале рассмотрим некоторые константы. Они пригодятся нам чуть ниже.


#define MP_OKAY       0   /* ok result */#define MP_MEM        -2  /* out of mem */#define MP_VAL        -3  /* invalid input */#define MP_YES        1   /* yes response */

Далее следует сказать, что существует функция mp_init_multi, которая может возвращать значения MP_OKAY и MP_MEM:


static int mp_init_multi(mp_int *mp, ...);

И теперь собственно код с ошибкой:


static intmp_div(mp_int * a, mp_int * b, mp_int * c, mp_int * d){  ....  /* init our temps */  if ((res = mp_init_multi(&ta, &tb, &tq, &q, NULL) != MP_OKAY)) {     return res;  }  ....}

Рассмотрим проверку более тщательно:


if ((res = mp_init_multi(....) != MP_OKAY))

Вновь не там поставлена скобка. Поэтому в начале вычисляется:


TEMP = (mp_init_multi(....) != MP_OKAY);

Значение TEMP может быть только 0 или 1. Этим числам соответствуют константы MB_OKAY и MP_YES.


Далее выполняется присваивание и одновременно проверка:


if ((res = TEMP))   return res;

Видите подвох? Статус ошибки MP_MEM (-2) вдруг превратился в статус MB_YES (1). Последствия предсказать не могу, но ничего хорошего в этом нет.


Ошибка N27; Забыли разыменовать указатель


Предупреждение PVS-Studio: V595 The 'outbuf' pointer was utilized before it was verified against nullptr. Check lines: 374, 381. protocomm.c 374


static int protocomm_version_handler(uint32_t session_id,                                     const uint8_t *inbuf, ssize_t inlen,                                     uint8_t **outbuf, ssize_t *outlen,                                     void *priv_data){    protocomm_t *pc = (protocomm_t *) priv_data;    if (!pc->ver) {        *outlen = 0;        *outbuf = NULL;                                  // <=        return ESP_OK;    }    /* Output is a non null terminated string with length specified */    *outlen = strlen(pc->ver);    *outbuf = malloc(*outlen);                           // <=    if (outbuf == NULL) {                                // <=        ESP_LOGE(TAG, "Failed to allocate memory for version response");        return ESP_ERR_NO_MEM;    }    memcpy(*outbuf, pc->ver, *outlen);    return ESP_OK;}

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


Если указатель pc->ver является нулевым, то функция досрочно завершает свою работу и при этом записывает значение по адресу, хранящегося в указателе outbuf:


*outbuf = NULL;

Запись по этому адресу происходит и далее:


*outbuf = malloc(*outlen);

А не нравится анализатору то, что затем этот указатель проверяется:


if (outbuf == NULL)

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


Правильный код:


*outbuf = malloc(*outlen);if (*outbuf == NULL) {  ESP_LOGE(TAG, "Failed to allocate memory for version response");  return ESP_ERR_NO_MEM;}

Ошибка N28; Повторное присваивание


Предупреждение PVS-Studio: V519 The 'usRegCount' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 186, 187. mbfuncholding.c 187


eMBExceptioneMBFuncReadHoldingRegister( UCHAR * pucFrame, USHORT * usLen ){  ....  USHORT          usRegCount;  ....  usRegCount = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_REGCNT_OFF] << 8 );  usRegCount = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_REGCNT_OFF + 1] );  ....}

Код явно писался методом Copy-Paste. Строчку скопировали, но изменили только частично. По соседству есть вот такой осмысленный код:


usRegCount = ( USHORT )( pucFrame[MB_PDU_FUNC_WRITE_MUL_REGCNT_OFF] << 8 );usRegCount |= ( USHORT )( pucFrame[MB_PDU_FUNC_WRITE_MUL_REGCNT_OFF + 1] );

Видимо, и в рассмотренном коде с ошибкой, следовало в первой строке использовать оператор =, а во второй оператор |=.


Логические ошибки


Ошибка N29 N31; Неправильная работа с кодами возврата (Rare)


Предупреждение PVS-Studio: V547 Expression is always false. linenoise.c 256


static int getColumns(void) {  ....  /* Restore position. */  if (cols > start) {    char seq[32];    snprintf(seq,32,"\x1b[%dD",cols-start);    if (fwrite(seq, 1, strlen(seq), stdout) == -1) {      /* Can't recover... */    }    flushWrite();  }  ....}

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


Суть же самой ошибки в том, что функция fwrite не возвращает статус -1. Это физически невозможно, так как функция fwrite возвращает значение целочисленного типа size_t:


size_t fwrite( const void *restrict buffer, size_t size, size_t count,               FILE *restrict stream );

А вот что возвращает эта функция:


The number of objects written successfully, which may be less than count if an error occurs.

If size or count is zero, fwrite returns zero and performs no other action.

Таким образом, проверка статуса является неверной.


Аналогичные места безобидной неправильной проверки статуса:


  • V547 Expression is always false. linenoise.c 481
  • V547 Expression is always false. linenoise.c 569

Ошибка N32, N33; Неправильная работа с кодами возврата (Medium)


Предупреждение PVS-Studio: V547 Expression is always false. linenoise.c 596


int linenoiseEditInsert(struct linenoiseState *l, char c) {  ....  if (fwrite(&c,1,1,stdout) == -1) return -1;  ....}

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


Аналогичную ошибку можно найти здесь: V547 Expression is always false. linenoise.c 742.


Ошибка N34; Неправильная работа с кодами возврата (Well Done)


Предупреждение PVS-Studio: V547 Expression is always false. linenoise.c 828


static int linenoiseEdit(char *buf, size_t buflen, const char *prompt)  ....  while(1) {    ....    if (fread(seq+2, 1, 1, stdin) == -1) break;    ....  }  ....}

Ошибка в том, что, как и в случае с fwrite, функция fread не возвращает в качестве статуса значение -1.


size_t fread( void *restrict buffer, size_t size, size_t count,              FILE *restrict stream );

Return value

Number of objects read successfully, which may be less than count if an error or end-of-file condition occurs.

If size or count is zero, fread returns zero and performs no other action.

fread does not distinguish between end-of-file and error, and callers must use feof and ferror to determine which occurred.

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


Ошибка N35; Использование оператора || там, где нужен оператор &&


Предупреждение PVS-Studio: V547 Expression is always true. essl_sdio.c 209


esp_err_t essl_sdio_init(void *arg, uint32_t wait_ms){  ....  // Set block sizes for functions 1 to given value (default value = 512).  if (ctx->block_size > 0 || ctx->block_size <= 2048) {    bs = ctx->block_size;  } else {    bs = 512;  }  ....}

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


Итак, перед нами всегда истинное условие. Ведь некая переменная всегда или больше 0 или меньше 2048. Из-за этого размер какого-то блока не будет ограничен значением 512.


Правильный вариант кода:


if (ctx->block_size > 0 && ctx->block_size <= 2048) {  bs = ctx->block_size;} else {  bs = 512;}

Ошибка N35 N38; Переменная не изменяется


Предупреждение PVS-Studio: V547 Expression 'depth <= 0' is always false. panic_handler.c 169


static void print_backtrace(const void *f, int core){  XtExcFrame *frame = (XtExcFrame *) f;  int depth = 100;                                          // <=  //Initialize stk_frame with first frame of stack  esp_backtrace_frame_t stk_frame =    {.pc = frame->pc, .sp = frame->a1, .next_pc = frame->a0};  panic_print_str("\r\nBacktrace:");  print_backtrace_entry(esp_cpu_process_stack_pc(stk_frame.pc),                        stk_frame.sp);  //Check if first frame is valid  bool corrupted =    !(esp_stack_ptr_is_sane(stk_frame.sp) &&      (esp_ptr_executable((void *)esp_cpu_process_stack_pc(stk_frame.pc)) ||       /* Ignore the first corrupted PC in case of InstrFetchProhibited */       frame->exccause == EXCCAUSE_INSTR_PROHIBITED));  //Account for stack frame that's already printed  uint32_t i = ((depth <= 0) ? INT32_MAX : depth) - 1;      // <=  ....}

Переменной depth присваивается значение 100, и до момента проверки этой переменной её значение нигде не изменяется. Это весьма подозрительно. Где-то что-то забыли сделать?


Аналогичные случаи:


  • V547 Expression 'xAlreadyYielded == ((BaseType_t) 0)' is always true. event_groups.c 260
  • V547 Expression 'xAlreadyYielded == ((BaseType_t) 0)' is always true. tasks.c 1475
  • V547 Expression 'xAlreadyYielded == ((BaseType_t) 0)' is always true. tasks.c 1520

Ошибка N39; Использование неинициализированного буфера


Предупреждение PVS-Studio: V614 Potentially uninitialized buffer 'k' used. Consider checking the second actual argument of the 'sae_derive_keys' function. sae.c 854


int sae_process_commit(struct sae_data *sae){  u8 k[SAE_MAX_PRIME_LEN];  if (sae->tmp == NULL ||      (sae->tmp->ec && sae_derive_k_ecc(sae, k) < 0) ||      (sae->tmp->dh && sae_derive_k_ffc(sae, k) < 0) ||      sae_derive_keys(sae, k) < 0)    return ESP_FAIL;  return ESP_OK;}

Ошибка в логике. Предположим, что указатели ec и dh являются нулевыми. В этом случае массив k не инициализируется, но функция sae_derive_keys всё равно начнёт его обрабатывать.


Ошибка N40; Всегда ложное условие


Предупреждение PVS-Studio: V547 Expression 'bit_len == 32' is always false. spi_flash_ll.h 371


static inline void spi_flash_ll_set_usr_address(spi_dev_t *dev, uint32_t addr,                                                int bit_len){  // The blank region should be all ones  if (bit_len >= 32) {    dev->addr = addr;    dev->slv_wr_status = UINT32_MAX;  } else {    uint32_t padding_ones = (bit_len == 32? 0 : UINT32_MAX >> bit_len);    dev->addr = (addr << (32 - bit_len)) | padding_ones;  }}

Как легко увидеть, условие bit_len == 32 всегда даст ложный результат. Возможно, выше следовало написать не больше-или-равно (>=), а просто больше (>).


Ошибка N41; Повторное присваивание


Предупреждение PVS-Studio: V519 The '* pad_num' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 46, 48. touch_sensor_hal.c 48


void touch_hal_get_wakeup_status(touch_pad_t *pad_num){  uint32_t touch_mask = 0;  touch_ll_read_trigger_status_mask(&touch_mask);  if (touch_mask == 0) {    *pad_num = -1;  }  *pad_num = (touch_pad_t)(__builtin_ffs(touch_mask) - 1);}

Код явно ошибочен и возможно здесь не хватает оператора else. Я не уверен, но, возможно, код должен выглядеть так:


void touch_hal_get_wakeup_status(touch_pad_t *pad_num){  uint32_t touch_mask = 0;  touch_ll_read_trigger_status_mask(&touch_mask);  if (touch_mask == 0) {    *pad_num = -1;  } else {    *pad_num = (touch_pad_t)(__builtin_ffs(touch_mask) - 1);  }}

Выход за границу массива


Ошибка N42; Неправильная граничная проверка


Предупреждение PVS-Studio: V557 Array overrun is possible. The value of 'frame->exccause' index could reach 16. gdbstub_xtensa.c 132


int esp_gdbstub_get_signal(const esp_gdbstub_frame_t *frame){  const char exccause_to_signal[] =    {4, 31, 11, 11, 2, 6, 8, 0, 6, 7, 0, 0, 7, 7, 7, 7};  if (frame->exccause > sizeof(exccause_to_signal)) {    return 11;  }  return (int) exccause_to_signal[frame->exccause];}

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


if (frame->exccause >= sizeof(exccause_to_signal)) {

Ошибка N43; Длинный пример ошибки :)


В рассматриваемой функции выход за границу массива может произойти в двух местах, поэтому и предупреждений анализатора сразу два:


  • V557 Array overrun is possible. The value of 'other_if' index could reach 3. mdns.c 2206
  • V557 Array overrun is possible. The '_mdns_announce_pcb' function processes value '[0..3]'. Inspect the first argument. Check lines: 1674, 2213. mdns.c 1674

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


typedef enum mdns_if_internal {    MDNS_IF_STA = 0,    MDNS_IF_AP = 1,    MDNS_IF_ETH = 2,    MDNS_IF_MAX} mdns_if_t;

Обратите внимание, что значение константы MDNS_IF_MAX равно 3.


Теперь взглянем на определение структуры mdns_server_s. Здесь нам важно, что массив interfaces состоит из 3 элементов:


typedef struct mdns_server_s {    struct {        mdns_pcb_t pcbs[MDNS_IP_PROTOCOL_MAX];    } interfaces[MDNS_IF_MAX];    const char * hostname;    const char * instance;    mdns_srv_item_t * services;    SemaphoreHandle_t lock;    QueueHandle_t action_queue;    mdns_tx_packet_t * tx_queue_head;    mdns_search_once_t * search_once;    esp_timer_handle_t timer_handle;} mdns_server_t;mdns_server_t * _mdns_server = NULL;

Это ещё не всё. Нам понадобится заглянуть внутрь функции _mdns_get_other_if. Обратите внимание, что она может вернуть константу MDNS_IF_MAX. Т.е. она может вернуть значение 3.


static mdns_if_t _mdns_get_other_if (mdns_if_t tcpip_if){  if (tcpip_if == MDNS_IF_STA) {    return MDNS_IF_ETH;  } else if (tcpip_if == MDNS_IF_ETH) {     return MDNS_IF_STA;  }  return MDNS_IF_MAX;}

И вот, наконец, мы добрались до ошибок:


static void _mdns_dup_interface(mdns_if_t tcpip_if){    uint8_t i;    mdns_if_t other_if = _mdns_get_other_if (tcpip_if);    for (i=0; i<MDNS_IP_PROTOCOL_MAX; i++) {        if (_mdns_server->interfaces[other_if].pcbs[i].pcb) {        // <=            //stop this interface and mark as dup            if (_mdns_server->interfaces[tcpip_if].pcbs[i].pcb) {                _mdns_clear_pcb_tx_queue_head(tcpip_if, i);                _mdns_pcb_deinit(tcpip_if, i);            }            _mdns_server->interfaces[tcpip_if].pcbs[i].state = PCB_DUP;            _mdns_announce_pcb(other_if, i, NULL, 0, true);          // <=        }    }}

Итак, мы знаем, что функция _mdns_get_other_if может вернуть тройку. Значит переменная other_if может быть равна трём. И вот первый потенциальный выход за границу массива:


if (_mdns_server->interfaces[other_if].pcbs[i].pcb)

Второе место, где опасно используется переменная other_if, - это вызов функции _mdns_announce_pcb:


_mdns_announce_pcb(other_if, i, NULL, 0, true);

Заглянем в эту функцию:


static void _mdns_announce_pcb(mdns_if_t tcpip_if,                               mdns_ip_protocol_t ip_protocol,                               mdns_srv_item_t ** services,                               size_t len, bool include_ip){  mdns_pcb_t * _pcb = &_mdns_server->interfaces[tcpip_if].pcbs[ip_protocol];  ....}

Опять может использоваться индекс 3 для доступа к массиву, состоящего из 3 элементов. А максимальный доступный индекс это двойка.


Нулевые указатели


Ошибка N44 N47; Ошибка очерёдности проверки указателей


Предупреждение PVS-Studio: V595 The 'hapd->wpa_auth' pointer was utilized before it was verified against nullptr. Check lines: 106, 113. esp_hostap.c 106


bool hostap_deinit(void *data){  struct hostapd_data *hapd = (struct hostapd_data *)data;  if (hapd == NULL) {    return true;  }  if (hapd->wpa_auth->wpa_ie != NULL) {    os_free(hapd->wpa_auth->wpa_ie);  }  if (hapd->wpa_auth->group != NULL) {    os_free(hapd->wpa_auth->group);  }  if (hapd->wpa_auth != NULL) {    os_free(hapd->wpa_auth);  }  ....}

Неправильная последовательность проверки указателей:


if (hapd->wpa_auth->group != NULL)....if (hapd->wpa_auth != NULL)

Если указатель hapd->wpa_auth окажется нулевым, то всё плохо. Последовательность действий нужно поменять местами и сделать вложенной:


if (hapd->wpa_auth != NULL){  ....  if (hapd->wpa_auth->group != NULL)  ....}

Аналогичные ошибки:


  • V595 The 'hapd->conf' pointer was utilized before it was verified against nullptr. Check lines: 118, 125. esp_hostap.c 118
  • V595 The 'sm' pointer was utilized before it was verified against nullptr. Check lines: 1637, 1647. esp_wps.c 1637
  • V595 The 'sm' pointer was utilized before it was verified against nullptr. Check lines: 1693, 1703. esp_wps.c 1693

Ошибка N48 N64; Нет проверки указателя после выделения памяти


В целом, в проекте принято проверять, удалось выделить память или нет. Т.е. много кода с подобными проверками:


dhcp_data = (struct dhcp *)malloc(sizeof(struct dhcp));if (dhcp_data == NULL) {  return ESP_ERR_NO_MEM;}

Но местами про проверки забыли.


Предупреждение PVS-Studio: V522 There might be dereferencing of a potential null pointer 'exp'. Check lines: 3470, 3469. argtable3.c 3470


TRex *trex_compile(const TRexChar *pattern,const TRexChar **error,int flags){  TRex *exp = (TRex *)malloc(sizeof(TRex));  exp->_eol = exp->_bol = NULL;  exp->_p = pattern;  ....}

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


Другие места, где отсутствуют проверки:


  • V522 There might be dereferencing of a potential null pointer 's_ledc_fade_rec[speed_mode][channel]'. Check lines: 668, 667. ledc.c 668
  • V522 There might be dereferencing of a potential null pointer 'environ'. Check lines: 108, 107. syscall_table.c 108
  • V522 There might be dereferencing of a potential null pointer 'it'. Check lines: 150, 149. partition.c 150
  • V522 There might be dereferencing of a potential null pointer 'eth'. Check lines: 167, 159. wpa_auth.c 167
  • V522 There might be dereferencing of a potential null pointer 'pt'. Check lines: 222, 219. crypto_mbedtls-ec.c 222
  • V522 There might be dereferencing of a potential null pointer 'attr'. Check lines: 88, 73. wps.c 88
  • V575 The potential null pointer is passed into 'memcpy' function. Inspect the first argument. Check lines: 725, 724. coap_mbedtls.c 725
  • V575 The potential null pointer is passed into 'memset' function. Inspect the first argument. Check lines: 3504, 3503. argtable3.c 3504
  • V575 The potential null pointer is passed into 'memcpy' function. Inspect the first argument. Check lines: 496, 495. mqtt_client.c 496
  • V575 The potential null pointer is passed into 'strcpy' function. Inspect the first argument. Check lines: 451, 450. transport_ws.c 451
  • V769 The 'buffer' pointer in the 'buffer + n' expression could be nullptr. In such case, resulting value will be senseless and it should not be used. Check lines: 186, 181. cbortojson.c 186
  • V769 The 'buffer' pointer in the 'buffer + len' expression could be nullptr. In such case, resulting value will be senseless and it should not be used. Check lines: 212, 207. cbortojson.c 212
  • V769 The 'out' pointer in the 'out ++' expression could be nullptr. In such case, resulting value will be senseless and it should not be used. Check lines: 233, 207. cbortojson.c 233
  • V769 The 'parser->m_bufferPtr' pointer in the expression equals nullptr. The resulting value of arithmetic operations on this pointer is senseless and it should not be used. xmlparse.c 2090
  • V769 The 'signature' pointer in the 'signature + curve->prime_len' expression could be nullptr. In such case, resulting value will be senseless and it should not be used. Check lines: 4112, 4110. dpp.c 4112
  • V769 The 'key' pointer in the 'key + 16' expression could be nullptr. In such case, resulting value will be senseless and it should not be used. Check lines: 634, 628. eap_mschapv2.c 634

Ошибка N65, N66; Нет проверки указателя после выделения памяти (показательный случай)


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


Предупреждение PVS-Studio: V701 realloc() possible leak: when realloc() fails in allocating memory, original pointer 'exp->_nodes' is lost. Consider assigning realloc() to a temporary pointer. argtable3.c 3008


static int trex_newnode(TRex *exp, TRexNodeType type){  TRexNode n;  int newid;  n.type = type;  n.next = n.right = n.left = -1;  if(type == OP_EXPR)    n.right = exp->_nsubexpr++;  if(exp->_nallocated < (exp->_nsize + 1)) {    exp->_nallocated *= 2;    exp->_nodes = (TRexNode *)realloc(exp->_nodes,                                      exp->_nallocated * sizeof(TRexNode));  }  exp->_nodes[exp->_nsize++] = n; // NOLINT(clang-analyzer-unix.Malloc)  newid = exp->_nsize - 1;  return (int)newid;}

Во-первых, если функция realloc вернёт NULL, то будет потеряно предыдущее значение указателя exp->_nodes. Возникнет утечка памяти.


Во-вторых, если функция realloc вернёт NULL, то запись значения произойдёт вовсе не по нулевому указателю. Имеется в виду эта строка:


exp->_nodes[exp->_nsize++] = n;

Значение exp->_nsize++ может быть любым, и, если запись произойдёт в какую-то случайную область памяти, доступную для записи, то программа продолжит своё выполнение, как ни в чём не бывало. При этом будут разрушены структуры данных, что приведёт к непредсказуемым последствиям.


Ещё одна такая ошибка: V701 realloc() possible leak: when realloc() fails in allocating memory, original pointer 'm_context->pki_sni_entry_list' is lost. Consider assigning realloc() to a temporary pointer. coap_mbedtls.c 737


Прочие ошибки


Ошибка N67; Лишний или неверный код


Предупреждение PVS-Studio: V547 Expression 'ret != 0' is always false. sdio_slave.c 394


esp_err_t sdio_slave_start(void){  ....  critical_exit_recv();  ret = ESP_OK;  if (ret != ESP_OK) return ret;  sdio_slave_hal_set_ioready(context.hal, true);  return ESP_OK;}

Это странный код, который можно сократить до:


esp_err_t sdio_slave_start(void){  ....  critical_exit_recv();  sdio_slave_hal_set_ioready(context.hal, true);  return ESP_OK;}

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


Ошибка N68; Лишний или неверный код


Предупреждение PVS-Studio: V547 Expression 'err != 0' is always false. sdio_slave_hal.c 96


static esp_err_t sdio_ringbuf_send(....){  uint8_t* get_ptr = ....;  esp_err_t err = ESP_OK;  if (copy_callback) {    (*copy_callback)(get_ptr, arg);  }  if (err != ESP_OK) return err;  buf->write_ptr = get_ptr;  return ESP_OK;}

В общем-то, всё то же самое, что и в предыдущем случае. Переменная err является лишней или её забыли изменить.


Ошибка N69; Использование потенциально неинициализированного буфера


Предупреждение PVS-Studio: V614 Potentially uninitialized buffer 'seq' used. Consider checking the first actual argument of the 'strlen' function. linenoise.c 435


void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) {    char seq[64];    if (hintsCallback && plen+l->len < l->cols) {        int color = -1, bold = 0;        char *hint = hintsCallback(l->buf,&color,&bold);        if (hint) {            int hintlen = strlen(hint);            int hintmaxlen = l->cols-(plen+l->len);            if (hintlen > hintmaxlen) hintlen = hintmaxlen;            if (bold == 1 && color == -1) color = 37;            if (color != -1 || bold != 0)                snprintf(seq,64,"\033[%d;%d;49m",bold,color);            abAppend(ab,seq,strlen(seq));                       // <=            abAppend(ab,hint,hintlen);            if (color != -1 || bold != 0)                abAppend(ab,"\033[0m",4);            /* Call the function to free the hint returned. */            if (freeHintsCallback) freeHintsCallback(hint);        }    }}

Буфер seq может быть заполнен, а может быть и не заполнен! Он заполняется только при выполнении условия:


if (color != -1 || bold != 0)  snprintf(seq,64,"\033[%d;%d;49m",bold,color);

Логично предположить, что условие может быть не выполнено, и тогда буфер останется неинициализированным. В этом случае его нельзя использовать для добавления к строке ab.


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


if (color != -1 || bold != 0){  snprintf(seq,64,"\033[%d;%d;49m",bold,color);  abAppend(ab,seq,strlen(seq));}

Ошибка N70; Странная маска


Предупреждение PVS-Studio: V547 Expression is always false. tasks.c 896


#ifndef portPRIVILEGE_BIT  #define portPRIVILEGE_BIT ( ( UBaseType_t ) 0x00 )#endifstatic void prvInitialiseNewTask(...., UBaseType_t uxPriority, ....){  StackType_t *pxTopOfStack;  UBaseType_t x;  #if (portNUM_PROCESSORS < 2)  xCoreID = 0;  #endif  #if( portUSING_MPU_WRAPPERS == 1 )    /* Should the task be created in privileged mode? */    BaseType_t xRunPrivileged;    if( ( uxPriority & portPRIVILEGE_BIT ) != 0U )    {      xRunPrivileged = pdTRUE;    }    else    {      xRunPrivileged = pdFALSE;    }  ....}

Константа portPRIVILEGE_BIT имеет значение 0. Поэтому странно использовать его как маску:


if( ( uxPriority & portPRIVILEGE_BIT ) != 0U )

Ошибка N71, Утечка памяти


Предупреждение PVS-Studio: V773 The function was exited without releasing the 'sm' pointer. A memory leak is possible. esp_wpa2.c 753


static int eap_peer_sm_init(void){  int ret = 0;  struct eap_sm *sm;  ....  sm = (struct eap_sm *)os_zalloc(sizeof(*sm));  if (sm == NULL) {    return ESP_ERR_NO_MEM;  }  s_wpa2_data_lock = xSemaphoreCreateRecursiveMutex();  if (!s_wpa2_data_lock) {    wpa_printf(MSG_ERROR, ".......");  // NOLINT(clang-analyzer-unix.Malloc)    return ESP_ERR_NO_MEM;             // <=  }  ....}

Если функция xSemaphoreCreateRecursiveMutex не сможет создать мьютекс, то функция eap_peer_sm_init завершит свою работу и при этом произойдёт утечка памяти. Как я понимаю, следует добавить вызов функции os_free для очистки памяти:


  s_wpa2_data_lock = xSemaphoreCreateRecursiveMutex();  if (!s_wpa2_data_lock) {    wpa_printf(MSG_ERROR, ".......");    os_free(sm);    return ESP_ERR_NO_MEM;  }

Что интересно, компилятор Clang тоже предупреждает об этой ошибке. Однако автор кода почему-то проигнорировал и даже специально подавил соответствующее предупреждение:


// NOLINT(clang-analyzer-unix.Malloc)

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


Заключение


Спасибо за внимание. Как видите, ошибок весьма много. А это ведь был только беглый просмотр неполного отчёта. Надеюсь, Юрий Попов примет эстафету и опишет ещё больше ошибок в своей последующей статье :).


Используйте статический анализатор PVS-Studio регулярно. Это позволит:


  1. Находить многие ошибки на раннем этапе, что существенно сократит расходы на их обнаружение и исправление;
  2. Находя и исправляя глупые опечатки и прочие ляпы с помощью статического анализа, вы высвободите время, которое можно потратить на более высокоуровневый обзор кода и алгоритмов;
  3. Лучше контролировать качество кода новичков и быстрее обучать их писать красивый надежный код;
  4. Если речь идёт о программном обеспечении для встраиваемых устройств, то очень важно устранить как можно больше ошибок до выпуска устройств в эксплуатацию. Поэтому любая дополнительно найденная ошибка с помощью анализатора кода, это здорово. Каждая незамеченная ошибка в программно-аппаратном устройстве потенциально несёт репутационные риски и затраты на обновление прошивок.

Приглашаю скачать и попробовать демонстрационную версию анализатора PVS-Studio. Также напоминаю, что если вы разрабатываете открытый проект или используете анализатор в академических целях, то для таких случаев мы предлагаем несколько вариантов бесплатных лицензий. Не ждите, когда коварный баг съест вашу ногу, начните использовать PVS-Studio прямо сейчас.


Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov. Espressif IoT Development Framework: 71 Shots in the Foot.

Подробнее..

Обработка дат притягивает ошибки или 77 дефектов в Qt 6

16.02.2021 22:10:00 | Автор: admin

PVS-Studio проверяет Qt 6
Относительно недавно состоялся релиз фреймворка Qt 6, и это стало поводом вновь проверить его с помощью PVS-Studio. В статье будут рассмотрены различные интересные ошибки, например, связанные с обработкой дат. Обнаружение всех этих ошибок хорошо демонстрирует пользу, которую может получить проект от использования таких инструментов, как PVS-Studio, особенно если они применяются регулярно.


Это классическая статья о проверке открытого проекта, которая пополнит нашу "доказательную базу" полезности и эффективности использования PVS-Studio для контроля качества кода. Хотя мы уже писали про поверку проекта Qt (в 2011, в 2014 и в 2018), очень полезно сделать это вновь. Так, мы на практике демонстрируем простую, но очень важную мысль: статический анализ должен применяться регулярно!


Наши статьи показывают, что анализатор PVS-Studio умеет находить множество разнообразнейших ошибок. И, как правило, авторы проектов быстро исправляют описанные нами ошибки. Однако всё это не имеет ничего общего с правильной и полезной методикой регулярного применения статических анализаторов. Когда анализатор встроен в процесс разработки, это позволяет быстро находить ошибки в новом или изменённом коде и тем самым их исправление обходится максимально дёшево.


Всё, теория заканчивается. Давайте посмотрим, что интересного нас ждёт в коде. А пока вы будете читать статью, предлагаю скачать PVS-Studio и запросить демонстрационный ключ, чтобы посмотреть, что интересного найдётся в ваших собственных проектах :).


Даты


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


Встретились ошибки обработки дат и в Qt. Давайте с них и начнём.


Фрагмент N1: неправильная интерпретация статуса ошибки


Для начала нам следует посмотреть, как устроена функция, возвращающая номер месяца по его сокращённому названию.


static const char qt_shortMonthNames[][4] = {    "Jan", "Feb", "Mar", "Apr", "May", "Jun",    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};static int fromShortMonthName(QStringView monthName){  for (unsigned int i = 0;       i < sizeof(qt_shortMonthNames) / sizeof(qt_shortMonthNames[0]); ++i)  {    if (monthName == QLatin1String(qt_shortMonthNames[i], 3))      return i + 1;  }  return -1;}

В случае успеха функция возвращает номер месяца (значение от 1 до 12). Если имя месяца некорректно, то функция возвращает отрицательное значение (-1). Обратите внимание, что функция не может вернуть значение 0.


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


QDateTime QDateTime::fromString(QStringView string, Qt::DateFormat format){  ....  month = fromShortMonthName(parts.at(1));  if (month)    day = parts.at(2).toInt(&ok);  // If failed, try day then month  if (!ok || !month || !day) {    month = fromShortMonthName(parts.at(2));    if (month) {      QStringView dayPart = parts.at(1);      if (dayPart.endsWith(u'.'))        day = dayPart.chopped(1).toInt(&ok);    }  }  ....}

Проверки номера месяца на равенство нулю никогда не сработают, и программа продолжит выполняться с некорректным отрицательным номером месяца. Анализатор PVS-Studio видит здесь целый букет несостыковок, о чем сообщает сразу четырьмя сообщениями:


  • V547 [CWE-571] Expression 'month' is always true. qdatetime.cpp 4907
  • V560 [CWE-570] A part of conditional expression is always false: !month. qdatetime.cpp 4911
  • V547 [CWE-571] Expression 'month' is always true. qdatetime.cpp 4913
  • V560 [CWE-570] A part of conditional expression is always false: !month. qdatetime.cpp 4921

Фрагмент N2: ошибка логики обработки даты


Для начала посмотрим на реализацию функции, возвращающей количество секунд.


enum {  ....  MSECS_PER_DAY = 86400000,  ....  SECS_PER_MIN = 60,};int QTime::second() const{    if (!isValid())        return -1;    return (ds() / 1000)%SECS_PER_MIN;}

Рассмотренная функция может вернуть значение в диапазоне [0..59] или статус ошибки -1.


В одном месте эта функция используется очень странным образом:


static qint64 qt_mktime(QDate *date, QTime *time, ....){  ....  } else if (yy == 1969 && mm == 12 && dd == 31             && time->second() == MSECS_PER_DAY - 1) {      // There was, of course, a last second in 1969, at time_t(-1); we won't      // rescue it if it's not in normalised form, and we don't know its DST      // status (unless we did already), but let's not wantonly declare it      // invalid.  } else {  ....}

Предупреждение PVS-Studio: V560 [CWE-570] A part of conditional expression is always false: time->second() == MSECS_PER_DAY 1. qdatetime.cpp 2488


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


Ошибочно вот это сравнение:


time->second() == MSECS_PER_DAY - 1

MSECS_PER_DAY 1 это 86399999. Функция second, как мы уже знаем, никак не может вернуть такое значение. Таким образом, здесь какая-то логическая ошибка и код заслуживает пристального внимания разработчиков.


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


Опечатки


Фрагмент N3: неожиданно, мы поговорим о HTML!


QString QPixelTool::aboutText() const{  const QList<QScreen *> screens = QGuiApplication::screens();  const QScreen *windowScreen = windowHandle()->screen();  QString result;  QTextStream str(&result);  str << "<html></head><body><h2>Qt Pixeltool</h2><p>Qt " << QT_VERSION_STR    << "</p><p>Copyright (C) 2017 The Qt Company Ltd.</p><h3>Screens</h3><ul>";  for (const QScreen *screen : screens)    str << "<li>" << (screen == windowScreen ? "* " : "  ")        << screen << "</li>";  str << "<ul></body></html>";  return result;}

Предупреждение PVS-Studio: V735 Possibly an incorrect HTML. The "</ body>" closing tag was encountered, while the "</ ul>" tag was expected. qpixeltool.cpp 707


В PVS-Studio есть диагностики, которые не только проверят сам код, но и выискивают аномалии в строковых константах. Здесь как раз сработала одна из таких диагностик. Это достаточно редкий случай, зато этим он и примечательный.


Дважды используется тег открытия списка. Это явная опечатка. Первый тег, должен открывать список, а второй закрывать. Правильный код:


str << "</ul></body></html>";

Фрагмент N4: повторная проверка в условии


class Node{  ....  bool isGroup() const { return m_nodeType == Group; }  ....};void DocBookGenerator::generateDocBookSynopsis(const Node *node){  ....  if (node->isGroup() || node->isGroup()      || node->isSharedCommentNode() || node->isModule()      || node->isJsModule() || node->isQmlModule() || node->isPageNode())    return;  ....}

Предупреждение PVS-Studio: V501 [CWE-570] There are identical sub-expressions to the left and to the right of the '||' operator: node->isGroup() || node->isGroup() docbookgenerator.cpp 2599


Простая опечатка, но её исправление зависит от того, чего на самом деле хотели достичь в этом коде. Если проверка просто дублируется, то её стоит просто удалить. Но возможен и другой сценарий: не проверено какое-то другое нужное условие.


Фрагмент N5: создание лишней локальной переменной


void MainWindow::addToPhraseBook(){  ....  QString selectedPhraseBook;  if (phraseBookList.size() == 1) {    selectedPhraseBook = phraseBookList.at(0);    if (QMessageBox::information(this, tr("Add to phrase book"),          tr("Adding entry to phrasebook %1").arg(selectedPhraseBook),           QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok)                          != QMessageBox::Ok)      return;  } else {    bool okPressed = false;    QString selectedPhraseBook =       QInputDialog::getItem(this, tr("Add to phrase book"),                            tr("Select phrase book to add to"),                            phraseBookList, 0, false, &okPressed);    if (!okPressed)      return;  }  MessageItem *currentMessage = m_dataModel->messageItem(m_currentIndex);  Phrase *phrase = new Phrase(currentMessage->text(),                              currentMessage->translation(),                              QString(), nullptr);  phraseBookHash.value(selectedPhraseBook)->append(phrase);}

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


Единорог из старой коллекции


Предупреждение PVS-Studio: V561 [CWE-563] It's probably better to assign value to 'selectedPhraseBook' variable than to declare it anew. Previous declaration: mainwindow.cpp, line 1303. mainwindow.cpp 1313


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


QString selectedPhraseBook =

В результате в else-блоке появилась ещё одна локальная строковая переменная, которая инициализируется, но не используется. Тем временем переменная с таким же именем во внешнем блоке останется пустой.


Фрагмент N6: приоритет операций


Классический паттерн ошибки, который встречается весьма часто.


bool QQmlImportInstance::resolveType(....){  ....  if (int icID = containingType.lookupInlineComponentIdByName(typeStr) != -1)  {    *type_return = containingType.lookupInlineComponentById(icID);  } else {    auto icType = createICType();    ....  }  ....}

Предупреждение PVS-Studio: V593 [CWE-783] Consider reviewing the expression of the 'A = B != C' kind. The expression is calculated as following: 'A = (B != C)'. qqmlimport.cpp 754


Значение переменной icID всегда будет иметь значение 0 или 1. Это явно не то, что задумывалось. Причина: в начале происходит сравнение с -1, а только затем инициализация переменной icID.


Современный синтаксис C++ позволяет корректно записать это условие следующим образом:


if (int icID = containingType.lookupInlineComponentIdByName(typeStr);    icID != -1)

Кстати, очень похожую ошибку мы уже обнаруживали в Qt:


char ch;while (i < dataLen && ((ch = data.at(i) != '\n') && ch != '\r'))  ++i;

Но пока на вооружение не будет взят такой анализатор кода, как PVS-Studio, программисты вновь и вновь будут допускать такие ошибки. Никто не совершенен. И да, это тонкий намёк внедрить PVS-Studio :).


Фрагмент N7: коварное деление по модулю


Часто бывает необходимо определить, делится число без остатка на 2 или нет. Правильный вариант это поделить по модулю два и проверить результат:


if (A % 2 == 1)

Но программисты вновь и вновь ошибаются и пишут что-то типа этого:


if (A % 1 == 1)

Это естественно неправильно, так как остаток от деления по модулю на один это всегда ноль. Не обошлось без этой ошибки и в Qt:


bool loadQM(Translator &translator, QIODevice &dev, ConversionData &cd){  ....  case Tag_Translation: {    int len = read32(m);    if (len % 1) {                                             // <=      cd.appendError(QLatin1String("QM-Format error"));      return false;    }    m += 4;    QString str = QString((const QChar *)m, len/2);  ....}

Предупреждение PVS-Studio: V1063 The modulo by 1 operation is meaningless. The result will always be zero. qm.cpp 549


Фрагмент N8: перезаписывание значения


QString Node::qualifyQmlName(){  QString qualifiedName = m_name;  if (m_name.startsWith(QLatin1String("QML:")))    qualifiedName = m_name.mid(4);  qualifiedName = logicalModuleName() + "::" + m_name;  return qualifiedName;}

Предупреждение PVS-Studio: V519 [CWE-563] The 'qualifiedName' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 1227, 1228. node.cpp 1228


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


QString qualifiedName = m_name;if (m_name.startsWith(QLatin1String("QML:")))  qualifiedName = m_name.mid(4);qualifiedName = logicalModuleName() + "::" + qualifiedName;return qualifiedName;

Фрагмент N9: copy-paste


class Q_CORE_EXPORT QJsonObject{  ....  bool operator<(const iterator& other) const  { Q_ASSERT(item.o == other.item.o); return item.index < other.item.index; }  bool operator<=(const iterator& other) const  { Q_ASSERT(item.o == other.item.o); return item.index < other.item.index; }  ....}

Прудпреждение PVS-Studio: V524 It is odd that the body of '<=' function is fully equivalent to the body of '<' function. qjsonobject.h 155


Такие скучные функции, как операторы сравнения, никто не проверяет. На них обычно не пишут тесты. Их не просматривают или делают это очень быстро на code review. А зря. Статический анализ кода здесь как нельзя кстати. Анализатор не устаёт и с удовольствием проверяет даже такой скучный код.


Реализация операторов < и <= совпадают. Это явно неправильно. Скорее всего, код писался методом Copy-Paste, и затем забыли изменить всё что нужно в скопированном коде. Правильно:


bool operator<(const iterator& other) const{ Q_ASSERT(item.o == other.item.o); return item.index < other.item.index; }bool operator<=(const iterator& other) const{ Q_ASSERT(item.o == other.item.o); return item.index <= other.item.index; }

Фрагмент N10: static_cast / dynamic_cast


void QSGSoftwareRenderThread::syncAndRender(){  ....  bool canRender = wd->renderer != nullptr;  if (canRender) {     auto softwareRenderer = static_cast<QSGSoftwareRenderer*>(wd->renderer);     if (softwareRenderer)       softwareRenderer->setBackingStore(backingStore);  ....}

Предупреждение PVS-Studio: V547 [CWE-571] Expression 'softwareRenderer' is always true. qsgsoftwarethreadedrenderloop.cpp 510


В начале рассмотрим вот эту проверку:


bool canRender = wd->renderer != nullptr;if (canRender) {

Благодаря ей можно быть уверенным, что внутри тела условного оператора значение указателя wd->renderer всегда точно ненулевое. Тогда непонятно, что же хотят проверить следующим кодом?


auto softwareRenderer = static_cast<QSGSoftwareRenderer*>(wd->renderer);if (softwareRenderer)

Если указатель wd->renderer ненулевой, то и указатель softwareRenderer точно ненулевой. Есть подозрение, что здесь опечатка, которая состоит в том, что на самом деле следовало использовать dynamic_cast. В этом случае код начинает приобретать смысл. Если преобразование типа невозможно, оператор dynamic_cast возвращает nullptr, и это возвращенное значение, естественно, следует проверять. Впрочем, возможно, я неправильно интерпретировал ситуацию и код нужно исправлять другим способом.


Фрагмент N11: скопировали блок кода и забыли изменить


void *QQuickPath::qt_metacast(const char *_clname){  if (!_clname) return nullptr;  if (!strcmp(_clname, qt_meta_stringdata_QQuickPath.stringdata0))    return static_cast<void*>(this);  if (!strcmp(_clname, "QQmlParserStatus"))    return static_cast< QQmlParserStatus*>(this);  if (!strcmp(_clname, "org.qt-project.Qt.QQmlParserStatus"))   // <=    return static_cast< QQmlParserStatus*>(this);  if (!strcmp(_clname, "org.qt-project.Qt.QQmlParserStatus"))   // <=    return static_cast< QQmlParserStatus*>(this);  return QObject::qt_metacast(_clname);}

Предупреждение PVS-Studio: V581 [CWE-670] The conditional expressions of the 'if' statements situated alongside each other are identical. Check lines: 2719, 2721. moc_qquickpath_p.cpp 2721


Эти две строчки:


if (!strcmp(_clname, "org.qt-project.Qt.QQmlParserStatus"))  return static_cast< QQmlParserStatus*>(this);

Были размножены с помощью Copy-Paste. После чего они не были модифицированы и не имеют смысла.


Фрагмент N12: переполнение из-за не там поставленной скобки


int m_offsetFromUtc;....void QDateTime::setMSecsSinceEpoch(qint64 msecs){  ....  if (!add_overflow(msecs, qint64(d->m_offsetFromUtc * 1000), &msecs))    status |= QDateTimePrivate::ValidWhenMask;  ....}

Предупреждение PVS-Studio: V1028 [CWE-190] Possible overflow. Consider casting operands of the 'd->m_offsetFromUtc * 1000' operator to the 'qint64' type, not the result. qdatetime.cpp 3922


Программист предвидит ситуацию, что при умножении переменной типа int на 1000 может произойти переполнение. Чтобы этого избежать, он планирует использовать при умножении 64-битный тип qint64. И использует явное приведение типа.


Вот только толку от этого приведения типа никакого нет. В начале всё равно произойдёт переполнение. И только затем выполнится приведение типа. Правильный вариант:


add_overflow(msecs, qint64(d->m_offsetFromUtc) * 1000, &msecs)

Фрагмент N13: не полностью инициализированный массив


class QPathEdge{  ....private:  int m_next[2][2];  ....};inline QPathEdge::QPathEdge(int a, int b)    : flag(0)    , windingA(0)    , windingB(0)    , first(a)    , second(b)    , angle(0)    , invAngle(0){    m_next[0][0] = -1;    m_next[1][0] = -1;    m_next[0][0] = -1;    m_next[1][0] = -1;}

Предупреждения PVS-Studio:


  • V1048 [CWE-1164] The 'm_next[0][0]' variable was assigned the same value. qpathclipper_p.h 301
  • V1048 [CWE-1164] The 'm_next[1][0]' variable was assigned the same value. qpathclipper_p.h 302

Перед нами неудачная попытка инициализировать массив размером 2x2. Два элемента инициализируются повторно, и два остаются неинициализированными. Правильный вариант:


m_next[0][0] = -1;m_next[0][1] = -1;m_next[1][0] = -1;m_next[1][1] = -1;

Я очень люблю такие примеры ошибок, которые встречаются в коде профессиональных разработчиков. Это как раз такой случай. Он показывает, что любой может опечататься, и поэтому статический анализ является вашим другом. Дело в том, что я уже десяток лет веду бой со скептиками, которые уверены, что такие ошибки можно встретить только в лабораторных работах студентов и что они такие ошибки никогда не делают :). Ещё 10 лет назад я написал заметку "Миф второй профессиональные разработчики не допускают глупых ошибок", и с тех пор, естественно, ничего не изменилось. Люди всё также делают такие ошибки и всё также утверждают, что это не так :).


Будь мудр - используй статический анализатор кода


Ошибки в логике


Фрагмент N14: недостижимый код


void QmlProfilerApplication::tryToConnect(){  Q_ASSERT(!m_connection->isConnected());  ++ m_connectionAttempts;  if (!m_verbose && !(m_connectionAttempts % 5)) {// print every 5 seconds    if (m_verbose) {      if (m_socketFile.isEmpty())        logError(          QString::fromLatin1("Could not connect to %1:%2 for %3 seconds ...")          .arg(m_hostName).arg(m_port).arg(m_connectionAttempts));      else        logError(          QString::fromLatin1("No connection received on %1 for %2 seconds ...")          .arg(m_socketFile).arg(m_connectionAttempts));    }  }  ....}

Предупреждение PVS-Studio: V547 [CWE-570] Expression 'm_verbose' is always false. qmlprofilerapplication.cpp 495


Этот код никогда ничего не запишет в лог. Причиной являются противоположные условия:


if (!m_verbose && ....) {  if (m_verbose) {

Фрагмент N15: перетирание значения переменной


void QRollEffect::scroll(){  ....  if (currentHeight != totalHeight) {      currentHeight = totalHeight * (elapsed/duration)          + (2 * totalHeight * (elapsed%duration) + duration)          / (2 * duration);      // equiv. to int((totalHeight*elapsed) / duration + 0.5)      done = (currentHeight >= totalHeight);  }  done = (currentHeight >= totalHeight) &&         (currentWidth >= totalWidth);  ....}

Предупреждение PVS-Studio: V519 [CWE-563] The 'done' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 509, 511. qeffects.cpp 511


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


Фрагмент N16-N20: перетирание значения переменной


Альтернативный вариант перетирания значения переменной.


bool QXmlStreamWriterPrivate::finishStartElement(bool contents){  ....  if (inEmptyElement) {    ....    lastNamespaceDeclaration = tag.namespaceDeclarationsSize;   // <=    lastWasStartElement = false;  } else {    write(">");  }  inStartElement = inEmptyElement = false;  lastNamespaceDeclaration = namespaceDeclarations.size();      // <=  return hadSomethingWritten;}

Предупреждение PVS-Studio: V519 [CWE-563] The 'lastNamespaceDeclaration' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 3030, 3036. qxmlstream.cpp 3036


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


Есть ещё 4 предупреждения, указывающих на такой же паттерн ошибки:


  • V519 [CWE-563] The 'last' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 609, 637. qtextengine.cpp 637
  • V519 [CWE-563] The 'm_dirty' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 1014, 1017. qquickshadereffect.cpp 1017
  • V519 [CWE-563] The 'changed' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 122, 128. qsgdefaultspritenode.cpp 128
  • V519 [CWE-563] The 'eaten' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 299, 301. qdesigner.cpp 301

Фрагмент N21: путаница между нулевым указателем и пустой строкой


// this could become a list of all languages used for each writing// system, instead of using the single most common language.static const char languageForWritingSystem[][6] = {    "",     // Any    "en",  // Latin    "el",  // Greek    "ru",  // Cyrillic    ...... // Нулевых указателей нет. Используются пустые строковые литералы.    "", // Symbol    "sga", // Ogham    "non", // Runic    "man" // N'Ko};static void populateFromPattern(....){  ....  for (int j = 1; j < QFontDatabase::WritingSystemsCount; ++j) {    const FcChar8 *lang = (const FcChar8*) languageForWritingSystem[j];    if (lang) {  ....}

Предупреждение PVS-Studio: V547 [CWE-571] Expression 'lang' is always true. qfontconfigdatabase.cpp 462


В массиве languageForWritingSystem нет нулевых указателей. Поэтому проверка if(lang) не имеет смысла. Зато в массиве есть пустые строки. Быть может, хотелось сделать проверку именно на пустую строку? Если да, тогда корректный код должен выглядеть так:


if (strlen(lang) != 0) {

Или можно ещё проще написать:


if (lang[0] != '\0') {

Фрагмент N22: странная проверка


bool QNativeSocketEnginePrivate::createNewSocket(....){  ....  int socket = qt_safe_socket(domain, type, protocol, O_NONBLOCK);  ....  if (socket < 0) {    ....    return false;  }  socketDescriptor = socket;  if (socket != -1) {    this->socketProtocol = socketProtocol;    this->socketType = socketType;  }  return true;}

Предупреждение PVS-Studio: V547 [CWE-571] Expression 'socket != 1' is always true. qnativesocketengine_unix.cpp 315


Условие socket != -1 всегда истинно, так как выше происходит выход из функции, если значение переменной socket отрицательное.


Фрагмент N23: так что же всё-таки должна вернуть функция?


bool QSqlTableModel::removeRows(int row, int count, const QModelIndex &parent){  Q_D(QSqlTableModel);  if (parent.isValid() || row < 0 || count <= 0)    return false;  else if (row + count > rowCount())    return false;  else if (!count)    return true;  ....}

Предупреждение PVS-Studio: V547 [CWE-570] Expression '!count' is always false. qsqltablemodel.cpp 1110


Для упрощения выделю самое главное:


if (.... || count <= 0)  return false;....else if (!count)  return true;

Первая проверка говорит нам, что если значение count меньше или равно 0, то это ошибочное состояние и функция должна вернуть false. Однако ниже мы видим точное сравнение этой переменной с нулём, и этот случай уже интерпретируется по-другому: функция должна вернуть true.


Здесь явно что-то не так. Я подозреваю, что на самом деле проверка должна быть не <=, а просто <. Тогда код обретает смысл:


bool QSqlTableModel::removeRows(int row, int count, const QModelIndex &parent){  Q_D(QSqlTableModel);  if (parent.isValid() || row < 0 || count < 0)    return false;  else if (row + count > rowCount())    return false;  else if (!count)    return true;  ....}

Фрагмент N24: лишний статус?


В следующем коде переменная identifierWithEscapeChars выглядит просто как лишняя сущность. Или это логическая ошибка? Или код не дописан? К моменту второй проверки эта переменная в любом случае всегда будет равна true.


int Lexer::scanToken(){  ....  bool identifierWithEscapeChars = false;  ....  if (!identifierWithEscapeChars) {    identifierWithEscapeChars = true;    ....  }  ....  if (identifierWithEscapeChars) {    // <=    ....  }  ....}

Предупреждение PVS-Studio: V547 [CWE-571] Expression 'identifierWithEscapeChars' is always true. qqmljslexer.cpp 817


Фрагмент N25: что делать с девятью объектами?


bool QFont::fromString(const QString &descrip){  ....  const int count = l.count();  if (!count || (count > 2 && count < 9) || count == 9 || count > 17 ||      l.first().isEmpty()) {    qWarning("QFont::fromString: Invalid description '%s'",             descrip.isEmpty() ? "(empty)" : descrip.toLatin1().data());    return false;  }  setFamily(l[0].toString());  if (count > 1 && l[1].toDouble() > 0.0)    setPointSizeF(l[1].toDouble());  if (count == 9) {                           // <=    setStyleHint((StyleHint) l[2].toInt());    setWeight(QFont::Weight(l[3].toInt()));    setItalic(l[4].toInt());    setUnderline(l[5].toInt());    setStrikeOut(l[6].toInt());    setFixedPitch(l[7].toInt());  } else if (count >= 10) {  ....}

Предупреждение PVS-Studio: V547 [CWE-570] Expression 'count == 9' is always false. qfont.cpp 2142


Как должна себя вести функция, если переменная count равна 9? С одной стороны, функция должна выдать предупреждение и завершить свою работу. Ведь явно написано:


if (.... || count == 9 || ....) {  qWarning(....);  return false;}

С другой стороны, для 9 объектов предусмотрено выполнение специального кода:


if (count == 9) {  setStyleHint((StyleHint) l[2].toInt());  setWeight(QFont::Weight(l[3].toInt()));  setItalic(l[4].toInt());  ....}

Этот код, конечно, никогда не выполняется. Код ждёт, чтобы его пришли и исправили :).


Нулевые указатели


Фрагмент N26-N42: использование указателя до его проверки


class __attribute__((visibility("default"))) QMetaType {  ....  const QtPrivate::QMetaTypeInterface *d_ptr = nullptr;};QPartialOrdering QMetaType::compare(const void *lhs, const void *rhs) const{    if (!lhs || !rhs)        return QPartialOrdering::Unordered;    if (d_ptr->flags & QMetaType::IsPointer)        return threeWayCompare(*reinterpret_cast<const void * const *>(lhs),                               *reinterpret_cast<const void * const *>(rhs));    if (d_ptr && d_ptr->lessThan) {        if (d_ptr->equals && d_ptr->equals(d_ptr, lhs, rhs))            return QPartialOrdering::Equivalent;        if (d_ptr->lessThan(d_ptr, lhs, rhs))            return QPartialOrdering::Less;        if (d_ptr->lessThan(d_ptr, rhs, lhs))            return QPartialOrdering::Greater;        if (!d_ptr->equals)            return QPartialOrdering::Equivalent;    }    return QPartialOrdering::Unordered;}

Предупреждение PVS-Studio: V595 [CWE-476] The 'd_ptr' pointer was utilized before it was verified against nullptr. Check lines: 710, 713. qmetatype.cpp 710


Ошибка на первый взгляд может быть не заметна. Но на самом деле всё просто. Проследим, как работают с указателем d_ptr:


if (d_ptr->flags & ....)if (d_ptr && ....)

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


Это один из самых распространённых паттернов ошибки в языке C и С++. Пруфы. В исходных Qt кодах тоже встречается немало ошибок этой разновидности:


  • V595 [CWE-476] The 'self' pointer was utilized before it was verified against nullptr. Check lines: 1346, 1351. qcoreapplication.cpp 1346
  • V595 [CWE-476] The 'currentTimerInfo' pointer was utilized before it was verified against nullptr. Check lines: 636, 641. qtimerinfo_unix.cpp 636
  • V595 [CWE-476] The 'lib' pointer was utilized before it was verified against nullptr. Check lines: 325, 333. qlibrary.cpp 325
  • V595 [CWE-476] The 'fragment.d' pointer was utilized before it was verified against nullptr. Check lines: 2262, 2266. qtextcursor.cpp 2262
  • V595 [CWE-476] The 'window' pointer was utilized before it was verified against nullptr. Check lines: 1581, 1583. qapplication.cpp 1581
  • V595 [CWE-476] The 'window' pointer was utilized before it was verified against nullptr. Check lines: 1593, 1595. qapplication.cpp 1593
  • V595 [CWE-476] The 'newHandle' pointer was utilized before it was verified against nullptr. Check lines: 873, 879. qsplitter.cpp 873
  • V595 [CWE-476] The 'targetModel' pointer was utilized before it was verified against nullptr. Check lines: 454, 455. qqmllistmodel.cpp 454
  • V595 [CWE-476] The 'childIface' pointer was utilized before it was verified against nullptr. Check lines: 102, 104. qaccessiblequickitem.cpp 102
  • V595 [CWE-476] The 'e' pointer was utilized before it was verified against nullptr. Check lines: 94, 98. qquickwindowmodule.cpp 94
  • V595 [CWE-476] The 'm_texture' pointer was utilized before it was verified against nullptr. Check lines: 235, 239. qsgplaintexture.cpp 235
  • V595 [CWE-476] The 'm_unreferencedPixmaps' pointer was utilized before it was verified against nullptr. Check lines: 1140, 1148. qquickpixmapcache.cpp 1140
  • V595 [CWE-476] The 'camera' pointer was utilized before it was verified against nullptr. Check lines: 263, 264. assimpimporter.cpp 263
  • V595 [CWE-476] The 'light' pointer was utilized before it was verified against nullptr. Check lines: 273, 274. assimpimporter.cpp 273
  • V595 [CWE-476] The 'channel' pointer was utilized before it was verified against nullptr. Check lines: 337, 338. assimpimporter.cpp 337
  • V595 [CWE-476] The 'm_fwb' pointer was utilized before it was verified against nullptr. Check lines: 2492, 2500. designerpropertymanager.cpp 2492

Фрагмент N43: использование указателя до его проверки в рамках одного выражения


Та же самая ситуация, что и выше. Однако в этот раз разыменование и проверка указателя находятся в одном выражении. Классическая ошибка, возникающая из-за невнимательности при написании кода и при последующем code-review.


void QFormLayoutPrivate::updateSizes(){  ....  QFormLayoutItem *field = m_matrix(i, 1);  ....  if (userHSpacing < 0 && !wrapAllRows && (label || !field->fullRow) && field)  ....}

Предупреждение PVS-Studio: V713 [CWE-476] The pointer 'field' was utilized in the logical expression before it was verified against nullptr in the same logical expression. qformlayout.cpp 405


Минутка отдыха


Я устал. Думаю, устали и читатели. Тут устанешь, даже если просто бегло просматривать текст статьи :). Поэтому я пошёл за второй чашечкой кофе. Первую я выпил где-то на рубеже 12-ого фрагмента. Приглашаю и читателей сходить за любимым напитком.


И пока все ушли на кухню, рекламная пауза. Приглашаю команду, занимающуюся разработкой проекта Qt рассмотреть вопрос приобретения лицензии на анализатор кода PVS-Studio. Запросить прайс можно здесь. С нашей стороны поддержка и помощь в настройке. Да, согласен, я сегодня более навязчив, чем обычно. Это эксперимент :).


Минутка отдыха с единорогом


Фрагмент N44-N72: нет проверки, что вернула функция malloc


void assignData(const QQmlProfilerEvent &other){  if (m_dataType & External) {    uint length = m_dataLength * (other.m_dataType / 8);    m_data.external = malloc(length);                          // <=    memcpy(m_data.external, other.m_data.external, length);    // <=  } else {    memcpy(&m_data, &other.m_data, sizeof(m_data));  }}

Предупреждение PVS-Studio: V575 [CWE-628] The potential null pointer is passed into 'memcpy' function. Inspect the first argument. Check lines: 277, 276. qqmlprofilerevent_p.h 277


Нельзя просто взять и использовать указатель, который вернула функция malloc. Нужно обязательно проверить, не является ли этот указатель нулевым, даже если очень лень всем этим заниматься. Для этого есть 4 причины, которые описаны в статье "Почему важно проверять, что вернула функция malloc".


Перед нами один из случаев, где отсутствует необходимая проверка. Есть и другие предупреждения, но из-за их количества включать весь список в статью не хочется. На всякий случай, я выписал 28 предупреждений в файл: qt6-malloc.txt. Но на самом деле разработчикам, конечно, лучше самим перепроверить проект и самостоятельно изучить предупреждения. У меня не было задачи выявить как можно больше ошибок.


Что интересно, на фоне важных забытых проверок есть совершенно ненужные. Речь идёт о вызове оператора new, который в случае ошибки выделения памяти сгенерирует исключение std::bad_alloc. Вот один из примеров такой избыточной проверки:


static QImageScaleInfo* QImageScale::qimageCalcScaleInfo(....){  ....  QImageScaleInfo *isi;  ....  isi = new QImageScaleInfo;  if (!isi)    return nullptr;  ....}

Предупреждение PVS-Studio: V668 [CWE-570] There is no sense in testing the 'isi' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. qimagescale.cpp 245


P.S. Здесь читатели всегда задают вопрос, учитывает ли анализатор placement new или "new (std::nothrow) T"? Да, учитывает и не выдаёт для них ложные срабатывания.


Избыточный код ("код с запахом")


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


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


Часто я вообще не рассматриваю в статьях такие предупреждения. Это неинтересно. Однако в проекте Qt вдруг таких случаев оказалось удивительно много. Явно больше, чем обычно. Поэтому я решил уделить этому немного внимания и разобрать несколько таких случаев. Думаю, будет полезно провести рефакторинг этих и многих других аналогичных мест. Для этого потребуется использовать полный отчёт, не руководствоваться тем, что я выпишу в статью.


Итак, взглянем на несколько показательных случаев.


Фрагмент N73: "код с запахом" обратная проверка


void QQuick3DSceneManager::setWindow(QQuickWindow *window){  if (window == m_window)    return;  if (window != m_window) {    if (m_window)      disconnect(....);    m_window = window;    connect(....);    emit windowChanged();  }}

Предупреждение PVS-Studio: V547 [CWE-571] Expression 'window != m_window' is always true. qquick3dscenemanager.cpp 60


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


Фрагмент N74: "код с запахом" странная инициализация


QModelIndex QTreeView::moveCursor(....){  ....  int vi = -1;  if (vi < 0)    vi = qMax(0, d->viewIndex(current));  ....}

Предупреждение PVS-Stduio: V547 [CWE-571] Expression 'vi < 0' is always true. qtreeview.cpp 2219


Что это? Зачем так писать?


Что это? Зачем так писать? Код можно упростить до одной строки:


int vi = qMax(0, d->viewIndex(current));

Фрагмент N75: "код с запахом" недостижимый код


bool copyQtFiles(Options *options){  ....  if (unmetDependencies.isEmpty()) {    if (options->verbose) {      fprintf(stdout, "  -- Skipping %s, architecture mismatch.\n",              qPrintable(sourceFileName));    }  } else {    if (unmetDependencies.isEmpty()) {      if (options->verbose) {        fprintf(stdout, "  -- Skipping %s, architecture mismatch.\n",                  qPrintable(sourceFileName));      }    } else {      fprintf(stdout, "  -- Skipping %s. It has unmet dependencies: %s.\n",              qPrintable(sourceFileName),              qPrintable(unmetDependencies.join(QLatin1Char(','))));    }  }  ....}

Предупреждение PVS-Studio: V571 [CWE-571] Recurring check. The 'if (unmetDependencies.isEmpty())' condition was already verified in line 2203. main.cpp 2209


На первый взгляд перед нами респектабельный код, формирующий подсказку. Но давайте приглядимся. Если первый раз условие unmetDependencies.isEmpty() выполнилось, то второй раз этого уже не произойдёт. Это нестрашно, так как автор планировал вывести то же самое сообщение. Настоящей ошибки нет, но код переусложнён. Он может быть упрощен до следующего варианта:


bool copyQtFiles(Options *options){  ....  if (unmetDependencies.isEmpty()) {    if (options->verbose) {      fprintf(stdout, "  -- Skipping %s, architecture mismatch.\n",              qPrintable(sourceFileName));    }  } else {    fprintf(stdout, "  -- Skipping %s. It has unmet dependencies: %s.\n",            qPrintable(sourceFileName),            qPrintable(unmetDependencies.join(QLatin1Char(','))));  }  ....}

Фрагмент N76: "код с запахом" сложный тернарный оператор


bool QDockAreaLayoutInfo::insertGap(....){  ....  QDockAreaLayoutItem new_item    = widgetItem == nullptr      ? QDockAreaLayoutItem(subinfo)      : widgetItem ? QDockAreaLayoutItem(widgetItem)                    : QDockAreaLayoutItem(placeHolderItem);  ....}

Предупреждение PVS-Studio: V547 [CWE-571] Expression 'widgetItem' is always true. qdockarealayout.cpp 1167


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


  QDockAreaLayoutItem new_item    = widgetItem == nullptr      ? QDockAreaLayoutItem(subinfo) : QDockAreaLayoutItem(widgetItem);

Фрагмент N77: "код с запахом" избыточная защита


typedef unsigned int uint;ReturnedValue TypedArrayCtor::virtualCallAsConstructor(....){  ....  qint64 l = argc ? argv[0].toIndex() : 0;  if (scope.engine->hasException)    return Encode::undefined();  // ### lift UINT_MAX restriction  if (l < 0 || l > UINT_MAX)    return scope.engine->throwRangeError(QLatin1String("Index out of range."));  uint len = (uint)l;  if (l != len)    scope.engine->throwRangeError(      QStringLiteral("Non integer length for typed array."));  ....}

Предупреждение PVS-Studio: V547 [CWE-570] Expression 'l != len' is always false. qv4typedarray.cpp 306


Кто-то очень переживает, что значение 64-битной переменной не вмещается в 32-битную переменную unsigned. И использует сразу две проверки корректности. При этом вторая проверка избыточна.


Вот этого условия более чем достаточно:


if (l < 0 || l > UINT_MAX)

Приведенный ниже фрагмент можно смело удалить, и программа менее надёжной не станет:


uint len = (uint)l;if (l != len)  scope.engine->throwRangeError(    QStringLiteral("Non integer length for typed array."));

Дальше продолжать не буду. Думаю, идею вы поняли.


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


Другие ошибки


Я остановился после того, как описал 77 дефектов. Это красивое число и выписанного более чем достаточно, чтобы написать статью. Однако это не значит, что нет других ошибок, которые способен выявить PVS-Studio. При изучении лога я был весьма поверхностен и пропускал всё, где нужно было разбираться более пары минут, ошибка это или нет :).


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


Заключение


Статический анализ это круто! После внедрения PVS-Studio будет экономить время и нервы, выявляя множество ошибок сразу после написания кода. Намного лучше искать на code review с коллегами не опечатки, а высокоуровневые ошибки и обсуждать эффективность реализованного алгоритма. Тем более, как показывает практика, эти дурацкие опечатки всё равно отлично прячутся при просмотре кода глазами. Так что пусть их лучше ищет программа, а не человек.


Если у вас ещё остались вопросы или возражения, приглашаю познакомиться со статьёй "Причины внедрить в процесс разработки статический анализатор кода PVS-Studio". С вероятность 90 % вы найдете в ней ответ на ваши вопросы :). В оставшихся 10 % случаев напишите нам, пообщаемся :).


Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov. Date Processing Attracts Bugs or 77 Defects in Qt 6.

Подробнее..

Оценочный уровень доверия (ОУД4) и ГОСТ Р ИСОМЭК 15408-3-2013. Введение

18.02.2021 10:20:32 | Автор: admin

Привет, Хабр!


В настоящее время в ИТ индустрии крайне актуальна тема построения процесса безопасной разработки ПО (по англ. Secure SDLC или Secure software development life cycle). Некоторые организации и команды самостоятельно приходят к необходимости такого процесса в своих продуктах, свободно выбирают подходящий фреймворк, инструменты и выстраивают свой вариант безопасной разработки. Другие, подпадающие под внешние регуляции, вынуждены разбираться с конкретными, заранее выбранными регуляторами фреймворками или стандартами. Ко второму варианту относятся многочисленные финансовые организации, деятельность которых регулируется Банком России. В нормативах последнего с мая 2018 года стали фигурировать вопросы анализа уязвимостей и появилась аббревиатура ОУД 4.

Этой статьёй я хочу начать цикл, освещающий процессы безопасной разработки в контексте стандартов серии ГОСТ Р ИСО/МЭК 15408. Моя задача последовательно, фундаментально и лаконично изложить этот фреймворк, чтобы уменьшить порог вхождения в предмет. Материал предназначен для разработчиков, менеджеров, методологов и других людей, которые в силу обстоятельств, вынуждены погружаться в безопасную разработку в контексте ОУД и требований Банка России. В подготовке материала мне помогал независимый эксперт и специалист по информационной безопасности - Рустам Гусейнов.


Подробнее под катом

Оглавление

  1. Введение и цели цикла

  2. История и эволюция фреймворка

  3. Центральный банк как популяризатор ОУД4

  4. Общие положения и концепции ОУД4

  5. Заключение

  6. Полезные ссылки и материалы

  7. Приложение (список сокращений)

Введение и цели цикла

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

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

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

История и эволюция фреймворка

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

- архитектурой (дизайном);

- разработкой (кодированием архитектуры в конечный продукт);

- внедрением (настройкой);

- эксплуатацией (обслуживанием).

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

Рисунок 1. Иллюстрация вектора атаки

Корни фреймворка уходят в становление индустрии ИБ в США в семидесятых и восьмидесятых годах прошлого века. По мере автоматизации и развития системного подхода силовые ведомства стремились гарантировать надежность системных компонентов, используемых государственными структурами для защиты информации. В течение какого-то времени несколько подобных стандартов существовали в различных юрисдикциях параллельно, пока не были объединены международными организациями ISO/IEC в новый, единый фреймворк ISO/IEC 15408 Common Criteria for Information Technology Security Evaluation (или просто Common Criteria то есть Общие Критерии). Любопытные до истории и фактов могут изучить подробности хроники по ссылкам внизу статьи, нас же интересует тот факт, что отечественные стандартизаторы с 2012 года начали анализировать и перерабатывать указанное семейство стандартов, чтобы издать их официально на русском под эгидой Стандартинформа.

Здесь стоит сделать небольшое отступление про статус подобных стандартов. На фоне вступления России в ВТО и либерализации законодательства национальные стандарты перестали быть обязательными на территории Российской Федерации. Сегодня, в соответствии со ст. 27Федерального закона 162-ФЗ О стандартизации, ГОСТы на территории России являются обязательными тогда, когда на них явно ссылаются какие-то нормативно-правовые акты уполномоченных органов государственной власти. Здесь на сцену выходит Центральный Банк России.

Рисунок 2. Титульный лист ГОСТ Р 15408-1-2012

Центральный банк как популяризатор ОУД4

Примерно в то время, когда Стандартинформ публикует первые документы семейства ГОСТ 15408, Банк России, в соответствии с международными трендами, начинает ужесточать требования к информационной безопасности платежей и кредитных организаций. Его целью является обеспечение устойчивости и надежности платежной и банковской системы, в том числе борьба с мошенничеством и несанкционированными переводами. Летом 2012 года принимается знаменитое положение 382-П, которое на следующие 8 лет становится основным нормативом по информационной безопасности для организаций, занимающихся денежными переводами на территории РФ (не считая более нишевых и специфичных требований, вроде стандарта PCI DSS, которые существовали и до этого).

С положения 382-П начинается внедрение ОУД 4 в жизнь финансовых организаций. В мае 2018 года Банк России, сталкиваясь с фактами печальной статистики уязвимостей ДБО и иных публично доступных интерфейсов взаимодействия с клиентами, вносит изменения в упомянутое Положение. Среди них содержится п. 2.5.5.1, цитата:

необходимо обеспечить: использование для осуществления переводов денежных средств прикладного программного обеспечения автоматизированных систем и приложений, сертифицированных в системе сертификации Федеральной службы по техническому и экспортному контролю на соответствие требованиям по безопасности информации, включая требования по анализу уязвимостей и контролю отсутствия недекларированных возможностей, в соответствии с законодательством Российской Федерации или в отношении которых проведен анализ уязвимостей по требованиям к оценочному уровню доверия не ниже чем ОУД 4 в соответствии с требованиями национального стандарта Российской Федерации ГОСТ Р ИСО/МЭК 15408-3-2013 .

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

- 672-П (через ссылку на ГОСТ Р 57580.1-2017, где в требованиях ЖЦ.8 и ЖЦ.20 косвенно формулируется ежегодный ОУД4);

- 683-П;

- 684-П;

- 719-П.

Некоторые шероховатости формулировок и весьма общее указание на границы применимости ОУД в историческом 382-П, где было упомянуто прикладное ПО для осуществления перевода денежных средств, породили разночтения и длительные споры участников рынка об области применения ОУД. Стал популярным вопрос: надо ли проводить работы по ОУД в отношении всех информационных систем, участвующих в переводе денежных средств (например в отношении АБС), или все же речь идет о каких-то наиболее критичных/высокорискованных системах? Впоследствии эти вопросы были разрешены официальной позицией регулятора. Забегая вперед скажем, что требования к работам по ОУД касаются лишь того ПО, которое соответствует одному из двух условий (либо обоим):

  1. ПО распространяется клиентам организации (физическим или юридическим лицам);

  2. ПО доступно через интернет неограниченному кругу лиц (а не закрыто VPN или иными ограничителями).

Рисунок 3. Границы применимости ОУД для приложений финансовых организаций

Рассмотрим, для примера, как эта логика работает для банков. Из п. 4.1 Положения 683-П, которое устанавливает требования к ИБ кредитных организаций, вытекает необходимость работ по ОУД для ПО распространяемого кредитной организацией клиентам для совершения действий в целях осуществления банковских операций, а также {ПО}, обрабатывающего информацию на участках, используемых для приема электронных сообщений, к исполнению в автоматизированных системах и приложениях с использованием {} Интернет . В информационном письме Банка России от 8 июля 2020 г. N ИН-014-56/110, посвященном ОУД, приведена ссылка на Профиль Защиты Прикладного ПО, как на методику проведения анализа уязвимостей (подробнее об этом документе поговорим ниже). В свою очередь в п. 2.3.1 упомянутого Профиля развернуто сформулированы критерии подпадания прикладного ПО под работы в соответствии с ОУД4: клиентское ПО и/или выход в интернет. В упомянутой цитате из профиля находим:

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

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

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

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

Таким образом, спор о границах применимости ОУД был разрешен, а широкая формулировка границ применимости, проистекающая из 382-П уйдет в небытие вместе с указанным положением (оно утрачивает силу 01 января 2022 года на основании положения Банка России 719-П). Остается единственный вопрос как быть, если несколько приложений подпадают под требования? Наш ответ неоригинален риск-ориентированный подход и приоритизация проектов по ОУД внутри организации, особенно в условиях жестко лимитированного бюджета. Такую приоритизацию можно выстраивать на основании масштаба финансовых переводов, с учетом количества обрабатываемых персональных данных или исходить из других, менее очевидных соображений, например возможностей команды разработки оказывать поддержку проекту. В любом случае у вас наверняка есть более критичные каналы взаимодействия с клиентами и есть менее критичные. Если бюджет проекта ограничен, а количество различных приложений подпадающих под ОУД велико, самый простой критерий принятия решения - финансовый.

Теперь, когда мы изучили контекст вопроса, определились с границами применимости требований, решили в какой последовательности мы будем прогонять наши продукты через горнило ОУД, давайте поговорим о ключевых особенностях фреймворка и сделаем его обзор.

Общие положения и концепции ОУД4

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

Ключевыми документами, для понимания фреймворка, являются упомянутые в ссылках ГОСТы, которые не только вводят терминологию, но и содержат многочисленные разъяснения и примеры его различных аспектов. Ниже перечень, на наш взгляд, наиболее интересных и полезных:

  1. ГОСТ 15408-1-2012

  2. ГОСТ 15408-2-2013;

  3. ГОСТ 15408-3-2013;

  4. ГОСТ 18045-2013;

  5. ГОСТ 57628-2017;

  6. ГОСТ Р 58143-2018.

Общий процесс разработки и тестирования, согласно фреймворку, описывается в первой части ГОСТ 15408. В общем случае жизненный цикл продукта может быть представлен так, как показано на рисунке ниже.

Рис 4. Управление конфигурацией и жизненный цикл продукта (согласно ГОСТ Р 15408-1-2012)

Этапы работ, формирующих ОУД, описываются в третьей части стандарта ГОСТ 15408, как показано на следующем рисунке.

Рис 5. Компоненты, формирующие ОУД

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

  1. Ключевых концепций и ролей;

  2. Базовых процессов (взаимодействий) и их результатов.

На базовом уровне можно выделить три ключевые роли:

А. Заказчик (может совпадать с пользователем или владельцем), который использует некий продукт и требует соответствие этого продукта неким установленным требованиям к ИБ;

Б. Разработчик, который создает этот продукт и выстраивает его жизненный цикл, в соответствии с требованиями Заказчика;

В. Оценщик, который проверяет соответствие разработанного продукта установленным требованиям.

Здесь нужно сделать оговорку, что несмотря на методологическую рекомендацию разделять описанные выше роли между субъектами во избежании конфликта интересов (например, чтобы не проверять самих себя, а привлекать третью, независимую сторону), на практике, в некоторых случаях, такое совмещение ролей допустимо и легально. Например, в последнем абзаце п. 9 Положения 684-П сказано, что анализ уязвимостей по требованиям к ОУД4 может проводиться некредитными финансовыми организациями самостоятельно, без привлечения третьей стороны (для сравнения в текущей редакции 683-П такого пункта нет, и подобные работы необходимо осуществлять с привлечением сторонней организации лицензиата ФСТЭК).

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

Какие же процессы и взаимодействия возникают в ходе работ в соответствии с фреймворком ОУД? Практически каждый процесс ИБ начинается с описания границ проекта и требований, безопасная разработка не исключение.


В общих чертах фреймворк декларирует следующую последовательность действий:

  1. Разработать документацию и политики на продукт, которые регламентируют требования к ИБ приложения и разработки;

  2. Обеспечить практическое применение документов в разработке конкретного продукта;

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

Ключевым элементом фреймворка является объект оценки (или ОО, например, ДБО, или какой-то компонент внутри ДБО), к которому предъявляются функциональные требования к безопасности (или ФТБ, например, необходимая реализация системы управления доступом или стойкая криптография), формулируемые в задании по безопасности или профиле защиты (разница между двумя в том, что задание по безопасности обычно создается Заказчиком самостоятельно, под себя; а профиль тиражируется различными институциями как минимально-адекватный стандарт для какого-то класса решений, например для межсетевых экранов или антивирусов). Так как объект оценки существует не в вакууме, а актуальные уязвимости связаны не только с разработкой и проектированием, но и с настройкой и эксплуатацией объекта, важными понятиями являются среда функционирования и конфигурация среды/объекта оценки. Наконец, сам оценочный уровень доверия представляет собой гамбургер из большего или меньшего набора компонент, в зависимости от выбранного уровня (смотри рисунок 5 выше).

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

Такое подтверждение может осуществляться с помощью двух базовых сценариев:

  1. С помощью сертификации (делается специализированными лабораториями, включенными в систему ФСТЭК);

  2. С помощью оценки соответствия или анализа уязвимостей (делается самостоятельно для себя, либо сторонней организацией с лицензией ФСТЭК на ТЗКИ).

Это разделение нашло свое отражение в упомянутых по тексту положениях Банка России. В порядке убывания сложности и цены указанные сценарии можно разместить следующим образом:

  1. Сертификация;

  2. Оценка соответствия;

  3. Анализ уязвимостей.

Разница между оценкой соответствия и анализом уязвимостей наглядно иллюстрируется рисунком 5. В первом случае необходима именно оценка соответствия, по множеству компонентов, формирующих указанный ОУД - по сути оценка всего SDLC. Во втором проработка лишь одного компонента, который зависит от построения SDLC, но является лишь малой частью того, что составляет целый ОУД.

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

  1. Описание архитектуры безопасности;

  2. Функциональные спецификации;

  3. Руководство пользователя по эксплуатации;

  4. Представление реализации;

  5. Проект объекта оценки;

  6. Задание по безопасности.

Для начала такого состава документов нам хватит. Что же касается качества реализации фреймворка и надежности заключений Оценщика, то независимо от выбранного сценария работ, это во многом зависит от соблюдения Заказчиком и Разработчиком установленных семейством 15408 пререквизитов. От выстроенного в организации SDLC и наличия проработанной документации на конкретный продукт. Здесь действует правило аналогичное любому типу анализа защищенности чем более прозрачным для проверяющих будет ПО, чем полнее окажутся его описания, чем более компетентным и проактивным будет Заказчик, тем больше недостатков и уязвимостей смогут найти проверяющие и тем качественнее окажутся их заключения. Увы, в реальной жизни, в силу дороговизны работ и сложности процесса, указанные выше пререквизиты часто либо отсутствуют, либо находятся в зачаточном состоянии.

Заключение

Это наш первый опыт популярного изложения вопросов комплаенса (или соответствия стандартам) для широкой публики и нам важно получить обратную связь, чтобы адаптировать будущие тексты под интересы читателей и сделать их полезнее. Расскажите, пожалуйста, в комментариях - что вам понравилось в первой статье цикла, а что нет? Какие специфические темы, вопросы или примеры из жизни вы бы хотели подробно разобрать в контексте темы цикла? А может быть вы уже активно работаете с ОУД и хотите поделиться решенными проблемами, замечаниями и наблюдениями? Помимо упомянутой во введении методички по вопросам ОУД, мы планируем серию сопутствующих вебинаров, содержанием которых могут стать ответы на ваши вопросы и демонстрация реальных кейсов внедрения фреймворка. Для желающих приватности адрес электронной почты для связи: aantonov@swordfishsecurity.com

Полезные ссылки и материалы по теме:

  1. https://malotavr.blogspot.com/ блог Дмитрия Кузнецова, эксперта Positive Technologies, одного из немногих специалистов, систематически рассматривающих вопросы сертификации ПО и анализа уязвимостей по ОУД4. Написал несколько материалов про требования ЦБ и ОУД4, см. статьи за 2019 год.

  2. https://www.youtube.com/watch?v=0ZoNdaoAeyc&t=658s обзорный вебинар компании RTM Group, посвященный особенностям фреймворка, с участием компании Safe Tech, описывающий реализацию фреймворка в отношении своего продукта.

  3. Переведенные стандарты:

    a. ГОСТ 15408-1-2012;

    b. ГОСТ 15408-2-2013;

    c. ГОСТ 15408-3-2013;

    d. ГОСТ 18045-2013;

    e. ГОСТ 57628-2017;

    f. ГОСТ Р 58143-2018.

  4. https://en.wikipedia.org/wiki/Common_Criteria Обзор лучших практик по безопасной разработке статья в вики для желающих самостоятельно углубиться в исторические предпосылки.

  5. Лучшие практики безопасной разработки:

    a. https://doi.org/10.6028/NIST.CSWP.04232020 обзор актуальных практик SDLC от американского института NIST Mitigating the Risk of Software Vulnerabilities by Adopting a Secure Software Development Framework (SSDF) от 23.04.2020;

    b. https://www.iso.org/standard/55583.html стандарт ISO/IEC 27034-3:2018 Information technology Application security Part 3: Application security management process;

    c. https://www.pcisecuritystandards.org/document_library стандарты PCI SSC линейки Software Security Framework:

    i. Secure Software Requirements and Assessment Procedures;

    ii. Secure Software Lifecycle (Secure SLC) Requirements and Assessment Procedures.

  6. https://www.cbr.ru/information_security/acts/ Методический документ Банка России ПРОФИЛЬ ЗАЩИТ прикладного программного обеспечения автоматизированных систем и приложений кредитных организаций и некредитных финансовых организаций.

Приложение (список сокращений)

По тексту документа использованы следующие сокращения/условные обозначения:

  1. 382-П/Положение 382-П Положение Банка России от 9 июня 2012 г. 382-П О требованиях к обеспечению защиты информации при осуществлении переводов денежных средств и о порядке осуществления Банком России контроля за соблюдением требований к обеспечению защиты информации при осуществлении переводов денежных средств (актуальная редакция от 07.05.2018, действительно до 01.01.2022 года, отменяется 719-П);

  2. 719-П/Положение 719-П Положение Банка России от 4 июня 2020 г. 719-П О требованиях к обеспечению защиты информации при осуществлении переводов денежных средств и о порядке осуществления Банком России контроля за соблюдением требований к обеспечению защиты информации при осуществлении переводов денежных средств;

  3. 672-П/Положение 672-П Положение Банка России от 09.01.2019 N 672-П О требованиях к защите информации в платежной системе Банка России;

  4. 683-П/Положение 683-П Положение Банка России от 17 апреля 2019 г. N 683-П Об установлении обязательных для кредитных организаций требований к обеспечению защиты информации при осуществлении банковской деятельности в целях противодействия осуществлению переводов денежных средств без согласия клиента;

  5. 684-П/Положение 684-П Положение Банка России от 17.04.2019 N 684-П Об установлении обязательных для некредитных финансовых организаций требований к обеспечению защиты информации при осуществлении деятельности в сфере финансовых рынков в целях противодействия осуществлению незаконных финансовых операций;

  6. 162-ФЗ/Федеральный закон 162 ФЗ Федеральный закон О стандартизации в Российской Федерации от 29.06.2015 N 162-ФЗ;

  7. АБС автоматизированная банковская система;

  8. ГОСТ Р национальный государственный стандарт Российской Федерации;

  9. ИБ информационная безопасность;

  10. ИТ информационные технологии;

  11. ОО объект оценки;

  12. ОУД оценочный уровень доверия (см. ГОСТ/МЭК 15408);

  13. ПО программное обеспечение, приложение;

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

  15. ISO/IEC International Organization for Standardization(ISO) and theInternational Electrotechnical Commission(IEC);

  16. SDLC - Software Development Lifecycle.

Соавтор: (Рустам Гусейнов, специалист по информационной безопасности).

Подробнее..

Как PVS-Studio защищает от поспешных правок кода

24.03.2021 12:04:07 | Автор: admin

Недостижимый код
Хотя только недавно была заметка про проект CovidSim, есть хороший повод вновь про него вспомнить и продемонстрировать пользу регулярного использования PVS-Studio. Бывает, что все мы спешим и вносим правки в код, потеряв сосредоточенность. Статический анализатор может оказаться здесь хорошим помощником.


Всё началось с написания вот этих двух небольших заметок про открытый проект COVID-19 CovidSim Model:



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


Только мы начали проверять этот проект, как результаты не заставили себя ждать :). Сейчас я покажу свежую ошибку, вызванную недавними неаккуратными правками кода. Конечно, мы не станем описывать в дальнейшем каждый баг и недочёт, обнаруженный анализатором. Будем писать только про что-то интересное.


Вот что получилось, после недавних модификаций файла CovidSim.cpp:


Правки


Кто-то решил создавать массивы не на стеке, а в куче. Но был невнимателен, внося изменения. Обратите внимание, что освобождение памяти находится после оператора return:


int GetXMLNode(....){  char* buf = new char[65536];  char* CloseNode = new char[2048];  char* CloseParent = new char[2048];  ....  if (ResetFilePos) fseek(dat, CurPos, 0);  return ret;  delete[] buf;  delete[] CloseNode;  delete[] CloseParent;}

В результате перед нами фрагмент недостижимого кода (unreachable code). И заодно утечка памяти.


Хорошо, что PVS-Studio тут же сообщает про эту ошибку: V779 Unreachable code detected. It is possible that an error is present. CovidSim.cpp 675


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


И последнее, что хочется отметить. Эта ошибка была бы невозможна, если не вручную управлять выделением и освобождением памяти, а применить RAII подход и использовать умные указатели.


Правильный и надёжный вариант кода:


std::unique_ptr<char[]> buf(new char[65536]);std::unique_ptr<char[]> CloseNode(new char[2048]);std::unique_ptr<char[]> CloseParent(new char[2048]);

Спасибо за внимание. Следуйте за мной в мир С++ и багов :). Twitter. Facebook.

Подробнее..

Как работает команда DevOps в Positive Technologies

13.04.2021 14:10:30 | Автор: admin
Изображение: freepik.comИзображение: freepik.com

Всем привет! Меня зовут Тимур Гильмуллин, я работаю в отделе технологий и процессов разработки Positive Technologies. Внутри компании нас неформально называют DevOps-отделом. Мы занимаемся автоматизацией внутренних процессов и помогаем разработчикам и тестировщикам. В прошлой статье я уже писал, как выстроен карьерный рост у инженеров, а сегодня хочу рассказать про внутреннюю кухню о том, что делают DevOps-инженеры у нас в компании. Вы узнаете, что лежит в основе идей DevOps, какие плюсы дает бизнесу работа по принципам DevOps и как при этом изменяется процесс разработки.


Как мы понимаем идеи DevOps

Мы не претендуем на идеальную реализацию всех идей DevOps, для этого есть специализированные компании и гуру в этой области, а у нас в Позитиве своя специфика разработки в области ИБ и свои процессы автоматизации.

Как мы понимаем работу по принципам DevOps и в чем их ключевые отличия от классического подхода к разработке? Для начала разберемся, как когда-то шла разработка ПО не только в нашей компании, а в целом в отрасли.

Команда программистов (Dev) писала код и сама делала его сборку, команда тестировщиков (QA/QC) тестировала то, что им отдали программисты, а далее команда администраторов (IT) вручную устанавливала это на боевые серверы. Обычные проблемы такого подхода: Я код написал, сохранил, дальше не моя задача!, Я баг протестировал в старой версии, а новую мне никто не давал!, Я ваше приложение установил, оно не работает, что с ним делать, я не знаю, спрашивайте разработчиков!. А если это была распределенная команда с сотрудниками в разных часовых поясах, то при таком подходе разработка затягивалась на месяцы и годы. Чтобы решить эти проблемы, нужно было объединить всех участников в едином и понятном для них процессе.

Понятие Development Operations (или DevOps) включает в себя не только инструменты разработки и сервисы, но также методики и лучшие практики их использования. Иногда бывает, что даже на уровне руководителей разработки звучат мнения: Пусть ваши DevOps-инженеры просто настроят нам деплой (или CI)! или А вот в других компаниях DevOps-инженеры занимаются <подставить нужное>, вы тоже должны делать это!. Это говорит о том, что работу DevOps-инженеров часто понимают как смешение различных ролей. Инженеры, работающие по принципам DevOps, помогают подружить между собой всех, кто участвует в процессе разработки ПО, и объединить их совместный труд в рамках общего производственного конвейера. Они смотрят на все процессы сверху, при этом направления работ у таких инженеров динамически меняются и подстраиваются под реальность продуктовой разработки, чтобы предлагать наиболее подходящие решения под конкретные задачи.

Вот пример подобной гибкости из нашего опыта. В последние два года к поддержке направлений Dev и Ops для наших DevOps-инженеров добавились работы по внедрению инструментов безопасной разработки в уже существующие сборочные CI-конвейеры, то есть мы занялись развитием Sec-направления. Об этом мы подробно рассказывали в статье о том, как внедряли анализатор кода PT Application Inspector в продуктовый конвейер: для этого пришлось разработать методику и выстроить архитектуру решения, развернуть серверную и клиентскую части, разработать скрипты для работы с API PTAI и шаблоны сканирования для интеграции с CI (их можно найти в опенсорсе). Как сейчас работает PTAI в сборочном конвейере, мы показали на вебинаре.

Были в моей практике и нетривиальные инженерные задачи, например нужно было помочь тестировщикам нашего веб-сканера автоматически отсеивать false-позитивы найденных уязвимостей для одного из этапов тестирования. Для этого пришлось создать полноценную математическую модель и внедрить в CI нейро-нечеткий классификатор собственной разработки. Также мы внедряли систему нагрузочного тестирования на базе Яндекс.Танк, когда это еще не стало мейнстримом :) Но такие задачи достаточно редки, и они не совсем типичны для работы DevOps-инженера.

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

Плюсы, которые дает бизнесу работа по принципам DevOps

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

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

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

В нашей компании примеров внедрения автоматизации уже очень много. Но главной задачей было объяснить людям, зачем им нужна эта автоматизация. Проще всего, когда техническая задача сразу идет от бизнеса: когда у людей, приносящих деньги компании, есть четкое понимание того, что, как и когда нужно сделать или оптимизировать. Подробнее о том, какие плюсы для бизнеса приносит внедрение идей DevOps, мы писали в статьях о наших первых шагах в DevOps, о том, как мы пытались построить свой CI-конвейер, и о том, к чему в итоге пришли в автоматизации CI/CD-процессов. А также в статье о том, как научились выделять типовые шаги производственного конвейера, планировать их внедрение и тиражирование при помощи технологической карты.

Какие проблемы разработки может помочь решить DevOps-команда

Как я отметил ранее, одна из основных задач DevOps-инженера упрощение взаимодействия между командами разработки (Dev) и эксплуатации (Ops, обычно эту роль выполняет IT-подразделение). Они могут сделать совместный труд разработчиков и IT-инженеров более эффективным и продуктивным, обеспечивая обмен информацией и технологиями между ними.

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

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

Как идет разработка по принципам DevOps

Основные шаги

Процесс разработки сильно зависит от специфики работы каждой конкретной компании. У нас в Positive Technologies, являющейся вендором программных продуктов в сфере информационной безопасности, все решения от DevOps-инженеров типовые, масштабируемые и построены на шаблонах. Для всех CI/CD-проектов общие шаги остаются неизменными: разработчик делает git-коммит и запускается автоматическая сборка в GitLabCI (building) далее запускается автоматическое развертывание на тестовые серверы (deploying) выполняются функциональное и прочие виды автотестирования (testing) сборка продвигается в релизный репозиторий на Artifactory (promoting) публикация компонентов и инсталляторов на корпоративный сервер обновлений GUS (publishing) распространение через FUS-серверы в интернете (delivering) установка или обновление продукта на инфраструктуре заказчиков (installing/updating).

На каждом шаге DevOps-инженеры помогают разработчикам и тестировщикам решать множество конкретных технических задач, например:

  • подготовить и настроить проекты в GitLab для хранения кода и конфигураций;

  • подготовить пулы сборочных серверов;

  • разработать сборочные конфигурации, используя типовые шаблоны;

  • сохранить артефакты сборки (компоненты и инсталляторы) в репозитории на Artifactory;

  • создать сценарии автоматического развертывания тестового окружения на виртуальных машинах;

  • разработать конфигурации для развертывания компонент и инсталляторов на тестовых серверах;

  • помочь тестировщикам с подготовкой тестовых конфигураций и запуском в них тестовых сценариев;

  • продвинуть сборку, то есть переместить ее в хранилище успешно протестированных артефактов;

  • опубликовать сборку на корпоративном Update-сервере;

  • помочь с написанием сценариев для развертывания сборки на боевом сервере;

  • помочь с техническими деталями лицензирования и сбора телеметрии от установленных продуктов.

Инструменты для поддержки автоматизации

Как правило, инструменты для автоматизации разработки выбираются из потребностей решаемых задач. У нас за 7 лет эволюции CI/CD-конвейера сложилась устойчивая связка из трех базовых сервисов, предоставляемых для разработчиков и тестировщиков по умолчанию: GitLab как хранилище кода, GitLabCI для реализации CI/CD вместе с пулами сборочных агентов и Artifactory как хранилище собранных компонент и инсталляторов, а также кеширующий прокси для внешних репозиториев различного типа.

Разработчики пишут в основном на C++, C# и Python. Тестировщики пишут SaltStack или Ansible сценарии для подготовки тестового окружения, используют py.test, Selenium, RobotFramework, Яндекс.Танк, JMeter и собственные фреймворки для реализации различных видов тестов. Отчеты по тестам публикуются в сервисах TestRail или Allure.

Для работы с внешними заказчиками используется набор сервисов SupplyLab собственной разработки: GUS-сервер для публикации успешно протестированных релизов инсталляторов и пакетов обновлений, FUS-серверы для автоматической доставки релизов и License-сервер для работы с лицензиями. Подготовка окружения и развертывание продуктов компании на конечных серверах заказчиков производится либо самописными инсталляторами, либо также с использованием сценариев на SaltStack или Ansible.

Для ведения проектной работы мы предоставляем командам трекеры на выбор: TFS, YouTrack или Jira. Организовать связанную работу всех перечисленных сервисов, трекеров и интегрировать в них результаты труда продуктовых команд также задача наших DevOps-инженеров.

Большая часть шагов CI/CD-конвейера и все задействованные сервисы разработки контролируются в системе мониторинга Zabbix. При любом сбое наши инженеры быстро локализуют проблему и исправляют ошибки. Чтобы избежать повторения проблем, мы создаем и ведем странички так называемых разборов полетов.

Кроме того, наши DevOps-инженеры используют опенсорс-решения и сами пишут небольшие инструменты для интеграции с сервисами разработки (проект DevOpsHQ в GitHub).

Итоги

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

DevOps-инженер, конечно, может обладать широким кругозором в различных системах CI/CD и инфраструктуре, но также он может быть и узким специалистом (например, отлично разбираться только в мониторинге или сборе телеметрии от установленных продуктов). Как правило, в отдел инженеров, внедряющих идеи DevOps, выделяют нескольких специалистов из различных технических областей. И вот тогда они вместе как отдел представляют собой того самого мастера-универсала, который так необходим для развития продуктовой разработки.

Автор: Тимур Гильмуллин, заместитель руководителя отдела технологий и процессов разработки в Positive Technologies

Подробнее..

DevSecOps. PT Application Inspector в разработке ПО блокировка релиза

13.05.2021 10:08:31 | Автор: admin
Изображение: ptsecurity.comИзображение: ptsecurity.com

Всем привет! Меня зовут Тимур Гильмуллин, я работаю в отделе технологий и процессов разработки Positive Technologies. Неформально нас называют DevOps-отделом, а наши ребята занимаются автоматизацией различных процессов и помогают программистам и тестировщикам работать с продуктовыми конвейерами.

В прошлой статье мы с коллегами рассказали, как наши инженеры внедряли анализатор защищенности кода PT Application Inspector в сборочные конвейеры продуктовых команд. Тогда мы предложили клиент-серверную архитектуру и методику внедрения анализатора в CI/CD-конвейер, чтобы максимально упростить работу инженерам по инфраструктуре и CI-инженерам. Если перед вашими инженерами стоит задача внедрения сканера PT Application Inspector обязательно прочитайте прошлую статью.

А из этой статьи вы узнаете:

  • как организованы DevSecOps-процессы в нашей компании, как построена архитектура размещения сканера PT Application Inspector в CI-конвейере и как изменилась схема типовой сборки при добавлении сканирования;

  • что такое Security Gates, как группировать уязвимости по степени важности и как при помощи шаблонов сканирования настроить блокирование мердж-реквестов в GitLab;

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

Процессы DevSecOps в Positive Technologies

Начну с небольшого обзора концепции DevSecOps на примере схемы типового CI/CD-процесса в нашей компании. Так будет проще понять, какое место занимает сканер PT Application Inspector в этом процессе.

DevSecOps-конвейер в Positive Technologies: безопасный цикличный процесс разработки, сборки, развертывания, тестирования, продвижения, публикации, установки обновлений, сбора телеметрии и мониторингаDevSecOps-конвейер в Positive Technologies: безопасный цикличный процесс разработки, сборки, развертывания, тестирования, продвижения, публикации, установки обновлений, сбора телеметрии и мониторинга

Общие CI/CD-шаги для всех продуктов остаются неизменными. Разработчик пишет код фичи или исправляет найденные ошибки (Developing), делает git-коммит, после чего запускается автоматическая сборка в GitLab CI (Unit-Testing + Building). Далее происходит автоматическое развертывание на тестовые серверы (Deploying) и выполняются функциональное и прочие виды автотестирования (Functional Testing). Затем сборка продвигается в релизный репозиторий на Artifactory (Promoting), после чего компоненты и инсталляторы публикуются на корпоративный сервер обновлений GUS и происходит их автоматическое распространение через FLUS-серверы в интернете (Publishing GUS/FLUS). После этого на инфраструктуре заказчиков выполняется установка или обновление продукта (Installing/Updating). За установленным продуктом ведется наблюдение и сбор метрик (Collecting telemetry), его сервисы мониторятся (Monitoring) и собирается обратная связь от пользователей (User's feedback). Затем процесс разработки повторяется.

На схеме выше я предлагаю рассматривать Security-процесс так же непрерывно, как и процессы разработки, развертывания, тестирования и доставки. Безопасность нужно обеспечивать соответствующими инструментами на каждом этапе производственного конвейера. В частности, все изменения в коде должны проверяться на наличие потенциальных уязвимостей и ошибок. Решение нашей компании, сканер PT Application Inspector, позволяет проводить анализ исполняемого кода как статический, так и динамический и интерактивный. Помимо этого продукта у нас в CI/CD-конвейере используются и другие решения, например система выявления инцидентов ИБ MaxPatrol.SIEM и межсетевой экран уровня веб-приложений PT Application Firewall.

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

Как PT Application Inspector внедряли в Positive Technologies

Теперь расскажу, как внедрение сканера происходило у нас. Сначала появились заинтересованные программисты из разных команд, которые хотели автоматизировать проверку своего кода, а затем к ним подключились DevOps-инженеры, которым было интересно попробовать внедрить новый инструмент в сборки. Они провели эксперименты и сделали пилот на одном из проектов. В итоге это вылилось в инициативу по развитию направления DevSecOps внутри всего сборочного конвейера Positive Technologies.

В рамках этой инициативы нам было нужно разработать для DevSecOps-специалистов, CI-инженеров и в первую очередь для программистов компании методику использования PT Application Inspector в разработке ПО, а также определить метрики, которые критичны для сборочного процесса. Такая методика необходима для понимания того, как разработчики должны использовать сканер, на какие найденные уязвимости обращать внимание и как эти уязвимости должны влиять на выпуск релиза или даже блокировать его; как работать с ветками и мердж-реквестами, а также как DevSecOps-специалисты должны выстраивать инфраструктуру безопасной разработки и внедрять PT Application Inspector с нуля в производственные процессы.

Мы преследовали несколько внутренних целей:

  1. Внедрить SAST/DAST/IAST-решение в сборочный CI-конвейер всех продуктов, что позволит находить уязвимости в коде на ранних стадиях разработки (реализовать концепцию shift-left).

  2. Использовать анализатор защищенности для сертификации продуктов компании все сертифицирующие лаборатории требуют отчет по статическому анализу кода. До этого нам приходилось использовать сторонние продукты.

  3. Дать возможность команде продуктовой разработки PT Application Inspector обкатать свои решения на домашнем полигоне, выяснить потребности живых программистов основных пользователей сканера и CI-инженеров основных внедренцев продукта в сборочный конвейер, а также скорректировать планы разработки инструмента в зависимости от полученной обратной связи.

  4. Продолжить развитие направления DevSecOps внутри продуктового конвейера компании. PT Application Inspector относится к общей схеме DevSecOps, это один из удобных инструментов для CI/CD-конвейера. Мы хотели дать внутренним разработчикам в Positive Technologies действительно удобный инструмент, которым бы они сами хотели пользоваться, который упростил бы и облегчил их работу.

Архитектура PT Application Inspector в CI-конвейере

Сканер PT Application Inspector в CI-инфраструктуреСканер PT Application Inspector в CI-инфраструктуре

Легенда:

  • DevOps.BuildAgent сборочный агент

  • Docker.Linux.AISA.Latest/TAG все докер-образы клиента AISA, доступные по тегу

  • AI.Agent агент сканирования

  • AI.Server сервер PT Application Inspector

  • DevOps.GitLab хранилище кода

  • DevOps.GitLab-CI CI-система

  • DevOps.Artifactory хранилище артефактов сборок и сторонних компонентов

  • Docker.Registry хранилище докер-образов

  • Docker.Linux.AISA артефакт сборки клиента AISA (рабочий докер-образ с клиентом)

  • AI.Shell Agent клиент AISA, запущенный внутри докер-контейнера, работающий с API сервера PT Application Inspector

  • BuildAgent.Console системная консоль сборочного агента

  • WorkingDirectory рабочий каталог на сборочном сервере, где лежит исходный код, который будет отправлен на сканирование

Коротко напомню, какую мы выбрали архитектуру. Сервер и агенты сканирования PT Application Inspector размещены на отдельных виртуальных машинах. Запуск задания сканирования осуществляется в отдельных шагах на сборочных агентах GitLab CI. Исходный код из проекта на GitLab сначала выгружается на сборочный агент в рабочую директорию сборки, а затем передается для анализа через консольный клиент AISA на сервер и далее на агенты сканирования.

AISA это аббревиатура от Application Inspector Shell Agent. Этот клиент умеет работать с API сервера PT Application Inspector. AISA запускается в отдельном докер-контейнере, который является своеобразной оберткой для клиента.

Докер-образы с AISA собираются CI-конфигурациями, которые поддерживают CI-инженеры нашего DevOps-отдела. После сборки образы выкладываются в docker registry на Artifactory. Благодаря поставке докер-образами сборочная инфраструктура компании не зависит от разработки клиента AISA.

Мы определили основные шаги методики внедрения сканера в существующие CI-процессы. Вот они:

  1. Подготовка серверной части:

    установка и настройка сервера PT Application Inspector;

    установка и настройка агентов сканирования.

  2. Подготовка клиентской части:

    организация релизного CI-цикла для клиента AISA (поставка докер-образом).

  3. Подготовка проекта сканирования:

    на стороне сервера;

    через клиент AISA.

  4. Работа с CI-системами:

    шаблоны сканирования в GitLab CI;

    метараннеры TeamCity;

    прочие средства (работа с CLI AISA).

Чтобы внедрить PT Application Inspector по этой методике в конвейер одного продукта и поддерживать его работу, потребуются силы одного-двух CI-инженеров средней квалификации.

Сборочный процесс с использованием PT Application Inspector

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

Типовые шаги процесса продуктовой сборкиТиповые шаги процесса продуктовой сборки

Как изменился алгоритм типовой сборки с учетом новых шагов сканирования:

  1. Сборочный агент в момент старта сборки запрашивает исходный код проекта на GitLab.

  2. Исходный код проекта скачивается на сборочный агент.

  3. Запускается скрипт build-on-server, и начинают выполняться по порядку шаги сборки. Этот скрипт точка входа для разделения логики сборки и ее внедрения в CI-конвейер. Скрипт build-on-server пишут разработчики, так как знают логику компиляции своего продукта, а CI-инженеры вызывают его в шагах сборки в своей CI-системе.

  4. В параллельном с основной сборкой режиме запускается легковесный клиент AISA. Он использует конфигурационный файл с настройками сканирования.

  5. Код передается на сервер сканирования.

  6. Сервер распределяет код между агентами сканирования, на которых запускается процесс анализа.

  7. Выполняется сканирование кода проекта. Сканирование никак не мешает основному сборочному процессу, так как выполняется на отдельных серверах.

  8. Результаты анализа сохраняются в базе данных на сервере сканирования.

  9. AISA-клиент получает результаты сканирования со стороны сервера, и они становятся доступны в сборочном пайплайне.

  10. Выполняется проверка правил Security Gates. Результатом проверки является значение Code Quality Status это 0, если все условия правил выполняются, и 1, если одно или больше условий не выполняются.

  11. Если Code Quality Status равен 0, тогда считается, что пайплайны сборки и сканирования завершились успешно. При значении 1 сборка может быть остановлена, а в логи добавлены соответствующие записи о проблемах с безопасностью кода.

  12. Артефакт сборки публикуется в хранилище на Artifactory. К артефакту могут быть добавлены некоторые метки качества.

  13. Специальный бот публикует отчет по сканированию и результаты проверки правил Security Gates в пайплайне на GitLab CI и в мердж-реквесте GitLab. Команде разработки отправляется уведомление об успешной сборке, к которому прикладываются результаты сканирования.

Что мы улучшили в этом процессе:

  1. Раньше мы предлагали запускать сканирование на том же агенте, где идет сборка. Сейчас научились запускать сканирование в параллельном режиме, вынесли шаги отправки кода через AISA и шаги сканирования в отдельный пайплайн GitLab CI.

  2. Раньше приходилось запускать сканирование, ждать отчета по его результатам и лишь потом запускать сборку и публикацию артефакта либо отправлять код на сервер PT Application Inspector и через некоторое время, заранее неизвестное, возвращаться туда и смотреть результаты. Но сейчас появились штатные механизмы GitLab CI, и мы вынесли сканирование в так называемые downstream pipelines, или дочерние пайплайны. Теперь сканирование запускается всегда, на каждую сборку, параллельно самой сборке и не мешая ее ходу.

  3. Добавили бота, который оповещает письмом или в чат о завершении сканирования, умеет добавлять отчеты прямо в мердж-реквесты GitLab, но, самое главное, понимает формат отчета сканера и может блокировать мердж-реквест на основании заданных правил, так называемых Security Gates (по аналогии с Code Quality Gates от SonarQube).

  4. Разработчикам теперь доступно несколько вариантов работы с ветками в git. В зависимости от того, релизная это ветка или фича-ветка, можно использовать различные наборы правил блокировки мердж-реквеста либо только информировать о потенциальных уязвимостях.

Security Gates: правила и группировки

Итак, что мы предлагаем для обеспечения непрерывной безопасной разработки, что такое Security Gates и как они используются для блокировки мердж-реквестов в GitLab.

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

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

Пример описания правил Security Gates в файле aisa-codequality.settings.yamlПример описания правил Security Gates в файле aisa-codequality.settings.yaml

Сами правила описываются в обычном yaml-файле, состоящем из двух разделов:

  • threats mapping отвечает за маппинг и соответствие терминологии критичности проблем кода самого GitLab (имена ключей) и терминологии критичности уязвимостей PT Application Inspector (значения ключей). Кроме того, для удобства работы в этом разделе можно сгруппировать уязвимости по типу. Например, сказать, чтобы GitLab сгруппировал уязвимости уровней Potential, Low, Medium от сканера и отображал их в группе проблем Info.

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

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

Пример портянки сообщений от SonarQube в треде мердж-реквеста GitLabПример портянки сообщений от SonarQube в треде мердж-реквеста GitLab

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

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

Security Gates: проверки

Пример треда мердж-реквеста в GitLab, не заблокированного ботом, так как правила Security Gates выполняютсяПример треда мердж-реквеста в GitLab, не заблокированного ботом, так как правила Security Gates выполняются

Если уязвимости найдены, но проходят условия Security Gates, то в сборке специальный шаг Code Quality Status выставляется в значение 0 (Passed). В этом случае мердж-реквест не будет заблокирован, а в треде на GitLab программист увидит комментарий от бота с перечислением найденных (некритичных для данного набора правил) уязвимостей. Прямо в треде он сможет посмотреть, что это за уязвимости, либо перейти по ссылкам к полному HTML-отчету или в сборку на GitLab CI или TeamCity, если сборка была инициирована там.

Пример треда мердж-реквеста в GitLab, заблокированного ботом, так как нарушено правило Security Gates: major-проблем (Medium-уязвимостей от сканера) не более двухПример треда мердж-реквеста в GitLab, заблокированного ботом, так как нарушено правило Security Gates: major-проблем (Medium-уязвимостей от сканера) не более двух

Во втором случае, то есть если уязвимости найдены и не проходят условия Security Gates, значение Code Quality Status выставляется в 1 (Failed) и мердж-реквест блокируется путем добавления ключевого слова Draft в его заголовок.

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

При каждом сканировании бот обновляет один и тот же тред на страничке мердж-реквеста: все уязвимости удобно группируются и отображаются в одном месте.

Пример интеграции отчета от сканера в сборки на TeamCityПример интеграции отчета от сканера в сборки на TeamCity

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

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

Теперь перейдем к тому, как работают правила Security Gates и как результат их проверки Code Quality Status блокирует выпуск наших релизов.

Security Gates: три режима работы

Мы спросили у наших разработчиков, какой бы они хотели видеть интеграцию сканера PT Application Inspector с их сборками. Их основное пожелание состояло в том, чтобы он не мешал оперативной разработке фич и не требовалось бы долгое ожидание скана и сборок. В идеале, чтобы сканирование вообще не мешало сборкам. Именно для этого мы решили запускать его в параллельном пайплайне на GitLab CI.

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

Основные отличия в работе шаблонов сканированияОсновные отличия в работе шаблонов сканирования

Основные отличия шаблонов в их влиянии на сборку и в глубине интеграции сканирования со сборочным пайплайном. Например, самый строгий режим сканирования при непрохождении правил Security Gates одновременно блокирует и мердж-реквест, и сам сборочный пайплайн.

Все шаблоны кастомизируются, и при настройке сборочного процесса в специальном файле конфигурации .gitlab-ci.yml можно выбрать различные варианты работы со сканером.

Security Gates: Information mode

Схема пайплайна сборки со сканированием в режиме информированияСхема пайплайна сборки со сканированием в режиме информирования

Давайте посмотрим на шаги типового пайплайна сборки на GitLab CI, в которой использовался шаблон для работы сканера в информационном режиме (AI Information Mode).

Для примера, пусть сама сборка состоит из типовых шагов:

  • юнит-тестирование кода (Unit tests);

  • сборка или компиляция (Build);

  • публикация собранного артефакта сборки в некоторое хранилище (Upload to registry).

В GitLab CI в файл gitlab-ci.yml можно импортировать любые другие шаблоны через команду include. При подключении шаблона сканирования к основным шагам пайплайна сборки добавляются дополнительные:

  • запуск в параллельном режиме внешнего пайплайна сканирования (Start AI Scan);

  • отправка кода на сервер и запуск сканирования через AISA (AI-Scanning);

  • в случае любых проблем с инфраструктурой сканирования отправка информации об этом на мониторинг (Send info);

  • по окончании сканирования отправка отчета о найденных уязвимостях со сканирующего сервера в AISA и его добавление к пайплайну основной сборки (AI Scan Report);

  • проверка правил Security Gates, добавление результата проверки Code Quality Status (0, Passed / 1, Failed) к основному пайплайну;

  • отправка оповещения о результатах сканирования и сборки на почту или в чат (Send emails).

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

Security Gates: Lock mode

Схема пайплайна сборки со сканированием в режиме блокирования мердж-реквестаСхема пайплайна сборки со сканированием в режиме блокирования мердж-реквеста

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

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

Security Gates: Strictest mode

Схема пайплайна со сканированием в режиме блокирования мердж-реквеста и самой сборкиСхема пайплайна со сканированием в режиме блокирования мердж-реквеста и самой сборки

И, наконец, третья схема (AI Strictest Mode) максимально строгая. Кроме шагов, описанных в предыдущих схемах, в дочерний пайплайн добавляется новый шаг, разрешающий или запрещающий публикацию сборочного артефакта (Approve build). Таким образом, если будут обнаружены уязвимости, не проходящие проверки Security Gates, то будут заблокированы и основной сборочный пайплайн, и мердж-реквест. Дополнительно мердж-реквест может быть переведен в статус черновика (Draft).

Далее расскажу, в каких случаях мы применяем разные шаблоны.

Работа с ветками git для разных режимов Security Gates

В разработке у нас используется классический git-flow для работы с ветками:

  • master ветка для стабильного кода;

  • develop ветка для основной разработки и стабилизации кода из влитых фича-веток;

  • feature в продуктовых командах работа идет параллельно над множеством фич во множестве таких веток;

  • release у наших продуктов может быть одновременно несколько поддерживаемых релизов, поэтому используется несколько релизных веток.

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

Схема классического git-flow и рекомендуемые шаблоны для различных ветокСхема классического git-flow и рекомендуемые шаблоны для различных веток

Мы рекомендуем:

  • В feature-ветках хранить и использовать шаблон сканирования в информационном режиме (Information mode). Тогда при мердж-реквестах между feature-ветками и при влитии в ветку develop само сканирование никак не будет влиять на сборочный процесс и разработка будет идти быстро. Однако при этом разработчики все равно получают максимально подробные отчеты и рекомендации от сканера PT Application Inspector.

  • В develop-ветке хранить и использовать самый строгий шаблон (Strictest mode), а также настраивать в нем самые строгие правила Security Gates. В этой ветке важно стабилизировать код и избавиться от всех возможных уязвимостей перед его влитием в релизные ветки. Этот шаблон пресечет любые попытки мерджа уязвимого кода в релиз, заблокирует мердж-реквесты и сами сборочные пайплайны, не даст опубликовать артефакт с уязвимостью в хранилище.

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

  • В master-ветке можно использовать сканирование в информационном режиме (Information mode), так как в этой ветке лежит стабильный, протестированный и проверенный код релизов, а значит, можно минимизировать проверки.

Демонстрация: Security Gates и мердж-реквесты

В апреле 2021 г. мы провели вебинар для программистов и DevSecOps-инженеров. На нем мы показали, как шаблоны сканирования и правила Security Gates функционируют вживую, как наши программисты работают с реальными проектами, делают мердж-реквесты с поиском уязвимостей в коде через Application Inspector и как исправляют найденные ошибки.

Open Source dohq-ai-best-practices

Все наработки для GitLab CI и TeamCity, схемы и рабочие шаблоны сканирования для PT Application Inspector мы выложили в Open Source в проект dohq-ai-best-practices под свободной MIT-лицензией. Там вы найдете:

Что еще почитать по теме DevOps

По мере развития нашего CI мы делились идеями и наработками в корпоративном блоге на Хабре:

Если вас интересует тема кибербезопасности, следите за нашими вебинарами и новыми статьями в блоге компании. А на этом пока все. Ждем ваших вопросов в комментариях :)

Автор статьи: Тимур Гильмуллин заместитель руководителя отдела технологий и процессов разработки в Positive Technologies. Отвечал за разработку архитектуры и методики внедрения PT Application Inspector в сборочные техпроцессы со стороны DevOps-команды, а также подготовку проекта Open Source.

Технический эксперт: Дима Рагулин CI-инженер отдела технологий и процессов разработки. Отвечал за подготовку архитектуры и скриптов для интеграции PT Application Inspector с CI-системами и подготовку проекта Open Source.

Развивать идеи DevSecOps в большой компании невозможно без коллектива высококлассных специалистов. Выражаю огромную благодарность нашим коллегам: Стасу Антонову, Антону Володченко, Олегу Мачалову и Саше Худышкину за оперативную техническую помощь по вопросам внедрения, разработки и использования сканера PT Application Inspector, Вите Рыжкову и Алексею Жукову за экспертизу и помощь с подготовкой вебинаров, всем инженерам нашего отдела DevOps Positive Technologies за техническое сопровождение сервиса PT Application Inspector и помощь с его внедрением в компании, а также всем разработчикам сканера за прекрасный продукт :)

Подробнее..

Перевод Dockle Диагностика безопасности контейнеров

07.06.2021 20:11:26 | Автор: admin

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

Установка Dockle

Трудностей при установке утилиты возникнуть не должно:

  • Установка в OSX

$ brew install goodwithtech/r/dockle
  • Установка в Linux

# RHEL$ VERSION=$( curl --silent "https://api.github.com/repos/goodwithtech/dockle/releases/latest" | \ grep '"tag_name":' | \ sed -E 's/.*"v([^"]+)".*/\1/' \) && rpm -ivh https://github.com/goodwithtech/dockle/releases/download/v${VERSION}/dockle_${VERSION}_Linux-64bit.rpm#Ubuntu$ VERSION=$( curl --silent "https://api.github.com/repos/goodwithtech/dockle/releases/latest" | \ grep '"tag_name":' | \ sed -E 's/.*"v([^"]+)".*/\1/' \) && curl -L -o dockle.deb https://github.com/goodwithtech/dockle/releases/download/v${VERSION}/dockle_${VERSION}_Linux-64bit.deb$ sudo dpkg -i dockle.deb && rm dockle.deb

После установки утилиты всё, что вам нужно сделать, это указать имя образа. Больше ничего настраивать не нужно, вам даже не потребуется Docker.

Пример использования Dockle

Запускаем утилиту, указав имя образа. Если проблем нет, в консоль будет выведено сообщение PASS, а при обнаружении проблем или уязвимостей подробная информация о них:

Попробуем запустить Dockle в Docker, на скриншоте видно, что утилита отлично работает:

Основные функции и преимущества Dockle

  • поиск уязвимостей в образах,

  • помощь в создании правильного Dockerfile,

  • простота в использовании, нужно указать только имя изображения,

  • поддержка CIS Benchmarks.

Сравнение с другими инструментами

Существует большое количество похожих инструментов для диагностики безопасности, например: Docker Bench или Hadolint. Но в сравнении с ними Dockle более функциональна:

Применение Dockle в DevSecOps

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

По ссылкам ниже вы можете найти примеры того, как настроить системы CI / CD для работы с Dockle:

Подробнее..

GitLab oпрос ждем Bаших предложений

09.02.2021 20:10:35 | Автор: admin


После успешного live-вебинара на тему Автоматизация процессов с GitLab CI/CD, который мы провели в oктябре, мы хотели бы узнать, какие темы ещё интерeсуют Вас.

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

Мы с нетерпением ждем результатов опроса и надеемся также увидеть Вас на нашем следующем мероприятии.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru