Introduction
Context Explanation
Craphp asks for a password and hands back a flag if you get it right. The source (index.php) is provided, and the whole thing hinges on it using CRC (Cyclic Redundancy Check) hashes in its password check.

Directive
Bypass the password check and read the flag. The catch is that CRC isn’t collision-resistant, so instead of recovering the real password we forge a different one that produces the same CRC16 and CRC8.
Solution
Analyzing Source Code
The check in index.php works like this:
- The real password is hardcoded:
$MYPASSWORD = "AdM1nP@assW0rd!";. - Our input is rejected unless its length matches
$MYPASSWORD. - It computes
crc16andcrc8of both the hardcoded password and our input. - If both CRCs match and our input isn’t literally
$MYPASSWORD, it prints$FLAG.
The relevant snippet:
if(isset($_POST['password']) && strlen($MYPASSWORD) == strlen($_POST['password'])) {
$pwhash1 = crc16($MYPASSWORD);
$pwhash2 = crc8($MYPASSWORD);
$password = $_POST['password'];
$pwhash3 = crc16($password);
$pwhash4 = crc8($password);
if($MYPASSWORD == $password) {
die("oops. Try harder!");
}
if($pwhash1 != $pwhash3) {
die("Oops. Nope. Try harder!");
}
if($pwhash2 != $pwhash4) {
die("OoOps. Not quite. Try harder!");
}
$access = true;
if($access) {
echo "You win a flag: $FLAG";
} else {
echo "Denied! :-(";
}
} else {
echo "Try harder!";
}
Observations and Vulnerability Insight
The design looks fine until you notice it’s leaning on CRC for security. CRC16 and CRC8 are error-detection checksums, not cryptographic hashes, and they collide freely: plenty of different inputs share the same CRC value. Stacking two of them (CRC16 + CRC8) narrows the search but doesn’t fix anything, the combined space is only 24 bits, trivially brute-forceable.
So we don’t need the real password, just a collision that satisfies:
"AdM1nP@assW0rd!" != $password
strlen("AdM1nP@assW0rd!") == strlen($password)
crc16("AdM1nP@assW0rd!") == crc16($password)
crc8("AdM1nP@assW0rd!") == crc8($password)
Exploitation
Crafting the Collision
Brute force it: generate random 15-character candidates (the length of $MYPASSWORD) until one matches both CRCs of the hardcoded password. A short PHP script does the job, reusing the challenge’s own crc16/crc8 so the values line up exactly:
<?php
$MYPASSWORD = "AdM1nP@assW0rd!";
$pwhash1 = crc16($MYPASSWORD);
$pwhash2 = crc8($MYPASSWORD);
for ($i = 0; $i < 100000000; $i++) {
$password = str_random(strlen($MYPASSWORD));
$pwhash3 = crc16($password);
$pwhash4 = crc8($password);
if ($pwhash1 == $pwhash3 && $pwhash2 == $pwhash4) {
echo "Collision found ! : $password\n";
break;
}
}
function str_random($length) {
$characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+';
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, strlen($characters) - 1)];
}
return $randomString;
}
function crc16($string) {
$crc = 0xFFFF;
for ($x = 0; $x < strlen ($string); $x++) {
$crc = $crc ^ ord($string[$x]);
for ($y = 0; $y < 8; $y++) {
if (($crc & 0x0001) == 0x0001) {
$crc = (($crc >> 1) ^ 0xA001);
} else { $crc = $crc >> 1; }
}
}
return $crc;
}
// https://stackoverflow.com/questions/507041/crc8-check-in-php/73305496#73305496
function crc8($input)
{
$crc8Table = [
0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15,
0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65,
0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5,
0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85,
0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2,
0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2,
0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32,
0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42,
0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C,
0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC,
0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C,
0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C,
0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B,
0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B,
0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB,
0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB,
0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3
];
$byteArray = unpack('C*', $input);
$len = count($byteArray);
$crc = 0;
for ($i = 1; $i <= $len; $i++) {
$crc = $crc8Table[($crc ^ $byteArray[$i]) & 0xff];
}
return $crc & 0xff;
}
?>
Accessing the Flag
The script lands a collision pretty quickly:
┌──(kali㉿kali)-[~/Desktop]
└─$ php crc_collision.php
Collision found ! : Tp#UTARjQMz+6X8
Submitting it clears both CRC checks and the server coughs up the flag:

You win a flag: ENO{Cr4hP_CRC_Collison_1N_P@ssw0rds!}
Tips & Tricks
- CRC is not a hash. CRC16/CRC8 are built for detecting transmission errors, not for comparing secrets. Seeing them in an auth check is a red flag.
- Spot small hash spaces. When a challenge validates against a hash, check its output size. 24 bits here is nothing to brute-force.
- Reuse the target’s own code. Copying the challenge’s
crc16/crc8into your script guarantees the values match and saves reimplementation bugs.