Scope

The text that came as description with the CTF was the following:

The ability to control the weather has been a well guarded secret. Until now. If you’re deemed worthy enough you might be able to change tomorrow’s weather to whatever you desire. Simply upload a new weather control instruction, if it’s valid we’ll get right to it.

Tomorrow’s weather: RAINING. So sad.

Malicious file upload?

The provided archive contained the following essential structure:

fake-weather
├─ static
├─ Dockerfile
├─ run.py

The static folder contained some bootstrap css files and two images, nothing of interest.

Walkthrough

When visiting the website you will be greeted with the rain image and the sentence

Tomorrow’s weather: RAINING. So sad.

But the only thing we care about is the upload functionality. There you have the option to upload two files and submit them to “change the weather”.

What happens in the backend after the upload is as follows (note that I have shortened some strings for better readability):

f1=uploaded_file.read()
f2=uploaded_file2.read()

sha1_1 = hashlib.sha1(f1).hexdigest()
sha1_2 = hashlib.sha1(f2).hexdigest()
md5_1 = hashlib.md5(f1).hexdigest()
md5_2 = hashlib.md5(f2).hexdigest()

# ------------------------------
# some flask template string here
# ------------------------------


if md5_1 == "d41d8cd98f00b204e9800998ecf8427e" or md5_2=="d41d8cd98f00b204e9800998ecf8427e": # if empty
    return f"There must be weather. Try something else.",403
if sha1_1[1:3] == "87" and sha1_1[7:9]=="7f":
    return f"no cheating. Try something else.",403
if sha1_1 != sha1_2:
    return f"Filehash_NOW: {sha1_1} DOES NOT match Filehash_FUTURE: {sha1_2}. The Hash must be the same, otherwise everyone could control the weather ;)",403
if md5_1 == md5_2:
    return f"WeatherContent_NOW: {md5_1}DOES match WeatherContent_FUTURE: {md5_2}. The File Content is the same. Keeping the same weather doesn't make sense",403

return f"The weather has been changed. Here's your flag {os.environ['FLAG']}",200

So the aim seems to be to submit two non-empty PDFs (although the ‘‘check’’ if it’s a PDF is basically non-existent) which have the same SHA1 hash but different md5 hashes.

The SHA1 hashing algorithm is known to be weak, and probably the best known example of a SHA1 collision is Google’s SHAttered project. However, the hash of these two files is 38762cf7f55934b34d179ae6a4c80cadccbb7f0a, which will fail this check:

if sha1_1[1:3] == "87" and sha1_1[7:9]=="7f":
    return f"no cheating. Try something else.",403

A friend of mine bypassed this check by looking for different files that had a SHA1 collision (I don’t know which ones), I went with a different approach. I appended the same content (a ‘1’) to both files and uploaded them. What happens is that they get a completely different SHA1 hash, but it’s still the same. I’m not smart enough to know why, according to my understanding of hash algorithms, they should now be completely different. My thought process was: worth a try. But hey, it worked. So what I did was download the SHAttered PDFS and:

echo "1" >> shattered-1.pdf
echo "1" >> shattered-2.pdf

After that, the SHA1 hash of both files is dbbd89aa905075b7f87ad276b4d72e365e490def, which doesn’t fail the hard check, and they have different MD5 hashes. Uploading these two returned the message with the flag in it :)