CTF/2025 CCE Photo Editing

[CCE] image_processor.py

cucu0417 2025. 9. 11. 09:55

오늘은 취약점이 존재하는 image_processor.py 코드 중 모르는 문법을 찾아보았다

 

본 게시물은 모르는 문법을 찾아 코드 해석을 가능하게 한 후, 다음 게시물에서 취약점에 관한 내용을 다룰 것이다

 

 

import os
import uuid

from PIL import Image, ImageFilter, ImageOps, ImageMath, ImageEnhance
from app.utils import get_processed_images

import (모듈 가져오기)

  • 모듈
  • 함수나 변수 또는 클래스를 모아 놓은 파이썬 파일

PIL(Pillow)

  • 이미지 처리 모듈

app.utils

  • app 패키지 안에 있는 utils 모듈 기능들을 사용함
  • app. 패키지명
  • 프로젝트의 메인 애플리케이션 코드를 담고 있는 최상위 패키지 또는 디렉터리
  • utils
  • 해당 패키지 내의 유용한 함수들을 모아놓은 모듈(또는 디렉터리)

 

def apply_transform(transform_name, filenames, user_uuid, options=None):
    """Apply a specific transformation to images and return PIL Image objects."""
    images = get_processed_images(filenames, user_uuid)
    if not images:
        return []
    if options is None:
        options = {}
  • 처리된 이미지 받아오기

 

#rotate(회전)
if transform_name == 'rotate':
	angle = options.get('angle', 90)
	return [img.rotate(angle, expand=True) for img in images]

 

options.get(’angle’, 90)

  • options.get(key, default_value)
  • 딕셔너리 키 값을 이용해 값을 가져오고 만약 키가 없을 경우, 기본값을 반환

return [img.rotate(angle, expand=True) for img in images]

  • img.rotate(각도, expand)
  • 이미지 객체를 회전시킴
  • expand = True → 회전된 이미지가 잘리지 않고 전체가 보이도록 이미지 크기를 조절
  • expand = False → 원본 이미지 크기를 유지, 회전하는 동안 이미지의 일부가 잘릴 수 있음

 

#composite(합성)
elif transform_name == 'composite':
    if len(images) != 2:
    	raise ValueError("이미지 합성은 반드시 2개의 이미지를 선택해야 합니다.")
    img1 = images[0]
    img2 = images[1].resize(img1.size)
    return [Image.blend(img1, img2, alpha=0.5)]
  • composite(합성)을 선택할 때 이미지 개수 확인

img2=images [1]. resize(img1.size)

  • img2를 img1의 사이즈만큼 이미지 크기 변경

image.blend(img1, img2, alpha=0.5)

  • Pillow 라이브러리에서 두 이미지를 알파 값을 사용해 선형 보간을 통해 섞는 합수
  • 쉽게 말해 알파 값에 따라 혼합되는 비율이 달라짐
  • 0일 경우, img1이 완전히 보이고 1일 경우, img2가 완전히 보임
  • 0.5일 경우, 두 이미지가 일정 비율로 섞임

 

#append(추가)
elif transform_name == 'append':
	num_images = len(images)
    if num_images == 0:
    	raise ValueError("이미지를 선택해주세요.")
    elif num_images > 4:
    	raise ValueError("이미지 조합은 최대 4장까지만 지원합니다.")
  • append(추가)를 선택했을 경우, 이미지 개수 확인

 

if num_images == 1:
    return images 
elif num_images <= 3:
    min_width = min(img.width for img in images)
    resized_images = [img.resize((min_width, int(img.height * min_width / img.width))) for img in images]
            
    total_height = sum(img.height for img in resized_images)
    dst = Image.new('RGB', (min_width, total_height))

min(img.width for img in images)

  • images 안에 있는 img 중 넓이가 최소인 img의 값을 반환
  • min(x)
  • 인수로 받은 자료형 내에서 최솟값을 찾아서 반환하는 함수

resized_images = [img.resize((min_width, int(img.height * min_width / img.width))) for img in images]

  • 넓이는 최소 넓이로 맞추고 높이는 비율을 계산하여 이미지 크기 변경

dst=img.new(’RGB’, (min_width, total_height))

  • 이 이미지는 RGB 방식으로 표현되며 넓이는 최소 넓이로 맞추고, 높이는 전체 사진의 합
  • image.new(mode, size, color=0)
  • : Pillow 라이브러리에서 새로 이미지를 생성하는 함수

 

y_offset = 0
for img in resized_images:
    dst.paste(img, (0, y_offset))
    y_offset += img.height
return [dst]

dst.paste(img, (0, y_offset))

  • 0, y_offset 위치에 img를 dst에 붙여 넣기
  • paste(img, (x, y))
  • img : 붙여 넣을 이미지 객체
  • x : 왼쪽 상단 모서리의 가로 위치
  • y : 세로 위치

 

elif num_images == 4:
    min_size = min(img.width for img in images), min(img.height for img in images)
    resized_images = [img.resize(min_size) for img in images]
            
    dst = Image.new('RGB', (min_size[0] * 2, min_size[1] * 2))
    dst.paste(resized_images[0], (0, 0))
    dst.paste(resized_images[1], (min_size[0], 0))
    dst.paste(resized_images[2], (0, min_size[1]))
    dst.paste(resized_images[3], (min_size[0], min_size[1]))
    return [dst]
  • 이미지가 4개일 때 처리 방법
  • min_size [0] == min(img.width for img in images)
  • min_size [1] == min(img.height for img in images)

최종 사진 위치) 3번째 사진   4번째 사진

                        1번째 사진   2번째 사진

 

elif transform_name == 'contour':
    return [img.filter(ImageFilter.CONTOUR) for img in images]

elif transform_name == 'solarize':
    threshold = options.get('threshold', 128)
    return [ImageOps.solarize(img, threshold=threshold) for img in images]

return [img.filter(ImageFilter.CONTOUR) form img in images]

  • contour (윤곽)을 이미지에 적용
  • img.filter()
  • Pillow 라이브러리의 Image 객체에 다양한 필터를 적용하는 데 사용

threshold = options.get(’threshold’,128)

  • threshold(임계값)을 키값을 이용해 가져오고 없다면 기본 값이 128로 설정
  • options.get
  • 특정 기능을 수행하는 다른 객체에서 키를 사용하여 값을 가져옴

return [ImageOps.solarize(img, threshold=threshold) for img in images]

  • threshold(=128)을 가져와 임계값 이상일 경우 픽셀 반전
  • ImageOps.solarize(img, threshold)
  • 특정 임계값(threshold) 이상일 경우, 이미지의 픽셀 값을 반전시킴(solarize)

픽셀 반전

: 픽셀의 색상 값을 최댓값에서 뺀 값으로 변환 (최대 픽셀 값 - 현재 픽셀 값)

 

elif transform_name == 'brightness':
    factor = options.get('factor', 1.5)
    return [ImageEnhance.Brightness(img).enhance(factor) for img in images]

elif transform_name == 'grayscale':
    return [img.convert('L') for img in images]

elif transform_name == ‘brightness’

  • brightness(명도)를 이미지에 적용

factor = option.get(’factor’, 1.5)

  • 키 값을 이용하여 factor값을 가져오고 만약 없다면 기본 값인 1.5로 설정

return [ImageEnhance.Brightness(img). enhance(factor) for img in images]

  • 이미지의 밝기를 1.5배 증가 시킴
  • ImageEnhance.Brightness(image)
  • Pillow 라이브러리에서 이미지의 밝기를 조절하는 데 사용

return [img.convert(’L’) for img in images]

  • img.convert()
  • 이미지의 색상 모드를 변화할 때 사용
  • img.convert(’L’)
  • 이미지를 흑백이미지(grayscale)로 변환

 

elif transform_name == 'sepia':
    sepia_matrix = [
        0.393, 0.769, 0.189, 0,
        0.349, 0.686, 0.168, 0,
        0.272, 0.534, 0.131, 0
    ]
    return [img.convert("RGB", sepia_matrix) for img in images]

return [img.convert(”RGB”, sepia_matrix) for img in images]

  • 이미지에 세피아 톤 적용

 

elif transform_name == 'custom_formula':
    exp = options.get('expression')
    if not exp:
        raise ValueError("Custom formula requires an 'expression'.")
    env = { fname: img for fname, img in zip(filenames, images) }
    try:
    	result = ImageMath.eval(exp, env)
            return [result]
	except Exception as e:
        return [None]
else:
    raise ValueError(f"Unknown transformation: {transform_name}")

env = { fname : img for fname, img in zip(filenames, images) }

  • filenames와 images에서 차례대로 한 개씩 짝지어 fname:img 형태의 딕셔너리를 만듦
  • zip()
  • 두 그룹의 데이터를 서로 엮어 튜플 형태로 만듦
///ex)
number = [1,2,3]
letters = ["A", "B", "C"]
for pair in zip(numbers, letters):
	print(pair)

......

(1, 'A')
(2, 'B')
(3, 'C')

result = ImageMath.eval(exp, env)

  • ImageMath.eval
  • 이미지에 대한 수학적 표현식을 문자열 형태로 입력받아 계산, 그 결과를 이미지나 숫자로 반환
///ex)
#이미지 픽셀 값을 2배로 늘리는 연산
result_img=imageMath.eval("a * 2", a=img)

'CTF > 2025 CCE Photo Editing' 카테고리의 다른 글

[CCE] utils.py  (0) 2025.09.10