commit a5e84c786280f373f70d20f3464883d1bc54f60b
Author: Louis Burda <quent.burda@gmail.com>
Date: Mon, 1 Apr 2024 20:55:25 +0200
Add solution
Diffstat:
9 files changed, 647 insertions(+), 0 deletions(-)
diff --git a/chall/description b/chall/description
@@ -0,0 +1,5 @@
+If you give a user some data and read it from the user later (e.g. when using cookies), you have to ensure the user has not tampered with it.
+
+This challenge implements a simple, but insecure MAC scheme.
+
+https://en.wikipedia.org/wiki/Message_authentication_code
diff --git a/chall/intro-crypto-2.zip b/chall/intro-crypto-2.zip
Binary files differ.
diff --git a/solve/.gitignore b/solve/.gitignore
@@ -0,0 +1 @@
+__pycache__
diff --git a/solve/flag b/solve/flag
@@ -0,0 +1 @@
+CSCG{Should_have_used_HMAC_or_KMAC_instead-.-}
diff --git a/solve/hlextend.py b/solve/hlextend.py
@@ -0,0 +1,470 @@
+# Copyright (C) 2014 by Stephen Bradshaw
+#
+# SHA1 and SHA2 generation routines from SlowSha https://code.google.com/p/slowsha/
+# which is: Copyright (C) 2011 by Stefano Palazzo
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+'''
+ Pure Python Hash Length Extension module.
+
+ Currently supports SHA1, SHA256 and SHA512, more algorithms will
+ be added in the future.
+
+
+ Create a hash by calling one of the named constuctor functions:
+ sha1(), sha256(), and sha512(), or by calling new(algorithm).
+
+ The hash objects have the following methods:
+
+ hash(message):
+
+ Feeds data into the hash function using the normal interface.
+
+ extend(appendData, knownData, secretLength, startHash, raw=False):
+
+ Performs a hash length extension attack. Returns the string to
+ use when appending data.
+
+ hexdigest():
+
+ Returns a hexlified version of the hash output.
+
+
+ Assume you have a hash generated from an unknown secret value concatenated with
+ a known value, and you want to be able to produce a valid hash after appending
+ additional data to the known value.
+
+ If the hash algorithm used is one of the vulnerable functions implemented in
+ this module, is is possible to achieve this without knowing the secret value
+ as long as you know (or can guess, perhaps by brute force) the length of that
+ secret value. This is called a hash length extension attack.
+
+
+ Given an existing sha1 hash value '52e98441017043eee154a6d1af98c5e0efab055c',
+ known data of 'hello', an unknown secret of length 10 and data you wish
+ to append of 'file', you would do the following to perform the attack:
+
+ >>> import hlextend
+ >>> sha = hlextend.new('sha1')
+ >>> print sha.extend('file', 'hello', 10, '52e98441017043eee154a6d1af98c5e0efab055c')
+ 'hello\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00
+ \\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00
+ \\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00xfile'
+ >>> print sha.hexdigest()
+ c60fa7de0860d4048a3bfb36b70299a95e6587c9
+
+ The unknown secret (of length 10), that when hashed appended with 'hello' produces
+ a SHA1 hash of '52e98441017043eee154a6d1af98c5e0efab055c', will then produce
+ a SHA1 hash of 'c60fa7de0860d4048a3bfb36b70299a95e6587c9' when appended with the output
+ from the extend function above.
+
+ If you are not sure of the exact length of the secret value, simply try the above
+ multiple times specifying different values for the length to brute force.
+
+'''
+
+
+
+from re import match
+from math import ceil
+
+
+__version__ = "0.1"
+
+
+
+class Hash(object):
+ '''Parent class for hash functions'''
+
+
+ def hash(self, message):
+ '''Normal input for data into hash function'''
+
+ length = bin(len(message) * 8)[2:].rjust(self._blockSize, "0")
+
+ while len(message) > self._blockSize:
+ self._transform(''.join([bin(ord(a))[2:].rjust(8, "0") for a in message[:self._blockSize]]))
+ message = message[self._blockSize:]
+
+ message = self.__hashBinaryPad(message, length)
+
+
+ for a in range(len(message) // self._b2):
+ self._transform(message[a * self._b2:a * self._b2 + self._b2])
+
+
+
+ def extend(self, appendData, knownData, secretLength, startHash, raw=False):
+ '''Hash length extension input for data into hash function'''
+
+ self.__checkInput(secretLength, startHash)
+ self.__setStartingHash(startHash)
+
+ extendLength = self.__hashGetExtendLength(secretLength, knownData, appendData)
+
+ message = appendData
+
+ while len(message) > self._blockSize:
+ self._transform(''.join([bin(ord(a))[2:].rjust(8, "0") for a in message[:self._blockSize]]))
+ message = message[self._blockSize:]
+
+ message = self.__hashBinaryPad(message, extendLength)
+
+ for i in range(len(message) // self._b2):
+ self._transform(message[i * self._b2:i * self._b2 + self._b2])
+
+ return self.__hashGetPadData(secretLength, knownData, appendData, raw=raw)
+
+
+ def hexdigest(self):
+ '''Outputs hash data in hexlified format'''
+ return ''.join( [ (('%0' + str(self._b1) + 'x') % (a)) for a in self.__digest()])
+
+
+ def __init__(self):
+ # pre calculate some values that get used a lot
+ self._b1 = self._blockSize/8
+ self._b2 = self._blockSize*8
+
+
+
+ def __digest(self):
+ return [self.__getattribute__(a) for a in dir(self) if match('^_h\d+$', a)]
+
+
+ def __setStartingHash(self, startHash):
+ c = 0
+ hashVals = [ int(startHash[a:a+int(self._b1)],base=16) for a in range(0,len(startHash), int(self._b1)) ]
+ for hv in [ a for a in dir(self) if match('^_h\d+$', a) ]:
+ self.__setattr__(hv, hashVals[c])
+ c+=1
+
+
+ def __checkInput(self, secretLength, startHash):
+ if not isinstance(secretLength, int):
+ raise TypeError('secretLength must be a valid integer')
+ if secretLength < 1:
+ raise ValueError('secretLength must be grater than 0')
+ if not match('^[a-fA-F0-9]{' + str(len(self.hexdigest())) + '}$', startHash):
+ raise ValueError('startHash must be a string of length ' + str(len(self.hexdigest())) + ' in hexlified format')
+
+
+ def __byter(self, byteVal):
+ '''Helper function to return usable values for hash extension append data'''
+ if byteVal < 0x20 or byteVal > 0x7e:
+ return '\\x%02x' %(byteVal)
+ else:
+ return chr(byteVal)
+
+
+ def __binToByte(self, binary):
+ '''Convert a binary string to a byte string'''
+ return ''.join([ chr(int(binary[a:a+8],base=2)) for a in range(0,len(binary),8) ])
+
+
+
+ def __hashGetExtendLength(self, secretLength, knownData, appendData):
+ '''Length function for hash length extension attacks'''
+ # binary length (secretLength + len(knownData) + size of binarysize+1) rounded to a multiple of blockSize + length of appended data
+ originalHashLength = int(ceil((secretLength+len(knownData)+self._b1+1)/float(self._blockSize)) * self._blockSize)
+ newHashLength = originalHashLength + len(appendData)
+ return bin(newHashLength * 8)[2:].rjust(self._blockSize, "0")
+
+
+ def __hashGetPadData(self, secretLength, knownData, appendData, raw=False):
+ '''Return append value for hash extension attack'''
+ originalHashLength = bin((secretLength+len(knownData)) * 8)[2:].rjust(self._blockSize, "0")
+ padData = ''.join(bin(ord(i))[2:].rjust(8, "0") for i in knownData) + "1"
+ padData += "0" * (((self._blockSize*7) - (len(padData)+(secretLength*8)) % self._b2) % self._b2) + originalHashLength
+ if not raw:
+ return ''.join([ self.__byter(int(padData[a:a+8],base=2)) for a in range(0,len(padData),8) ]) + appendData
+ else:
+ return self.__binToByte(padData) + appendData
+
+
+ def __hashBinaryPad(self, message, length):
+ '''Pads the final blockSize block with \x80, zeros, and the length, converts to binary'''
+ message = ''.join(bin(ord(i))[2:].rjust(8, "0") for i in message) + "1"
+ message += "0" * (((self._blockSize*7) - len(message) % self._b2) % self._b2) + length
+ return message
+
+
+
+
+class SHA1 (Hash):
+
+ _h0, _h1, _h2, _h3, _h4, = (
+ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0)
+
+ _blockSize = 64
+
+
+ def _transform(self, chunk):
+
+ lrot = lambda x, n: (x << n) | (x >> (32 - n))
+ w = []
+
+ for j in range(len(chunk) // 32):
+ w.append(int(chunk[j * 32:j * 32 + 32], 2))
+
+ for i in range(16, 80):
+ w.append(lrot(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1)
+ & 0xffffffff)
+
+ a = self._h0
+ b = self._h1
+ c = self._h2
+ d = self._h3
+ e = self._h4
+
+ for i in range(80):
+
+ if i <= i <= 19:
+ f, k = d ^ (b & (c ^ d)), 0x5a827999
+ elif 20 <= i <= 39:
+ f, k = b ^ c ^ d, 0x6ed9eba1
+ elif 40 <= i <= 59:
+ f, k = (b & c) | (d & (b | c)), 0x8f1bbcdc
+ elif 60 <= i <= 79:
+ f, k = b ^ c ^ d, 0xca62c1d6
+
+ temp = lrot(a, 5) + f + e + k + w[i] & 0xffffffff
+ a, b, c, d, e = temp, a, lrot(b, 30), c, d
+
+ self._h0 = (self._h0 + a) & 0xffffffff
+ self._h1 = (self._h1 + b) & 0xffffffff
+ self._h2 = (self._h2 + c) & 0xffffffff
+ self._h3 = (self._h3 + d) & 0xffffffff
+ self._h4 = (self._h4 + e) & 0xffffffff
+
+
+
+class SHA256 (Hash):
+
+ _h0, _h1, _h2, _h3, _h4, _h5, _h6, _h7 = (
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
+ 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19)
+
+ _blockSize = 64
+
+
+ def _transform(self, chunk):
+ rrot = lambda x, n: (x >> n) | (x << (32 - n))
+ w = []
+
+ k = [
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+ 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+ 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+ 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+ 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+ 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2]
+
+ for j in range(len(chunk) // 32):
+ w.append(int(chunk[j * 32:j * 32 + 32], 2))
+
+ for i in range(16, 64):
+ s0 = rrot(w[i - 15], 7) ^ rrot(w[i - 15], 18) ^ (w[i - 15] >> 3)
+ s1 = rrot(w[i - 2], 17) ^ rrot(w[i - 2], 19) ^ (w[i - 2] >> 10)
+ w.append((w[i - 16] + s0 + w[i - 7] + s1) & 0xffffffff)
+
+ a = self._h0
+ b = self._h1
+ c = self._h2
+ d = self._h3
+ e = self._h4
+ f = self._h5
+ g = self._h6
+ h = self._h7
+
+ for i in range(64):
+ s0 = rrot(a, 2) ^ rrot(a, 13) ^ rrot(a, 22)
+ maj = (a & b) ^ (a & c) ^ (b & c)
+ t2 = s0 + maj
+ s1 = rrot(e, 6) ^ rrot(e, 11) ^ rrot(e, 25)
+ ch = (e & f) ^ ((~ e) & g)
+ t1 = h + s1 + ch + k[i] + w[i]
+
+ h = g
+ g = f
+ f = e
+ e = (d + t1) & 0xffffffff
+ d = c
+ c = b
+ b = a
+ a = (t1 + t2) & 0xffffffff
+
+ self._h0 = (self._h0 + a) & 0xffffffff
+ self._h1 = (self._h1 + b) & 0xffffffff
+ self._h2 = (self._h2 + c) & 0xffffffff
+ self._h3 = (self._h3 + d) & 0xffffffff
+ self._h4 = (self._h4 + e) & 0xffffffff
+ self._h5 = (self._h5 + f) & 0xffffffff
+ self._h6 = (self._h6 + g) & 0xffffffff
+ self._h7 = (self._h7 + h) & 0xffffffff
+
+
+
+
+class SHA512 (Hash):
+
+ _h0, _h1, _h2, _h3, _h4, _h5, _h6, _h7 = (
+ 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b,
+ 0xa54ff53a5f1d36f1, 0x510e527fade682d1, 0x9b05688c2b3e6c1f,
+ 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179)
+
+ _blockSize = 128
+
+
+ def _transform(self, chunk):
+
+ rrot = lambda x, n: (x >> n) | (x << (64 - n))
+ w = []
+
+ k = [
+ 0x428a2f98d728ae22, 0x7137449123ef65cd,
+ 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc,
+ 0x3956c25bf348b538, 0x59f111f1b605d019,
+ 0x923f82a4af194f9b, 0xab1c5ed5da6d8118,
+ 0xd807aa98a3030242, 0x12835b0145706fbe,
+ 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2,
+ 0x72be5d74f27b896f, 0x80deb1fe3b1696b1,
+ 0x9bdc06a725c71235, 0xc19bf174cf692694,
+ 0xe49b69c19ef14ad2, 0xefbe4786384f25e3,
+ 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65,
+ 0x2de92c6f592b0275, 0x4a7484aa6ea6e483,
+ 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5,
+ 0x983e5152ee66dfab, 0xa831c66d2db43210,
+ 0xb00327c898fb213f, 0xbf597fc7beef0ee4,
+ 0xc6e00bf33da88fc2, 0xd5a79147930aa725,
+ 0x06ca6351e003826f, 0x142929670a0e6e70,
+ 0x27b70a8546d22ffc, 0x2e1b21385c26c926,
+ 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df,
+ 0x650a73548baf63de, 0x766a0abb3c77b2a8,
+ 0x81c2c92e47edaee6, 0x92722c851482353b,
+ 0xa2bfe8a14cf10364, 0xa81a664bbc423001,
+ 0xc24b8b70d0f89791, 0xc76c51a30654be30,
+ 0xd192e819d6ef5218, 0xd69906245565a910,
+ 0xf40e35855771202a, 0x106aa07032bbd1b8,
+ 0x19a4c116b8d2d0c8, 0x1e376c085141ab53,
+ 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8,
+ 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb,
+ 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3,
+ 0x748f82ee5defb2fc, 0x78a5636f43172f60,
+ 0x84c87814a1f0ab72, 0x8cc702081a6439ec,
+ 0x90befffa23631e28, 0xa4506cebde82bde9,
+ 0xbef9a3f7b2c67915, 0xc67178f2e372532b,
+ 0xca273eceea26619c, 0xd186b8c721c0c207,
+ 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178,
+ 0x06f067aa72176fba, 0x0a637dc5a2c898a6,
+ 0x113f9804bef90dae, 0x1b710b35131c471b,
+ 0x28db77f523047d84, 0x32caab7b40c72493,
+ 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c,
+ 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a,
+ 0x5fcb6fab3ad6faec, 0x6c44198c4a475817]
+
+ for j in range(len(chunk) // 64):
+ w.append(int(chunk[j * 64:j * 64 + 64], 2))
+
+ for i in range(16, 80):
+ s0 = rrot(w[i - 15], 1) ^ rrot(w[i - 15], 8) ^ (w[i - 15] >> 7)
+ s1 = rrot(w[i - 2], 19) ^ rrot(w[i - 2], 61) ^ (w[i - 2] >> 6)
+ w.append((w[i - 16] + s0 + w[i - 7] + s1) & 0xffffffffffffffff)
+
+ a = self._h0
+ b = self._h1
+ c = self._h2
+ d = self._h3
+ e = self._h4
+ f = self._h5
+ g = self._h6
+ h = self._h7
+
+ for i in range(80):
+ s0 = rrot(a, 28) ^ rrot(a, 34) ^ rrot(a, 39)
+ maj = (a & b) ^ (a & c) ^ (b & c)
+ t2 = s0 + maj
+ s1 = rrot(e, 14) ^ rrot(e, 18) ^ rrot(e, 41)
+ ch = (e & f) ^ ((~ e) & g)
+ t1 = h + s1 + ch + k[i] + w[i]
+
+ h = g
+ g = f
+ f = e
+ e = (d + t1) & 0xffffffffffffffff
+ d = c
+ c = b
+ b = a
+ a = (t1 + t2) & 0xffffffffffffffff
+
+ self._h0 = (self._h0 + a) & 0xffffffffffffffff
+ self._h1 = (self._h1 + b) & 0xffffffffffffffff
+ self._h2 = (self._h2 + c) & 0xffffffffffffffff
+ self._h3 = (self._h3 + d) & 0xffffffffffffffff
+ self._h4 = (self._h4 + e) & 0xffffffffffffffff
+ self._h5 = (self._h5 + f) & 0xffffffffffffffff
+ self._h6 = (self._h6 + g) & 0xffffffffffffffff
+ self._h7 = (self._h7 + h) & 0xffffffffffffffff
+
+
+
+
+
+def new(algorithm):
+ obj = {
+ 'sha1': SHA1,
+ 'sha256': SHA256,
+ 'sha512': SHA512,
+ }[algorithm]()
+ return obj
+
+
+
+def sha1():
+ ''' Returns a new sha1 hash object '''
+ return new('sha1')
+
+
+
+def sha256():
+ ''' Returns a new sha256 hash object '''
+ return new('sha256', )
+
+
+
+def sha512():
+ ''' Returns a new sha512 hash object '''
+ return new('sha512', )
+
+
+
+__all__ = ('sha1', 'sha256', 'sha512')
+
+
diff --git a/solve/main.py b/solve/main.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python3
+
+from hashlib import sha1
+from base64 import b64encode, b64decode
+from secrets import token_hex
+
+from secret import FLAG
+
+
+KEY = token_hex(16)
+
+
+def get_mac(data: bytes) -> str:
+ return sha1(KEY.encode("latin1") + data).hexdigest()
+
+
+def parse_token(token: str) -> dict:
+ # Decode token
+ token = b64decode(token)
+
+ # Check the MAC
+ token, mac = token.split(b"|mac=")
+ if get_mac(token) != mac.decode("latin1"):
+ return None
+
+ # Parse values
+ values = dict()
+ for part in token.decode("latin1").split("|"):
+ key, value = part.split("=")
+ values[key] = value
+ return values
+
+
+def generate_token(values: dict) -> str:
+ token = "|".join(f"{key}={value}" for key, value in values.items())
+ secure_token = f"{token}|mac={get_mac(token.encode('latin1'))}"
+
+ return b64encode(secure_token.encode("latin1")).decode("latin1")
+
+
+def handle_register():
+ name = input("What is you name? ")
+ animal = input("What is your favorite animal? ")
+
+ token = generate_token(
+ {
+ "name": name,
+ "animal": animal,
+ "admin": "false",
+ }
+ )
+
+ print("Here is your access token:", token)
+
+
+def handle_show_animal_videos():
+ user_data = parse_token(input("Enter access token: "))
+
+ if user_data is None:
+ print("Invalid token.")
+ return
+
+ print(
+ f"\nHere are some {user_data['animal']} videos for you: https://www.youtube.com/results?search_query=funny+{user_data['animal']}+video+compilation"
+ )
+
+
+def handle_show_flag():
+ user_data = parse_token(input("Enter access token: "))
+
+ if user_data is None:
+ print("Invalid token.")
+ return
+
+ if user_data["admin"] == "true":
+ print("The flag is", FLAG)
+ else:
+ print("You are not an admin.")
+
+
+def main():
+ while True:
+ # Show main menu
+
+ print(
+ """
+ 1. Register
+ 2. Show animal videos
+ 3. Show flag
+ 4. Exit
+ """
+ )
+
+ try:
+ choice = int(input("Enter your choice: "))
+ except ValueError:
+ print("Please enter a number next time.")
+ continue
+ except EOFError:
+ break
+
+ if choice == 1:
+ handle_register()
+ elif choice == 2:
+ handle_show_animal_videos()
+ elif choice == 3:
+ handle_show_flag()
+ elif choice == 4:
+ break
+ else:
+ print("Please enter a valid choice.")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/solve/notes b/solve/notes
@@ -0,0 +1,14 @@
+The solution is a sha1 hash length extension attack which allows us to
+append a suffix to an existing message and given the old hash create a
+valid hash for the new message.
+
+To perform a length extension attack we first add the padding which is added
+during preprocessing of the message if its length is not a mulitple of 64..
+this results in the same hash. The padding is a Merkle–Damgård construction:
+
+msg + 0x80 + 0x00 (N byte pad) + ... (8 byte msg size)
+
+Since the hash represents the state of the hashing function we can then
+begin hashing the next block to add our suffix and create a valid hash
+for the entire message.
+
diff --git a/solve/secret.py b/solve/secret.py
@@ -0,0 +1 @@
+FLAG = "CSCG{Should_have_used_HMAC_or_KMAC_instead-.-}"
diff --git a/solve/solve b/solve/solve
@@ -0,0 +1,40 @@
+#!/usr/bin/env python3
+
+from pwn import *
+from sys import argv
+import base64
+import hlextend
+
+args = argv[1:]
+if args == []:
+ args = ["python3", "main.py"]
+io = process(args)
+
+io.readuntil(b"choice: ")
+io.sendline(b"1")
+
+io.sendline(b"")
+io.sendline(b"")
+
+io.readuntil(b"token: ")
+token = base64.b64decode(io.readline().strip().decode())
+
+sha = hlextend.new("sha1")
+data, mac = token.decode().split("|mac=")
+token = sha.extend("|admin=true", data, 32, mac, raw=True)
+token += "|mac=" + sha.hexdigest()
+token = token.encode("latin1")
+
+print(token)
+
+io.readuntil(b"choice: ")
+io.sendline(b"3")
+
+io.readuntil(b"token: ")
+io.sendline(base64.b64encode(token))
+
+print(io.readline().decode().strip())
+
+io.readuntil(b"choice: ")
+io.sendline(b"4");
+