This will be a write-up for the three shorter / more miscellaneous challenges I wrote:
if-it-leadsgitfabjengacrypt
As always, you can find copies of the binaries, containers, and full solution in our GitHub repo!
if-it-leads
I wanted to do a Citrixbleed-style challenge ever since the vulnerability came out, and this is it!
The TL;DR behind Citrixbleed2 (CVE-2023-4966) (and also this challenge) is that snprintf has
a surprising behavior: it doesn’t return the number of bytes it wrote, it returns
the number of bytes it wanted to write. So if it tries to write more than
the length value, it’ll return a different size than what was actually
written.
The specifically problematic line is:
fprintf(stderr, "Release year? --> ");
int year;
if(scanf("%d", &year) != 1) {
fprintf(stderr, "Invalid year!\n");
exit(1);
}
offset += snprintf(idv3 + offset, 5, "%04d", year);
while(getchar() != '\n') {} // discard rest of line
An integer can be up to 11 characters long (counting the - for negative
numbers). Even though we use the format specifier %04d, the user can enter
up to 11 characters, and instead of the offset increasing by 4 bytes (like it
looks like it’s supposed to), it increases by up to 11. That means that you can
read about 6 bytes past the end of the buffer.
It just so happens - not coincidentally - that that’s where the secret password lies.
I didn’t love using a secret password in addition to the flag, but I only had
about 6 characters and by the time we have the CTF{...} part of the flag,
we didn’t have any space left.
Making it a 64-bit integer or a string would have made the challenge too obvious, in my opinion.
gitfab
I had this idea, “I’ll implement that vulnerability from GitLab and call it ‘Git Fab’!”. That seemed fun. Then I finished the challenge and looked up the reference and realized it was Bit Bucket (CVE-2022-36804), not GitLab. Oops :)
In any case, I mostly just told AI to write a git viewer and make sure it wasn’t vulnerable to command injection. Then I took the output and found the command injection issue (which is what I expected - nobody remembers to check for newlines in shell commands!)
There are definitely other solutions, but I used a newline and command injection:
response = HTTParty.get("#{ base_url }/file/test%22%0acat%20/home/ctf/flag.txt%0aecho%20%22")
I’m pretty sure you can also just add a space and another argument to the URL as well, to view a second file; this wasn’t supposed to be a hard challenge!
jengacrypt
This was a cryptosystem I thought up in a fever dream (when I fell asleep watching some cartoon about Jenga).
It’s a bit-wise cryptosystem where the key tells you what to do with the first 3 bits of the plaintext: either move the second bit to the end or the first and third bits. Just like Jenga, get it??
Here’s the encryption code, in C:
void encrypt_bits(uint8_t *data, int64_t data_length, uint8_t *key, int64_t key_length) {
int64_t i;
uint64_t end = ((data_length - 1) / 3) * 3;
uint64_t key_bit = 0;
for(i = 0; i < end;) {
uint8_t bit = get_bit_from_array((uint8_t*)key, key_length, key_bit);
// We work in groups of three bits
// If it's a 0, move the middle bit of the three to the "top"
// If it's a 1, move the first and third bit
// fprintf(stderr, "(%2d) %d: ", i, bit);
if(bit == 1) {
// fprintf(stderr, "(%2d) |%d | ", key_bit, i + 1);
move_bit_to_end(data, i + 1, data_length);
i += 2;
} else {
// fprintf(stderr, "(%2d) |%d %d| ", key_bit, i, i + 2);
move_bit_to_end(data, i + 0, data_length);
move_bit_to_end(data, i + 1, data_length);
i += 1;
}
key_bit++;
// fprintf(stderr, "\n");
}
// fprintf(stderr, "After: ");
// print_bits_array(data, data_length);
}
And here’s the C decryption code (that wasn’t included).. it’s not super nice, but it works:
void decrypt_bits(uint8_t *data, int64_t data_length, uint8_t *key, int64_t key_length) {
int64_t i;
// Calculate the starting key_bit and offset
uint64_t end = ((data_length - 1) / 3) * 3;
uint64_t key_bit = 0;
uint64_t start;
for(i = 0; i < end;) {
uint8_t bit = get_bit_from_array((uint8_t*)key, key_length, key_bit++);
if(bit == 1) {
start = i;
i += 2;
} else {
start = i;
i += 1;
}
}
key_bit--; // We go one too far
// Round down to the nearest multiple of 3
// fprintf(stderr, "Length = %2d\n", data_length);
for(i = start; i >= 0; ) {
uint8_t bit = get_bit_from_array((uint8_t*)key, key_length, key_bit);
// We work in groups of three bits
// If it's a 0, move the middle bit of the three to the "top"
// If it's a 1, move the first and third bit
// fprintf(stderr, "(%2d) %2d: ", i, bit);
if(bit == 1) {
// fprintf(stderr, "(%2d) |%d/%c| ", key_bit, i + 1, data[data_length - 1]);
// fprintf(stderr, " %s", data);
move_bit_from_end(data, i + 1, data_length);
} else {
// fprintf(stderr, "(%2d) |%d/%c %d/%c|", key_bit, i + 2, data[data_length - 1], i + 0, data[data_length - 2]);
// fprintf(stderr, " %s", data);
move_bit_from_end(data, i + 1, data_length);
move_bit_from_end(data, i, data_length);
}
key_bit--;
uint8_t last_bit = get_bit_from_array((uint8_t*)key, key_length, key_bit);
if(last_bit == 1) {
i -= 2;
} else {
i -= 1;
}
// fprintf(stderr, " -> %s\n", data);
// fprintf(stderr, "%s\n", data);
}
}
And that’s it!
