MOTP

Flag: hkcert22{mistakes-off-the-page}

Vulnerability:

In line 139 of google2fa.php, the verify_key function:

if (self::oath_hotp($binarySeed, $ts) == $key)

The issue is caused by type confusion in PHP and a loose comparison operator is used (==).

When true is provided as the OTP ($key) “<whatever string from oath_hotp>" == true must evaluate to true.

This bypasses the OTP check and the flag is returned.

Exploit: Send a POST request to login.php with the payload:

{
	"username": "admin",
	"password": "admin",
	"otp1": true,
	"otp2": true,
	"otp3": true
}

Side note: I originally thought there was a problem with the SHA1 function used in the OTP generation function and I looked for hash collision. However this is not a crypto challenge, so this idea was discarded.

Official

Flag: hkcert22{offici4l-s4uces-st4ted-it-is-n0t-secure}

Vulnerability: The IV is reused.

const iv = crypto.createHash('sha256').update('myHashedIV').digest();

Exploit: Perform a two-time-pad attack (flag_enc XOR ciphertext XOR plaintext):

(Credit to: https://meowmeowxw.gitlab.io/ctf/utctf-2020-crypto/ - he provided the critical clue to approach and solve this question.)

# these two pairs of I/O are provided in official.js
plaintext = b"My treasure is buried behind Carl's Jr. on Telegraph."
ciphertext = bytes.fromhex("31b9e00aafcd3f7edbd394dc2cb05e7aca8b6bf01fae094a15979062bf190f4d8b0f32ca1a1a6aace3a6efc64b4f15f64a02d9b128")

flag = "hkcert22{<<REDACTED>>}" # this is what we need to find out
flag_enc = bytes.fromhex("14aba31bafdc6c3fd5ce979a2ca0172cd3d471a10eed0e5c508d8a32eb3f0a128e5c6c8323452abcf8e5bcf74d5602f445")
def xor(s1, s2):
	return bytes(x ^ y for x, y in zip(s1, s2))

# these two pairs of I/O are provided in official.js
plaintext = b"My treasure is buried behind Carl's Jr. on Telegraph."
ciphertext = bytes.fromhex("31b9e00aafcd3f7edbd394dc2cb05e7aca8b6bf01fae094a15979062bf190f4d8b0f32ca1a1a6aace3a6efc64b4f15f64a02d9b128")

flag = "hkcert22{<<REDACTED>>}" # this is what we need to find out
flag_enc = bytes.fromhex("14aba31bafdc6c3fd5ce979a2ca0172cd3d471a10eed0e5c508d8a32eb3f0a128e5c6c8323452abcf8e5bcf74d5602f445")

flag = xor(xor(flag_enc, ciphertext), payload)
print(flag)