ImaginaryCTF 2023 - perfect picture

Posted on Dec 18, 2023
tl;dr: Someone seems awful particular about where their pixels go...

perfect picture

We are supposed to upload a picture matching some very specific conditions.

The app

from flask import Flask, render_template, request
from PIL import Image
import exiftool
import random
import os

app = Flask(__name__)
app.debug = False

os.system("mkdir /dev/shm/uploads/")
app.config['UPLOAD_FOLDER'] = '/dev/shm/uploads/'
app.config['ALLOWED_EXTENSIONS'] = {'png'}

def check(uploaded_image):
    with open('flag.txt', 'r') as f:
        flag = f.read()
    with Image.open(app.config['UPLOAD_FOLDER'] + uploaded_image) as image:
        w, h = image.size
        if w != 690 or h != 420:
            return 0
        if image.getpixel((412, 309)) != (52, 146, 235, 123):
            return 0
        if image.getpixel((12, 209)) != (42, 16, 125, 231):
            return 0
        if image.getpixel((264, 143)) != (122, 136, 25, 213):
            return 0

    with exiftool.ExifToolHelper() as et:
        metadata = et.get_metadata(app.config['UPLOAD_FOLDER'] + uploaded_image)[0]
        try:
            if metadata["PNG:Description"] != "jctf{not_the_flag}":
                return 0
            if metadata["PNG:Title"] != "kool_pic":
                return 0
            if metadata["PNG:Author"] != "anon":
                return 0
        except:
            return 0
    return flag

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/upload', methods=['POST'])
def upload():
    if 'file' not in request.files:
        return 'no file selected'

    file = request.files['file']

    if file.filename == '':
        return 'no file selected'

    if file and allowed_file(file.filename):
        filename = file.filename

        img_name = f'{str(random.randint(10000, 99999))}.png'
        file.save(app.config['UPLOAD_FOLDER'] + img_name)
        res = check(img_name)

        if res == 0:
            os.remove(app.config['UPLOAD_FOLDER'] + img_name)
            return("hmmph. that image didn't seem to be good enough.")
        else:
            os.remove(app.config['UPLOAD_FOLDER'] + img_name)
            return("now that's the perfect picture:<br>" + res)

    return 'invalid file'

if __name__ == '__main__':
    app.run()

edit pixels

from PIL import Image


image_path = "not_perfect.png"
image = Image.open(image_path).convert("RGBA")
pixel_data = image.load()
x = 412  # Replace with the x-coordinate of the pixel you want to edit
y = 309  # Replace with the y-coordinate of the pixel you want to edit
new_rgba_value = (52, 146, 235, 123)
pixel_data[x, y] = new_rgba_value
x = 12
y = 209
new_rgba_value = (42, 16, 125, 231)
pixel_data[x, y] = new_rgba_value
x = 264
y = 143
new_rgba_value = (122, 136, 25, 213)
pixel_data[x, y] = new_rgba_value


output_path = "colors.png"  # Replace with the desired path to save the modified image
image.save(output_path)

edit exifdata

(kalikali)-[/mnt/ctf/web_perfect_picture]
└─$ exiftool -PNG:Description="jctf{not_the_flag}" not_perfect.png

                                                                                                                                                                                                  
┌──(kalikali)-[/mnt/ctf/web_perfect_picture]
└─$ exiftool -PNG:Description="jctf{not_the_flag}" colors.png     
    1 image files updated
                                                                                                                                                                                                  
┌──(kalikali)-[/mnt/ctf/web_perfect_picture]
└─$ exiftool -PNG:Title="kool_pic" colors.png     
    1 image files updated

upload the picture and get flag

now that's the perfect picture:
ictf{7ruly_th3_n3x7_p1c4ss0_753433}