La société Semper est spécialisée en archivage de documents électroniques. Afin de simplifier le travail des archivistes, un outil simple de suivi de modification a été mis en ligne. Depuis quelques temps néanmoins, cet outil dysfonctionne. Les salariés se plaignent de ne pas recevoir tous les documents et il n’est pas rare que le système plante. Le développeur de l’application pense avoir identifié l’origine du problème. Aidez-le à reproduire le bug.

We have an archive platform that allows us to archive PDF files, but there’s a bug in it apparently.

Index

We also have the source code of the platform:

# coding: utf-8
import hashlib
from web.services.database import Database
from web.services.mailer import Mailer


class ComparatorError(Exception):
    """Base class for all Comparator exceptions"""
    pass


class DatabaseError(ComparatorError):
    """Exception raised for errors in database operations."""


class StoreError(ComparatorError):
    """Exception raised for errors in store function.

    Attributes:
        message -- explanation of the error
    """

    def __init__(self, files, message):
        self.files = files
        self.message = message


class Comparator(object):
    """A class for Comparator"""

    BLOCK_SIZE = 8*1024

    def __init__(self, f1=None, f2=None):
        """
        Set default parameters

        Required parameters :
            f1: open file handler
            f2: open file handler
            db: database
            m : mailer

        """
        self.f1 = f1
        self.f2 = f2
        self.db = Database()
        self.m = Mailer()

    def compare(self):
        self._reset_cursor()
        return self.f1.read() == self.f2.read()

    def store(self):
        self._reset_cursor()
        f1_hash = self._compute_sha1(self.f1)
        f2_hash = self._compute_sha1(self.f2)

        if self.db.document_exists(f1_hash) or self.db.document_exists(f2_hash):
            raise DatabaseError()

        attachments = set([f1_hash, f2_hash])
        # Debug debug...
        if len(attachments) < 2:
            raise StoreError([f1_hash, f2_hash], self._get_flag())
        else:
            self.m.send(attachments=attachments)

    def _compute_sha1(self, f):
        h = hashlib.sha1()
        buf = f.read(self.BLOCK_SIZE)
        while len(buf) > 0:
            h.update(buf)
            buf = f.read(self.BLOCK_SIZE)
        return h.hexdigest()

    def _reset_cursor(self):
        self.f1.seek(0)
        self.f2.seek(0)

    def _get_flag(self):
        with open('flag.txt', 'r') as f:
            flag = f.read()
        return flag

Exploit

So apparently the platform uses the SHA1 of the files to see if they’re already stored according to this condition:

if self.db.document_exists(f1_hash) or self.db.document_exists(f2_hash):
		raise DatabaseError()

If we want to have the flag, we have to submit two different files that have the same SHA1 hash as we can see here:

attachments = set([f1_hash, f2_hash])

if len(attachments) < 2:
		raise StoreError([f1_hash, f2_hash], self._get_flag())
else:
		self.m.send(attachments=attachments)

If the hashes are the same, attachments will contain 1 value (thanks to set), otherwise it will have two values.

We quickly find Shattered on Google, and an interesting tool that allows us to create two PDF files with the same SHA1 hash: (SHA1collider).

magnussen@funcMyLife:~/revision$ diff file_1.pdf file_2.pdf
Binary files file_1.pdf and file_2.pdf differ
magnussen@funcMyLife:~/revision$ sha1sum file_1.pdf
0c8698e3153c2330ee4a6c483d642d659004d1b5  file_1.pdf
magnussen@funcMyLife:~/revision$ sha1sum file_2.pdf
9f9d71d7e1e55d6266694720b74082037c26f9c7  file_2.pdf
magnussen@funcMyLife:~/revision$ ./collide.py file_1.pdf file_2.pdf
[00:19:36] INFO: rendering file 1...
GPL Ghostscript 9.26 (2018-11-20)
Copyright (C) 2018 Artifex Software, Inc.  All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
Processing pages 1 through 1.
Page 1
[00:19:37] INFO: rendering file 2...
GPL Ghostscript 9.26 (2018-11-20)
Copyright (C) 2018 Artifex Software, Inc.  All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
Processing pages 1 through 1.
Page 1
[00:19:37] DEBUG: STREAM b'IHDR' 16 13
[00:19:37] DEBUG: STREAM b'iCCP' 41 2354
[00:19:37] DEBUG: iCCP profile name b'default_rgb.icc'
[00:19:37] DEBUG: Compression method 0
[00:19:37] DEBUG: STREAM b'pHYs' 2407 9
[00:19:37] DEBUG: STREAM b'tEXt' 2428 29
[00:19:37] DEBUG: STREAM b'IDAT' 2469 8192
[00:19:37] INFO: rendering images
[00:19:37] DEBUG: STREAM b'IHDR' 16 13
[00:19:37] DEBUG: STREAM b'iCCP' 41 2354
[00:19:37] DEBUG: iCCP profile name b'default_rgb.icc'
[00:19:37] DEBUG: Compression method 0
[00:19:37] DEBUG: STREAM b'pHYs' 2407 9
[00:19:37] DEBUG: STREAM b'tEXt' 2428 29
[00:19:37] DEBUG: STREAM b'IDAT' 2469 8192
[00:19:37] DEBUG: STREAM b'IHDR' 16 13
[00:19:37] DEBUG: STREAM b'iCCP' 41 2354
[00:19:37] DEBUG: iCCP profile name b'default_rgb.icc'
[00:19:37] DEBUG: Compression method 0
[00:19:37] DEBUG: STREAM b'pHYs' 2407 9
[00:19:37] DEBUG: STREAM b'tEXt' 2428 29
[00:19:37] DEBUG: STREAM b'IDAT' 2469 8192
[00:19:37] INFO: saving master images to TGA
[00:19:37] DEBUG: Importing BlpImagePlugin
[00:19:37] DEBUG: Importing BmpImagePlugin
[00:19:37] DEBUG: Importing BufrStubImagePlugin
[00:19:37] DEBUG: Importing CurImagePlugin
[00:19:37] DEBUG: Importing DcxImagePlugin
[00:19:37] DEBUG: Importing DdsImagePlugin
[00:19:37] DEBUG: Importing EpsImagePlugin
[00:19:37] DEBUG: Importing FitsStubImagePlugin
[00:19:37] DEBUG: Importing FliImagePlugin
[00:19:37] DEBUG: Importing FpxImagePlugin
[00:19:37] DEBUG: Importing FtexImagePlugin
[00:19:37] DEBUG: Importing GbrImagePlugin
[00:19:37] DEBUG: Importing GifImagePlugin
[00:19:37] DEBUG: Importing GribStubImagePlugin
[00:19:37] DEBUG: Importing Hdf5StubImagePlugin
[00:19:37] DEBUG: Importing IcnsImagePlugin
[00:19:37] DEBUG: Importing IcoImagePlugin
[00:19:37] DEBUG: Importing ImImagePlugin
[00:19:37] DEBUG: Importing ImtImagePlugin
[00:19:37] DEBUG: Importing IptcImagePlugin
[00:19:37] DEBUG: Importing JpegImagePlugin
[00:19:37] DEBUG: Importing Jpeg2KImagePlugin
[00:19:37] DEBUG: Importing McIdasImagePlugin
[00:19:37] DEBUG: Importing MicImagePlugin
[00:19:37] DEBUG: Importing MpegImagePlugin
[00:19:37] DEBUG: Importing MpoImagePlugin
[00:19:37] DEBUG: Importing MspImagePlugin
[00:19:37] DEBUG: Importing PalmImagePlugin
[00:19:37] DEBUG: Importing PcdImagePlugin
[00:19:37] DEBUG: Importing PcxImagePlugin
[00:19:37] DEBUG: Importing PdfImagePlugin
[00:19:37] DEBUG: Importing PixarImagePlugin
[00:19:37] DEBUG: Importing PngImagePlugin
[00:19:37] DEBUG: Importing PpmImagePlugin
[00:19:37] DEBUG: Importing PsdImagePlugin
[00:19:37] DEBUG: Importing SgiImagePlugin
[00:19:37] DEBUG: Importing SpiderImagePlugin
[00:19:37] DEBUG: Importing SunImagePlugin
[00:19:37] DEBUG: Importing TgaImagePlugin
[00:19:37] DEBUG: Importing TiffImagePlugin
[00:19:37] DEBUG: Importing WebPImagePlugin
[00:19:37] DEBUG: Importing WmfImagePlugin
[00:19:37] DEBUG: Importing XbmImagePlugin
[00:19:37] DEBUG: Importing XpmImagePlugin
[00:19:37] DEBUG: Importing XVThumbImagePlugin
[00:19:37] INFO: converting images to JPG
Independent JPEG Group's CJPEG, version 9b  17-Jan-2016
Copyright (C) 2016, Thomas G. Lane, Guido Vollbeding
2480x3508 RGB Targa image
Independent JPEG Group's CJPEG, version 9b  17-Jan-2016
Copyright (C) 2016, Thomas G. Lane, Guido Vollbeding
2480x3508 RGB Targa image
[00:19:38] INFO: producing final PDFs
magnussen@funcMyLife:~/revision$ diff out-file_1.pdf out-file_2.pdf
Binary files out-file_1.pdf and out-file_2.pdf differ
magnussen@funcMyLife:~/revision$ diff out-file_1.pdf out-file_2.pdf
Binary files out-file_1.pdf and out-file_2.pdf differ
magnussen@funcMyLife:~/revision$ sha1sum out-file_1.pdf
28dcf62fb359fb056f7de7982b688971fe857854  out-file_1.pdf
magnussen@funcMyLife:~/revision$  sha1sum out-file_2.pdf
28dcf62fb359fb056f7de7982b688971fe857854  out-file_2.pdf

As we can see, we have two different PDF files before using the tool that will perform the SHA1 collision. The files are still different after the execution of our tool but their hash is the same.

Let’s use these files on the platform and retrieve our flag!

Flag

Here’s the flag: FCSC{8f95b0fc1a793e102a65bae9c473e9a3c2893cf083a539636b082605c40c00c1}

This was an easy challenge, but fun to do!

Thanks to the FCSC team, this was an awesome CTF, I’ve learned a lot and had a lot of fun. Congratulation for the organization, see you next year!