Время от времени на работе наше задание проверки зависимостей конвейера CI выдает еще одну уязвимую зависимость, обнаруженную в нашем приложении. Не совсем удивительно, учитывая скорость, с которой в наши дни публикуются новые резюме.
Это не большая проблема для прямых (или первого уровня) зависимостей, если для этого уже есть исправление. Все, что вам нужно сделать, это обновить свою зависимость до последней исправленной версии, убедиться, что обновление на самом деле не нарушает работу вашего приложения, и все готово.
Если уязвимость заключается в транзитивной зависимости (зависимости от другой зависимости, обычно той, которую вы напрямую не объявляете), она начинает становиться немного запутанной. Здесь может возникнуть пара проблем:
- Вы даже не знаете, откуда взялась эта зависимость.
- Родительская зависимость еще не обновлена до последней исправленной версии.
К счастью, современные инструменты сборки помогают сделать эту рутинную работу (тем не менее важную) немного менее болезненной. В нашем случае это было сделано с некоторой помощью Gradle.
Отслеживание переходных зависимостей
Определение зависимости верхнего уровня, в которой используется уязвимая зависимость, обычно является первым шагом к исправлению вашего приложения.
Сканирование сборки Gradle, вероятно, может предоставить большую часть информации о вашем проекте в одном центральном месте, но это требует публикации сведений о сборке на серверах Gradle, чего вы, возможно, захотите избежать, если не хотите, чтобы конфиденциальные сведения, связанные с вашим проектом, были общедоступными.
Вместо этого быстрый и простой способ сделать это локально в командной строке – использовать dependencyInsight
задача, предоставляемая Gradle. Это визуализирует происхождение переходной зависимости и, в случае нескольких конфликтующих версий, также показывает, почему была выбрана конкретная версия.
Последнее отличает его от другой аналогичной задачи под названием dependencies
, которая предоставляет только необработанную визуализацию каждой зависимости в вашем проекте.
Вы можете запустить эту задачу из командной строки следующим образом:
$ gradle -q dependencyInsight --dependency
Вы также можете предоставить опцию --configuration
чтобы указать конфигурацию gradle для сканирования. Если ничего не указано, он сканирует compile Classpath
по умолчанию.
В моем случае я хотел отследить commons-io
(версия 2.6 содержала уязвимость CVE-2021-29425) в моем проекте. Выполнение вышеуказанного дало следующий результат:
commons-io:commons-io:2.6 (selected by rule) variant "compile" [ org.gradle.status = release (not requested) org.gradle.usage = java-api org.gradle.libraryelements = jar (compatible with: classes+resources) org.gradle.category = library Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external org.gradle.jvm.version = 11 ] commons-io:commons-io:2.6 +---:0.1.372 +--- compileClasspath
( Упрощенный вывод только для представления, большинство крупных проектов имеют гораздо более сложные деревья зависимостей )
Это говорит мне о том, что commons-io v2.6
– это зависимость от внутренней библиотеки, которую мы использовали между командами. Теперь я мог бы подождать, пока команда, ответственная за обслуживание этой внутренней библиотеки, исправит ее самостоятельно, но это означало бы блокировку моего конвейера CI от дальнейших развертываний на неизвестный период времени. Вместо этого мне нужен способ принудительного обновления этой зависимости до исправленной версии до тех пор, пока сопровождающие также не исправят свою библиотеку.
Именно здесь вступает в игру еще одна особенность Gradle.
Применение ограничений к переходным зависимостям
Ограничения зависимостей – это самый простой (и рекомендуемый) способ принудительного разрешения конкретной версии переходной зависимости. Ограничения применяются ко всем прямым и переходным зависимостям, определенным в проекте.
Ограничение на зависимость может быть применено в вашем скрипте build.gradle
следующим образом:
dependencies { implementation 'org.apache.httpcomponents:httpclient' constraints { implementation('org.apache.httpcomponents:httpclient:4.5.3') { because 'previous versions have a bug impacting this application' } implementation('commons-codec:commons-codec:1.11') { because 'version 1.9 pulled from httpclient has bugs affecting this application' } } }
Вышеуказанные ограничения вынуждают Gradle всегда использовать httpclient v4.5.3
и принудительно разрешают библиотеку commons-codec
до версии v1.11 вместо версии v1.9, указанной в httpclient
.
Поэтому для принудительного обновления до исправленной версии commons-io
, мне пришлось добавить следующее ограничение в мой скрипт build.gradle
:
constraints { implementation('commons-io:commons-io:2.10.0') { because('Versions < 2.7 allows limited path traversal with FileNameUtils.normalize method - CVE-2021-29425') } }
Закрытие потому что
позволяет вам объяснить, почему следует выбрать эту версию, и она появится в dependencyInsight
вывод.
Теперь мой dependencyInsight
вывод выглядит следующим образом:
commons-io:commons-io:2.10.0 variant "compile" [ org.gradle.status = release (not requested) org.gradle.usage = java-api org.gradle.libraryelements = jar (compatible with: classes+resources) org.gradle.category = library Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external org.gradle.jvm.version = 11 ] Selection reasons: - By constraint : Versions < 2.7 allows limited path traversal with FileNameUtils.normalize method - CVE-2021-29425 commons-io:commons-io:2.6 -> 2.10.0 +---:0.1.372 +--- compileClasspath
Теперь я могу быть уверен, что уязвимая зависимость всегда разрешается исправленной версией, а не той, которая объявлена во внутренней библиотеке.
Оригинал: “https://dev.to/srujan_g/escaping-a-transitive-dependency-nightmare-with-some-help-from-gradle-374b”