- PVSM.RU - https://www.pvsm.ru -

В рамках своей миссии по созданию самой надёжной в мире платформы мы в Canva непрерывно оцениваем безопасность наших программных зависимостей. Определение и устранение уязвимостей в сторонних зависимостях помогает повысить безопасность не только нашей платформы, но и интернета в целом. Вкупе с такими инструментами управления безопасностью, как изолированные среды, мы всё больше усложняем для злоумышленников процесс эксплуатации сторонних зависимостей.
Одной из таких используемых в Canva зависимостей является librsvg [1] (задействуется через libvips [2]). Эта библиотека позволяет быстро отрисовывать пользовательские SVG в пиктограммы, впоследствии отображаемые в виде PNG. Мы показали, что путём эксплуатации различий в отрисовке URL-парсерами SVG-изображений при помощи librsvg можно внедрять в итоговое изображение произвольные файлы с диска. Мейнтейнеры
librsvg быстро исправили эту уязвимость [3], впоследствии зарегистрированную как CVE-2023-38633 [4].
Мы делимся результатами проведённого исследования в качестве ещё одного примера опасностей, заключающихся в совместном использовании URL-парсеров, особенно с учётом того, что обнаруженный нами случай оказался трудноуловимым.
Отдельная благодарность мейнтейнеру librsvg Федерико, мейнтейнеру libvips Джону и мейнтейнеру Sharp [5] Ловеллу за проделанную ими работу и оперативный отклик.
Статья Виктора Кахана из Elttam на тему проблем XML-парсинга в Inkscape [6] показывает, насколько этот инструмент уязвим к атаке обхода каталога при рендеринге SVG. Расширяя исследование Виктора, мы выяснили, что хоть XInclude и не поддерживается в Inkscape 0.9 непосредственно, он демонстрирует интересное поведение, когда одно изображение SVG вложено в другое.
К примеру, взгляните на эту внутреннюю SVG-картинку.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="300" height="300" xmlns:xi="http://www.w3.org/2001/XInclude">
<rect width="300" height="300" style="fill:rgb(255,204,204);" />
<text x="0" y="100">
<xi:include href="/etc/passwd" parse="text" encoding="ASCII">
<xi:fallback>file not found</xi:fallback>
</xi:include>
</text>
</svg>
Мы закодировали её в виде URI и поместили в другое SVG-изображение, outer.svg.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="300" height="300" xmlns:xlink="http://www.w3.org/1999/xlink">
<image xlink:href="data:image/svg;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjMwMCIgaGVpZ2h0PSIzMDAiIHhtbG5zOnhpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hJbmNsdWRlIj4KICA8cmVjdCB3aWR0aD0iMzAwIiBoZWlnaHQ9IjMwMCIgc3R5bGU9ImZpbGw6cmdiKDI1NSwyMDQsMjA0KTsiIC8+CiAgPHRleHQgeD0iMCIgeT0iMTAwIj4KICAgIDx4aTppbmNsdWRlIGhyZWY9Ii9ldGMvcGFzc3dkIiBwYXJzZT0idGV4dCIgZW5jb2Rpbmc9IkFTQ0lJIj4KICAgICAgPHhpOmZhbGxiYWNrPmZpbGUgbm90IGZvdW5kPC94aTpmYWxsYmFjaz4KICAgIDwveGk6aW5jbHVkZT4KICA8L3RleHQ+Cjwvc3ZnPg==" />
</svg>
При выполнении с помощью Inkscape 0.92.4 на выходе получилось изображение, в котором активировалась XInclude fallback.
$ inkscape -f test.svg -e out.png -w 300

Здесь нас удивил сам факт поддержки XInclude, поскольку это зачастую ведёт к появлению уязвимостей безопасности. И хотя Inkscape в Canva не используется, анализ его пути выполнения кода [7] показал, что вложенные изображения загружаются с помощью GdkPixbuf [8], который, в свою очередь, делегирует загрузку SVG библиотеке librsvg. Это оказалось очень интересно, потому как librsvg в Canva уже используется.
XInclude [9] – это механизм слияния XML-документов, который может создавать уязвимости безопасности, когда пользовательский XML (вроде SVG) формируется или отрисовывается на сервере.

В XInclude выделяется два элемента:
xi:include, отвечающий за внедрение содержимого URL, например файла или HTTP-запроса. Включаемое содержимое может быть простым текстом или XML. xi:fallback, отвечающий за предоставление содержимого для отрисовки в случае, когда xi:Include не может загрузить то, на которое ведёт ссылка.Если не брать во внимание проверку безопасности, то следующий XML-документ при обработке загружает содержимое /etc/passwd.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<example xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="/etc/passwd" parse="text" encoding="ASCII">
<xi:fallback>file not found</xi:fallback>
</xi:include>
</example>
librsvg – это библиотека Rust для отрисовки SVG-изображений на поверхностях Cairo [10]. Сейчас основная часть её функциональности реализована на Rust, но при этом она опирается на библиотеки Cairo и GNOME.

Согласно предыстории нам было известно, что librsvg поддерживает как минимум некоторые из стандартов XInclude. Чтобы понять, какие именно, мы изучили её реализацию [11]. Выяснилось, что каждая внешняя URL-ссылка в SVG для валидации проходит через один метод. В частности, это касается следующих ссылок:
Метод librsvg url_resolver.resolve_href [12] реализует ряд строгих проверок безопасности [13], определяя, какие ссылки могут быть загружены при обработке SVG-документа:
data: разрешены, поскольку не могут ссылаться на внешние файлы.file:///web.archive.org/web/20230911070144/https://foo/bar/example.svg любой встречаемый URL должен соответствовать схеме file:.http:.Эти строгие правила стали причиной провала первых простых тестов XInclude. Но нам захотелось узнать, есть ли вариант их обойти. Это может привести к уязвимости обхода каталога при обработке SVG, например, к возможности включать в содержимое SVG, отрисовываемого в PNG, файлы вроде /etc/passwd.
Разрешение URL-адреса в SVG-документе происходит в два этапа:
Фрагменты из mod.rs [15] и io.rs [16]:
// xml/mod.rs
fn acquire(&self, href: Option<&str>, /* ... */) -> Result<(), AcquireError> {
let aurl = self.url_resolver.resolve_href(href) // ...
// ...
self.acquire_text(&aurl, encoding);
}
fn acquire_text(&self, aurl: &AllowedUrl, encoding: Option<&str>) -> Result<(), AcquireError> {
let binary = io::acquire_data(aurl, None);
// ...
return result;
}
// io.rs
pub fn acquire_data(aurl: &AllowedUrl, /* ... */) -> Result<BinaryData, IoError> {
let uri = aurl.as_str();
// ...
let file = GFile::for_uri(uri);
let (contents, _etag) = file.load_contents(cancellable)?;
// ...
return contents;
// ...
}
Зная о том, что здесь задействовано два парсера (один для валидации URL и один для загрузки содержимого), для обхода проверок безопасности нам нужно было найти URL, в котором эти парсеры не согласовывались.
Проведя пару тестов, мы определили, как парсеры обрабатывают разные URL.
| URL file:///web.archive.org/web/20230911070144/https://etc/passwd?foo=bar#baz |
Спарсенный результат Rust url [17] |
| Схема | file |
| Хост | нет |
| Путь | /etc/passwd |
| Запрос | foo=bar |
| Фрагмент | baz |
Gio [18] не раскрывает парсинг обобщённого URL-адреса (кроме GUri, который не находится на пути вызова), но в некоторых примерах результат g_filename_from_uri возвращается.
| URL | результат g_filename_from_uri |
| file:///web.archive.org/web/20230911070144/https://etc/passwd?foo=bar#baz | /etc/passwd?foo=bar#baz Примечание: в коммите Glib 3986471 [19] даёт сбой из-за символов? и #. |
| file:///web.archive.org/web/20230911070144/https://etc/passwd | /etc/passwd |
| file:///web.archive.org/web/20230911070144/https://etc/passwd&hello | /etc/passwd&hello |
| file:///web.archive.org/web/20230911070144/https://etc/passwd@host | /etc/passwd@host |
| file://host/etc/passwd | /etc/passwd |
Понимая, где находятся парсеры, мы взяли соответствующие части из librsvg и настроили фаззинг-тесты («resolve») для выполнения логики, соответствующей логике обработки URL, при встрече ссылки (href, XInclude и т.п.) из находящегося на диске файла current.svg. Это позволило нам быстро протестировать и проанализировать входные данные, чтобы понять, как обрабатывается парсинг и логика валидации. Вот некоторые интересные выводы фаззинга:
resolve 'current.svg': проходит ожидаемым образом.resolve run '../../../../../../../etc/passwd': каноникализация проваливается с ошибкой 'No such file or directory'.resolve 'current.svg?../../../../../../../etc/passwd': проходит.resolve 'none/../current.svg': проходит ожидаемым образом.
Последние два результата показали, что GFile::for_uri вполне позволяет выполнять обход каталога, в том числе в строке запроса. Тем не менее второй результат, ../../../../../../../etc/passwd, провалился из-за проверки каноникализации.
Часть валидации URL-адреса в librsvg заключается в каноникализации создаваемого URL для замены сегментов .. и . согласно стандартным правилам файловой системы. Библиотека выполняет это, используя std::fs::canonicalize [20] (вызывая realpath [21]), который выбрасывает ошибку, если:
Поскольку мы не всегда знаем имя «current» SVG на диске, для успешного прохождения валидации URL нам нужно было обойти каноникализацию. После небольшого тестирования выяснилось, что это не так сложно.
$ realpath current.svg
/home/zsims/projects/librsvg-poc/current.svg
$ realpath .
/home/zsims/projects/librsvg-poc/
Как оказалось, realpath(".") и std::fs::canonicalize(".") возвращают «текущий каталог». Мы можем использовать это в нашей проверке концепции в качестве плейсхолдера вместо current.svg.
Понимая, в чём расходятся URL-парсеры, и как можно обойти каноникализацию, не зная имени текущего файла, мы можем создать полезную нагрузку для внедрения /etc/passwd.
.?../../../../../../../etc/passwd
Внутри poc.svg это выглядит так:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="300" height="300" xmlns:xi="http://www.w3.org/2001/XInclude">
<rect width="300" height="300" style="fill:rgb(255,204,204);" />
<text x="0" y="100">
<xi:include href=".?../../../../../../../etc/passwd" parse="text" encoding="ASCII">
<xi:fallback>file not found</xi:fallback>
</xi:include>
</text>
</svg>
И даёт следующий вывод:
$ rsvg-convert poc.svg > poc.png

При выполнении через vipsthumbnail мы получаем аналогичный результат.
Чувствительные файлы в /proc, такие как /proc/self/environ, обработать не получилось из-за используемой в них кодировки символов.
$ rsvg-convert proc-poc.svg > proc-poc.png
thread 'main' panicked at 'str::ToGlibPtr<*const c_char>: unexpected '' character: NulError(21...
Заметьте, что эта проверка концепции работает только там, где SVG загружается из file://. SVG, загружаемые через схемы data: или resource:, неуязвимы.
После получения этого отчёта (Issue 996 [3]) мейнтейнер librsvg Федерико пропатчил уязвимость [22] путём улучшения валидации URL и использования в GFile прошедшего эту валидацию URL. Ответ Федерико включал просьбу к мейнтейнерам Sharp и libvips внести исправления до того, как проблема будет публично зарегистрирована под кодом CVE-2023-38633 [4].
Данная уязвимость также породила дискуссию на тему парсинга URL-адресов в glib [23], что привело к реализации в этой библиотеке дополнительной валидации [24].
В ходе обнаружения и исправления проблемы наиболее яркими оказались следующие моменты:
file:// и внутрипроцессного использования, в какой сетевых сервисов и URL http://.file:// являются особенными. Например, в спецификации URL подчёркивается поддержка строк запроса в адресах [26] file://, но в изученных нами реализациях поддержка сильно отличалась.xi:include отклоняются до того, как SVG достигает librsvg.Автор: Дмитрий Брайт
Источник [30]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/informatsionnaya-bezopasnost/387197
Ссылки в тексте:
[1] librsvg: https://web.archive.org/web/20230911070144/https:/gitlab.gnome.org/GNOME/librsvg
[2] libvips: https://web.archive.org/web/20230911070144/https:/www.libvips.org
[3] быстро исправили эту уязвимость: https://web.archive.org/web/20230911070144/https:/gitlab.gnome.org/GNOME/librsvg/-/issues/996
[4] CVE-2023-38633: https://web.archive.org/web/20230911070144/https:/nvd.nist.gov/vuln/detail/CVE-2023-38633
[5] Sharp: https://web.archive.org/web/20230911070144/https:/sharp.pixelplumbing.com
[6] проблем XML-парсинга в Inkscape: https://web.archive.org/web/20230911070144/https:/github.com/elttam/publications/blob/master/writeups/inkscape-xml.md
[7] анализ его пути выполнения кода: https://web.archive.org/web/20230911070144/https:/gitlab.com/inkscape/inkscape/-/blob/0.92.x/src/display/cairo-utils.cpp#L230
[8] GdkPixbuf: https://web.archive.org/web/20230911070144/https:/docs.gtk.org/gdk-pixbuf/
[9] XInclude: https://web.archive.org/web/20230911070144/https:/www.w3.org/TR/xinclude/
[10] Cairo: https://web.archive.org/web/20230911070144/https:/www.cairographics.org
[11] изучили её реализацию: https://web.archive.org/web/20230911070144/https:/gitlab.gnome.org/GNOME/librsvg/-/tree/eec9d15145da5f6e2b70c5974b3e3c6ecac876e0
[12] url_resolver.resolve_href: https://web.archive.org/web/20230911070144/https:/gitlab.gnome.org/GNOME/librsvg/-/blob/eec9d15145da5f6e2b70c5974b3e3c6ecac876e0/rsvg/src/url_resolver.rs#L32
[13] строгих проверок безопасности: https://web.archive.org/web/20230911070144/https:/gnome.pages.gitlab.gnome.org/librsvg/Rsvg-2.0/class.Handle.html#security-and-locations-of-referenced-files
[14] загрузка содержимого: https://web.archive.org/web/20230911070144/https:/gitlab.gnome.org/GNOME/librsvg/-/blob/eec9d15145da5f6e2b70c5974b3e3c6ecac876e0/rsvg/src/io.rs#L87
[15] mod.rs: https://web.archive.org/web/20230911070144/https:/gitlab.gnome.org/GNOME/librsvg/-/blob/eec9d15145da5f6e2b70c5974b3e3c6ecac876e0/rsvg/src/xml/mod.rs#L509-549
[16] io.rs: https://web.archive.org/web/20230911070144/https:/gitlab.gnome.org/GNOME/librsvg/-/blob/eec9d15145da5f6e2b70c5974b3e3c6ecac876e0/rsvg/src/io.rs#L95-122
[17] url: https://web.archive.org/web/20230911070144/https:/docs.rs/url/latest/url/
[18] Gio: https://web.archive.org/web/20230908170051/https://docs.gtk.org/gio/
[19] коммите Glib 3986471: https://web.archive.org/web/20230911070144/https:/gitlab.gnome.org/GNOME/glib/-/commit/39864716d6404766c325caf18c6bef722dc060cd
[20] std::fs::canonicalize: https://web.archive.org/web/20230911070144/https:/doc.rust-lang.org/std/fs/fn.canonicalize.html
[21] realpath: https://web.archive.org/web/20230911070144/https:/linux.die.net/man/3/realpath
[22] пропатчил уязвимость: https://web.archive.org/web/20230911070144/https:/gitlab.gnome.org/GNOME/librsvg/-/merge_requests/861
[23] URL-адресов в glib: https://web.archive.org/web/20230911070144/https:/gitlab.gnome.org/GNOME/glib/-/issues/3050
[24] дополнительной валидации: https://web.archive.org/web/20230911070144/https:/gitlab.gnome.org/GNOME/glib/-/merge_requests/3502
[25] совместного использования разных URL-парсеров: https://web.archive.org/web/20230911070144/https:/daniel.haxx.se/blog/2022/01/10/dont-mix-url-parsers/
[26] поддержка строк запроса в адресах: https://web.archive.org/web/20230911070144/https:/url.spec.whatwg.org#file-state
[27] MediaWiki: https://web.archive.org/web/20230911070144/https:/www.mediawiki.org/wiki/MediaWiki
[28] libvips: https://web.archive.org/web/20230911070144/https:/github.com/libvips/libvips
[29] Sharp: https://web.archive.org/web/20230911070144/https:/github.com/lovell/sharp
[30] Источник: https://habr.com/ru/companies/ruvds/articles/760766/?utm_source=habrahabr&utm_medium=rss&utm_campaign=760766
Нажмите здесь для печати.