Rolling your own crypto, the 4chan way
I got hold of the 4chan leaked source code and took a gander at userpwd.php
,
which seems to provide session cookies.
They roll their own crypto (!! D:), and it definitely looks interesting, to say the least :D
They use a simple XOR/one time pad cipher with a set key (== not used one time!). They do mix in a nonce, which could save them a little, but then it's just appended to the ciphertext.
Part of userpwd.php
// around line 784 in userpwd.php
public function getEncodedData() {
...
list($data, $nonce) = self::encrypt($data);
if (!$data) {
return false;
}
// Version + Nonce
$data = pack('C', self::VERSION) . $nonce . $data;
return self::b64_encode($data);
}
private static function encrypt($data) {
$data_len = strlen($data);
$key = hex2bin(self::XOR_KEY);
$nonce = openssl_random_pseudo_bytes(self::NONCE_SIZE);
if (!$data_len || !$nonce || $data_len > strlen($key)) {
return false;
}
$output_nonced = '';
// Apply nonce
$ni = 0;
for ($di = 0; $di < $data_len; ++$di) {
if ($ni >= self::NONCE_SIZE) {
$ni = 0;
}
$output_nonced = $output_nonced . ($data[$di] ^ $nonce[$ni]);
$ni++;
}
$output = '';
// XOR Encrypt
for ($i = 0; $i < $data_len; ++$i) {
$output = $output . ($output_nonced[$i] ^ $key[$i]);
}
return [ $output, $nonce ];
}
From this, you should be able to compute the encryption key (if you don't already know it because their code leaks and they have it hardcoded):
key[i] = ciphertext[i] ^ nonce[i % NONCE_SIZE] ^ plaintext[i]
You know at least parts of the plaintext, since it's just your IP, or other user info.
Ok, so you can read the cookie. They also sign it though, so you can't really change it.
Well... They use HMAC-SHA-1
, which may(?) be fine, but then they compare the
computed hash with the one you provided using a simple string comparison. In
theory you should be able to guess the signature/hash byte by byte, by timing
how long the comparison takes.
Part of userpwd.php
// around line 280 in userpwd.php
// Password signature
$valid_pwd_sig = $this->calc_sig(
[
$pwd_raw, $creation_ts, $activity_ts, $action_ts, $env_ts, $verified_level,
$post_count, $img_count, $thread_count, $report_count, $action_buffer, $ip_change_score,
$domain
]
);
...
if ($valid_pwd_sig && $valid_pwd_sig === $pwd_sig) {
...
}
$pwd_sig
here is the decoded signature from the cookie.
The cookie contains 4 signatures of different parts of the message, which seems unnecessary. A single signature for the whole cookie would've sufficed.
To add to injury, they truncate the signature to only 4 bytes. With the
possibility of a timing attack, this is very much bruteforceable (without even
decrypting the message -- the XOR cipher has no diffusion, changing a byte at
position i
in the ciphertext will change exactly the same way the byte at
position i
in the plaintext).
For the same cost, they could've included a single 16 byte signature, which is
almost the full 20 byte SHA-1
hash! This would also get rid of a second
timing attack, as they are not checking all the signatures together, but one
after another, retuning early whenever a check fails.
For example:
You can unexpire and old session.
You create a session for an account, whichever you want. You record the time
you created it. The encrypted cookie contains the field creation_ts
, which
decides when the session expires. You can reasonably guess what the field
contains, as you know when you created your session. You thus know the
ciphertext (cookie), nonce (also from cookie), and even the part of the
plaintext you want to change. You can calculate the part of the key which is
used to encrypt this part of the cookie.
Now you can decrypt the creation_ts
field of any cookie, since the key
stays the same.
You now decrypt the creation_ts
of the session you want to change.
Now modify the cookie to change the creation_ts
:
encrypted_creation_ts = creation_ts ^ nonce ^ key
new_encrypted_creation_ts = encrypted_creation_ts ^ creation_ts ^ new_creation_ts
= creation_ts ^ nonce ^ key ^ creation_ts ^ new_creation_ts
= new_creation_ts ^ nonce ^ key
The cookie has now our new creation_ts
field, but the signature is incorrect.
The signature is the tricky part. You probably don't want to blindly bruteforce
it, as it would require on avg. 2^31
tries. It should be possible to do a
timing attack: trying to change the signature's first byte, and advancing to
the next one whenever the error response takes a little bit longer. You will
want very fast and stable internet, and it will probably take a lot of tries to
conclude which of the 255 values takes longer to respond to, but it should be
orders of magnitude faster, and maybe doable :)
After you forge the signature, you're done! You now have a resuscitated session cookie.
I may be wrong about all of this, please correct me if that's the case. Also, I am not sure what impact this could actually have. I don't think the cookie has any data in it, which couldn't be shown to the cookie holder. The only thing I would actually be worried about is the timing attack, which I am not sure is even feasible with all the traffic 4chan gets.
All in all: weak crypto in my opinion, but idk what the consequences would actually be. I am not a cryptographer/cryptoanalyst.
DON'T ROLL YOUR OWN CRYPTO :)